2026-03-01-tui-improvements-design.md (3620B)
1 # TUI Improvements Design 2 3 ## Problems 4 5 1. **"Syncing" events pollute the event list.** The handler sends `{File: ".", Status: "syncing"}` before every rsync run. These pile up as permanent `⟳ . syncing...` rows and never clear. 6 7 2. **Per-file events don't scale.** A sync transferring 1000 files would produce 1000 event rows, overwhelming the list and slowing TUI updates. 8 9 3. **Event list doesn't fill the terminal.** The visible row count uses a hardcoded `height-10` offset. Tall terminals waste space; short terminals clip. 10 11 4. **No scrolling or timestamps.** Events are a flat, non-navigable list with no time information. 12 13 5. **`r` (full resync) is a dead key.** Shown in the help bar but has no handler. 14 15 6. **Stats bar shows 0.** The `totalSynced` / `totalBytes` / `totalErrors` counters are never updated because nothing sends `SyncStatsMsg`. 16 17 ## Design 18 19 ### Syncing indicator: transient header status 20 21 Remove "syncing" events from the event list. Add a new message type `SyncStatusMsg string` that updates only the header status line. The handler sends `SyncStatusMsg("syncing")` before rsync runs and `SyncStatusMsg("watching")` after. No syncing rows appear in the event list. 22 23 ### Top-level grouping of file events 24 25 After rsync completes, the handler in `cmd/sync.go` groups `result.Files` by top-level path component: 26 27 - Files in subdirectories are grouped by their first path segment. `cmd/sync.go` + `cmd/init.go` + `cmd/root.go` become one event: `✓ cmd/ 3 files 12.3KB`. 28 - Files at the root level get individual events: `✓ main.go 2.1KB`. 29 30 Grouping happens in the handler after rsync returns, so it adds no overhead to the transfer. The TUI receives at most `N_top_level_dirs + N_root_files` events per sync. 31 32 ### Event list fills terminal, scrollable with timestamps 33 34 **Layout**: compute available event rows as `height - 6`: 35 - Header: 3 lines (title, paths, status + blank) 36 - Stats + help: 3 lines 37 38 Pad with empty lines when fewer events exist so the section always fills. 39 40 **Timestamps**: each event row includes `HH:MM:SS` from `evt.Time`: 41 ``` 42 15:04:05 ✓ cmd/ 3 files 12.3KB 120ms 43 15:04:05 ✓ main.go 2.1KB 120ms 44 15:03:58 ✓ internal/ 5 files 45.2KB 200ms 45 ``` 46 47 **Scrolling**: add `offset int` to `DashboardModel`. `j`/`k` or `↑`/`↓` move the viewport. The event list is a window into `filteredEvents()[offset:offset+viewHeight]`. 48 49 ### `r` triggers full resync 50 51 Add a `resyncCh chan struct{}` to `AppModel`, exposed via `ResyncChan()`. When the user presses `r`, the dashboard emits a `ResyncRequestMsg`. AppModel catches it and sends on the channel. The handler in `cmd/sync.go` listens on `resyncCh` in a goroutine and calls `s.Run()` when signalled, feeding results back through the existing event channel. 52 53 ### Stats bar accumulates 54 55 The handler updates running totals (`totalSynced`, `totalBytes`, `totalErrors`) after each sync and sends a `SyncStatsMsg`. The dashboard renders these in the stats section. 56 57 ## Event row format 58 59 ``` 60 HH:MM:SS icon name(padded) detail size duration 61 15:04:05 ✓ cmd/ 3 files 12.3KB 120ms 62 15:04:05 ✓ main.go 2.1KB 120ms 63 15:04:05 ✗ internal/ error ─ ─ 64 ``` 65 66 ## Files to change 67 68 - `internal/tui/dashboard.go` — timestamps, scrolling, fill terminal, remove syncing events 69 - `internal/tui/app.go` — new message types (`SyncStatusMsg`, `ResyncRequestMsg`), resync channel 70 - `cmd/sync.go` — top-level grouping, stats accumulation, resync listener, remove syncing event send