Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@ All notable changes to stui will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### ✨ New Features

**Folder Update History Enhancements**
- **Jump to File from History**: Press `Enter` on any file in the folder history modal to navigate breadcrumbs directly to that file's location
- Automatically enters the folder and traverses the directory tree to highlight the selected file
- Graceful error handling: navigates as deep as possible if intermediate directories are missing
- Clear toast messages guide user through navigation errors (missing directories, missing files)
- **Implementation**: Pure path parsing logic with comprehensive test coverage (5 tests), reusable folder entry method

### 🔧 Improvements

**File Preview Datetime Formatting**
- Standardized datetime display in file preview metadata to match folder history format
- Before: `Modified: 2024-01-15T14:30:45.123456789Z` (raw RFC 3339)
- After: `Modified: 2024-01-15 14:30:45` (clean, human-readable)
- Added `format_datetime()` utility function with doctest coverage

### 🧪 Testing

- **604 total tests passing** (up from 569)
- Added 5 new tests for path parsing logic (deep paths, root-level files, spaces in names)
- Added integration test for jump-to-file path parsing
- Added doctest for datetime formatting function
- Zero compiler warnings, zero clippy warnings

---

## [0.9.1] - 2025-11-10

### 🎨 Code Quality & Architecture
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ Display visual indicators for file/folder states following `<file|dir><status>`
- `S`: Toggle reverse sort order
- `t`: Toggle info display (Off → TimestampOnly → TimestampAndSize → Off)
- `p`: Pause/resume folder (folder view only, with confirmation)
- `u`: **Folder Update History** - Shows recent file updates for the selected folder with lazy-loading pagination
- Loads files in batches of 100 as you scroll
- Auto-loads when within 10 items of bottom
- Press `Enter` on a file to jump directly to that file's location in breadcrumbs
- Vim keybindings (optional): `hjkl`, `gg`, `G`, `Ctrl-d/u`, `Ctrl-f/b`

### Search Feature
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ A fast, keyboard-driven terminal UI for managing [Syncthing](https://syncthing.n
- Shows remote files you need to download
- Shows local changes in receive-only folders (added/deleted/modified files)
- Works recursively across entire folder hierarchy
- **Update History**: View recent file changes with timestamps (lazy-loaded pagination) — press `Enter` to jump directly to any file's location
- **Flexible Sorting**: Sort by sync state, name, date, or size
- **File Preview Popup**: View file details, text content, ANSI art, or images directly in terminal
- **Text files**: Scrollable with vim keybindings
Expand Down Expand Up @@ -139,6 +140,7 @@ stui --debug
|-----|--------|--------------|
| `Ctrl-F` / `/` | **Search**: Enter search mode (recursive wildcard search) | No |
| `f` | **Filter**: Toggle out-of-sync filter (shows remote needed files + local changes) | No |
| `u` | **View Update History**: Show recent file updates for folder with lazy-loading pagination (folder view only). Press `Enter` on a file to jump to its location. | No |
| `?` | Show detailed file info popup (metadata, sync state, preview). Note: `Enter` on files also opens preview. | No |
| `c` | **Context-aware**: Change folder type (folder view) OR Copy path (breadcrumb view) | Selection menu / No |
| `p` | Pause/resume folder (folder view only) | Yes |
Expand Down Expand Up @@ -206,6 +208,26 @@ del %LOCALAPPDATA%\stui\cache\cache.db
- No async loading spinners (planned)
- No batch operations for multi-select yet (planned)

## What Stui Cannot Do (Yet?)

Stui is designed for **monitoring and file-level operations**, not initial setup or configuration. You'll still need the Syncthing Web UI for these tasks:

| Category | Missing Features (that Web UI CAN do) | Impact |
|----------|-----------------|--------|
| **Device Management** | Add/remove/edit devices, configure device settings (compression, rate limits, introducer), view device IDs | Cannot set up or manage sync relationships |
| **Folder Setup** | Create/delete folders, edit folder settings (path, label, versioning, intervals, pull order), share folders with devices | Cannot configure new sync folders or modify existing folder settings |
| **Versioning** | Enable/configure versioning schemes (Simple/Staggered/Trashcan/External), browse version history, restore old versions | No access to file version history or recovery |
| **System Configuration** | GUI settings (authentication, theme), connection settings (listen addresses, NAT, UPnP), global bandwidth limits, discovery/relay toggles | Cannot configure Syncthing's network or system behavior |
| **Advanced Ignore Patterns** | Direct `.stignore` editing with complex patterns, pattern validation/help | Limited to adding/removing individual files only |
| **Diagnostics & Monitoring** | Syncthing logs, failed items view | Limited troubleshooting capabilities |
| **System Control** | Restart/shutdown Syncthing, API key management | Must use command line for system administration |

**What Stui DOES Better Than Web UI:**
- File-level browsing, deletion, and restore operations (Web UI doesn't browse individual files)
- Real-time sync state monitoring with visual indicators
- Fast keyboard-driven navigation and search
- Terminal-native file previews (text, images, ANSI art)

## Contributing

Contributions welcome! This project is actively being developed. See [PLAN.md](PLAN.md) for roadmap and [CLAUDE.md](CLAUDE.md) for architecture details.
Expand Down
55 changes: 55 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::services::events::SyncthingEvent;
use crate::utils;
use anyhow::{Context, Result};
use reqwest::Client;
Expand Down Expand Up @@ -657,6 +658,40 @@ impl SyncthingClient {
Ok(updates)
}

/// Fetch folder events from Syncthing event stream
///
/// Returns events starting from `since` event ID, limited to `limit` events.
/// Uses /rest/events endpoint to get historical sync events.
///
/// # Arguments
/// * `since` - Event ID to start from (0 for all history, or recent ID for recent events)
/// * `limit` - Maximum number of events to fetch
///
/// # Returns
/// Vec of SyncthingEvent objects containing event metadata and data
#[allow(dead_code)] // Used in folder history feature (Task 4)
pub async fn get_folder_events(&self, since: u64, limit: usize) -> Result<Vec<SyncthingEvent>> {
let url = format!(
"{}/rest/events?since={}&limit={}",
self.base_url, since, limit
);

let response = self
.client
.get(&url)
.header("X-API-Key", &self.api_key)
.send()
.await
.context("Failed to fetch folder events")?;

let events: Vec<SyncthingEvent> = response
.json()
.await
.context("Failed to parse folder events")?;

Ok(events)
}

/// Pause or resume a folder
///
/// Uses PATCH /rest/config/folders/{id} to set the paused state
Expand Down Expand Up @@ -983,4 +1018,24 @@ mod tests {
state
);
}

#[test]
fn test_get_folder_events_exists() {
// Test that get_folder_events method exists with correct signature
// We can't easily test async functions in unit tests without tokio runtime,
// but we can verify the method exists by calling it in a type-checked way
let client = SyncthingClient {
base_url: "http://localhost:8384".to_string(),
api_key: "test-key".to_string(),
client: reqwest::Client::new(),
};

// Verify method exists by referencing it (won't execute due to being async)
let _method = SyncthingClient::get_folder_events;
// Verify parameters are correct types
let _since: u64 = 0;
let _limit: usize = 100;
// This would need tokio runtime to actually call:
// let _ = client.get_folder_events(_since, _limit).await;
}
}
Loading