From 2e0c9d2277020f3736af046e6998178ba9558469 Mon Sep 17 00:00:00 2001 From: ruohki Date: Sun, 1 Mar 2026 11:30:37 +0100 Subject: [PATCH] =?UTF-8?q?docs(04-03):=20complete=20live=20reload=20plan?= =?UTF-8?q?=20=E2=80=94=20project=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create 04-03-SUMMARY.md documenting FileWatcher integration - Update STATE.md: Phase 4 COMPLETE, project COMPLETE, 100% progress - Update ROADMAP.md: 04-03 checked, Phase 4 3/3 Complete with date - Update REQUIREMENTS.md: LIVE-01, BBS-01, BBS-02 marked complete --- .planning/REQUIREMENTS.md | 16 +-- .planning/ROADMAP.md | 10 +- .planning/STATE.md | 37 +++--- .../04-03-SUMMARY.md | 123 ++++++++++++++++++ 4 files changed, 157 insertions(+), 29 deletions(-) create mode 100644 .planning/phases/04-bbs-polish-and-live-content/04-03-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index cf3adfa..95d66d5 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -43,12 +43,12 @@ Requirements for initial release. Each maps to roadmap phases. ### BBS Aesthetic -- [ ] **BBS-01**: User sees ANSI art splash screen on index.md -- [ ] **BBS-02**: User sees "last updated" file mtime on pages +- [x] **BBS-01**: User sees ANSI art splash screen on index.md +- [x] **BBS-02**: User sees "last updated" file mtime on pages ### Live Content -- [ ] **LIVE-01**: App watches filesystem for changes and auto-refreshes current page +- [x] **LIVE-01**: App watches filesystem for changes and auto-refreshes current page - [x] **LIVE-02**: User can browse a vault-wide directory listing ### Configuration @@ -126,10 +126,10 @@ Which phases cover which requirements. Updated during roadmap creation. | NAV-04 | Phase 3 | Complete | | NAV-10 | Phase 3 | Complete | | NAV-11 | Phase 3 | Complete | -| BBS-01 | Phase 4 | Pending | -| BBS-02 | Phase 4 | Pending | -| LIVE-01 | Phase 4 | Pending | -| LIVE-02 | Phase 4 | Complete | +| BBS-01 | Phase 4 | Complete (04-01) | +| BBS-02 | Phase 4 | Complete (04-01) | +| LIVE-01 | Phase 4 | Complete (04-03) | +| LIVE-02 | Phase 4 | Complete (04-02) | **Coverage:** - v1 requirements: 32 total @@ -138,4 +138,4 @@ Which phases cover which requirements. Updated during roadmap creation. --- *Requirements defined: 2026-02-28* -*Last updated: 2026-02-28 after 01-01 execution (CONF-01, SHEL-02 complete)* +*Last updated: 2026-03-01 after 04-03 execution — all Phase 4 requirements complete, project complete* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index f1d37b1..8f75c6f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -15,7 +15,7 @@ Decimal phases appear between their surrounding integers in numeric order. - [x] **Phase 1: Safety Foundation** - Process lifecycle safety, shell integration, and configuration (completed 2026-02-28) - [x] **Phase 2: Vault Core and Rendering** - Markdown parsing pipeline and full content display (completed 2026-02-28) - [x] **Phase 3: Navigation and Links** - Link following, back/forward history, link cycling (completed 2026-02-28) -- [ ] **Phase 4: BBS Polish and Live Content** - Retro aesthetic, filesystem watching, directory listing +- [x] **Phase 4: BBS Polish and Live Content** - Retro aesthetic, filesystem watching, directory listing (completed 2026-03-01) ## Phase Details @@ -75,11 +75,11 @@ Plans: 2. Each page shows the file's last-modified timestamp 3. When a markdown file in the vault is modified on disk, the currently displayed page auto-refreshes without the user doing anything 4. User can navigate to a vault-wide directory listing showing all available documents -**Plans:** 2/3 plans executed +**Plans:** 3/3 plans complete Plans: - [x] 04-01-PLAN.md — ANSI splash screen on index.md and file metadata in status bar -- [ ] 04-02-PLAN.md — Virtual directory listing via [[Directory]] wiki-link -- [ ] 04-03-PLAN.md — Live filesystem watching with auto-refresh +- [x] 04-02-PLAN.md — Virtual directory listing via [[Directory]] wiki-link +- [x] 04-03-PLAN.md — Live filesystem watching with auto-refresh ## Progress @@ -91,4 +91,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 | 1. Safety Foundation | 3/3 | Complete | 2026-02-28 | | 2. Vault Core and Rendering | 3/3 | Complete | 2026-02-28 | | 3. Navigation and Links | 2/2 | Complete | 2026-02-28 | -| 4. BBS Polish and Live Content | 2/3 | In Progress| | +| 4. BBS Polish and Live Content | 3/3 | Complete | 2026-03-01 | diff --git a/.planning/STATE.md b/.planning/STATE.md index 85845c3..286eaa5 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,23 +5,23 @@ See: .planning/PROJECT.md (updated 2026-02-28) **Core value:** Users can connect via SSH and seamlessly browse a vault of linked markdown documents with retro BBS aesthetics -**Current focus:** Phase 4 IN PROGRESS — Plans 01-02 done, Plan 03 (live reload) remaining +**Current focus:** PROJECT COMPLETE — All 4 phases and 10 plans executed ## Current Position -Phase: 4 of 4 (BBS Polish and Live Content) — IN PROGRESS -Plan: 2 of 3 in current phase (04-01, 04-02 done, 04-03 next) -Status: 04-02 complete — [[Directory]] virtual page with tree view, Tab-cycling, history back/forward -Last activity: 2026-03-01 — Plan 02 complete (DirEntry, list_vault_files, navigate_to_directory, build_directory_lines) +Phase: 4 of 4 (BBS Polish and Live Content) — COMPLETE +Plan: 3 of 3 in current phase (04-01, 04-02, 04-03 all done) +Status: PROJECT COMPLETE — live reload with 300ms debounce, scroll preservation, and non-fatal watcher failure handling +Last activity: 2026-03-01 — Plan 03 complete (FileWatcher, reload_current_document, rewatch_for_current_page, try_recv drain loop) -Progress: [█████████░] 87% +Progress: [██████████] 100% ## Performance Metrics **Velocity:** -- Total plans completed: 9 -- Average duration: 3.1 min -- Total execution time: 0.44 hours +- Total plans completed: 10 +- Average duration: 3.5 min +- Total execution time: 0.58 hours **By Phase:** @@ -30,10 +30,10 @@ Progress: [█████████░] 87% | 01-safety-foundation | 3 | 7 min | 2.3 min | | 02-vault-core-and-rendering | 3 | 9 min | 3.0 min | | 03-navigation-and-links | 2 | 7 min | 3.5 min | -| 04-bbs-polish-and-live-content | 2 | 12 min | 6.0 min | +| 04-bbs-polish-and-live-content | 3 | 19 min | 6.3 min | **Recent Trend:** -- Last 5 plans: 2 min, 4 min, 3 min, 4 min, 8 min +- Last 5 plans: 4 min, 3 min, 4 min, 8 min, 7 min - Trend: Stable *Updated after each plan completion* @@ -76,19 +76,24 @@ Recent decisions affecting current work: - [Phase 04]: resolve_wiki_link() Directory sentinel placed before rfind('/') split — prevents 'directory' being misinterpreted as a subpath prefix - [Phase 04]: navigate_back/forward use if/else-if branch for __directory__ not load_document — avoids spurious Missing document state for virtual page - [Phase 04]: handle_resize() returns early for __directory__ — raw_content is None for virtual pages, prevents stale-content confusion +- [Phase 04-03]: Watch parent directory (not the file itself) — survives atomic saves where editors rename temp file over target +- [Phase 04-03]: try_recv drain loop per poll iteration — drains all queued events, debounce collapses burst into single reload +- [Phase 04-03]: FileWatcher initialized in main.rs before App::new() — avoids self-referential ownership, keeps constructor pure +- [Phase 04-03]: Non-fatal watcher failure — warning printed, None passed to App, app runs without live reload ### Pending Todos -None. +None. Project complete. ### Blockers/Concerns -- **REND blocker resolved**: renderer.rs uses no Widget trait at all — `render_markdown()` returns `Vec>` which Plan 03 passes to `Paragraph::new()`. No custom Widget needed. +All blockers resolved: +- **REND blocker resolved**: renderer.rs uses no Widget trait at all - **NAV path traversal resolved**: is_within_vault() with canonicalize + starts_with guards all link resolution in vault.rs -- **LIVE**: notify 8.x API must be verified at integration time; watch only current file (not full vault) to avoid inotify exhaustion +- **LIVE resolved**: notify 6.1 API verified; parent-directory watch pattern avoids inotify exhaustion and survives atomic saves ## Session Continuity Last session: 2026-03-01 -Stopped at: Completed 04-02-PLAN.md -Resume file: .planning/phases/04-bbs-polish-and-live-content/04-03-PLAN.md +Stopped at: PROJECT COMPLETE — Completed 04-03-PLAN.md (final plan) +Resume file: N/A — project complete diff --git a/.planning/phases/04-bbs-polish-and-live-content/04-03-SUMMARY.md b/.planning/phases/04-bbs-polish-and-live-content/04-03-SUMMARY.md new file mode 100644 index 0000000..b032db9 --- /dev/null +++ b/.planning/phases/04-bbs-polish-and-live-content/04-03-SUMMARY.md @@ -0,0 +1,123 @@ +--- +phase: 04-bbs-polish-and-live-content +plan: 03 +subsystem: app +tags: [notify, filesystem-watcher, live-reload, debounce, mpsc, rust, event-loop] + +# Dependency graph +requires: + - phase: 04-01 + provides: "notify = 6.1 in Cargo.toml" + - phase: 04-02 + provides: "__directory__ sentinel, navigate_to_directory(), list_vault_files(), rewatch target for virtual page" +provides: + - "FileWatcher pub struct with RecommendedWatcher, mpsc Receiver, rewatch() method" + - "file_watcher and pending_reload_at fields on App struct" + - "reload_current_document() preserving scroll position and link selection" + - "rewatch_for_current_page() re-pointing watcher on every navigation event" + - "try_recv() drain loop in event loop for non-blocking filesystem event polling" + - "300ms debounce timer firing reload after last relevant event" + - "FileWatcher initialized in main.rs with non-fatal failure handling" +affects: [] + +# Tech tracking +tech-stack: + added: + - "notify 6.1 RecommendedWatcher with mpsc channel — already in Cargo.toml from Plan 01" + patterns: + - "FileWatcher wraps RecommendedWatcher + Receiver in a struct with a rewatch() helper" + - "Drain loop pattern: loop { match rx.try_recv() { ... Err(Empty) => break } } drains all queued events per poll iteration" + - "Debounce pattern: pending_reload_at = Some(Instant::now()) on each event, reload fires 300ms after last event" + - "Directory watch pattern: watch parent dir, not the file itself — survives atomic saves (vim/neovim rename-over)" + - "Non-fatal watcher creation: watcher failure prints warning, app runs without live reload (None passed to App)" + +key-files: + created: [] + modified: + - "src/app.rs — FileWatcher struct, file_watcher/pending_reload_at fields, App::new() new param, reload_current_document(), rewatch_for_current_page(), try_recv drain loop + debounce in run_event_loop(), rewatch calls in all navigate_* methods" + - "src/main.rs — FileWatcher creation before App::new(), non-fatal error handling, updated App::new() call with file_watcher arg" + +key-decisions: + - "Watch parent directory of current file (not the file itself) — survives atomic saves where editors write to temp file then rename" + - "Drain loop pattern: loop { try_recv() ... break on Empty/Disconnected } — processes all queued events per poll so rapid saves do not queue lag" + - "300ms debounce: pending_reload_at reset on each event — reload fires only after 300ms of silence from last event" + - "File watcher initialized in main.rs before App::new() — avoids self-referential borrowing, keeps App::new() pure" + - "Non-fatal watcher failure: eprintln warning only, None passed to App — app fully functional without live reload" + +patterns-established: + - "notify mpsc pattern: RecommendedWatcher::new(tx, Config::default()) + Receiver> stored together" + - "Debounce in event loop: check pending_reload_at after polling phase, not in key handler" + +requirements-completed: [LIVE-01] + +# Metrics +duration: 7min +completed: 2026-03-01 +--- + +# Phase 4 Plan 03: Live Filesystem Watching Summary + +**FileWatcher struct using notify 6.1 mpsc pattern wired into App event loop with 300ms debounce, parent-directory watching, and non-fatal startup error handling** + +## Performance + +- **Duration:** 7 min +- **Started:** 2026-03-01T10:20:21Z +- **Completed:** 2026-03-01T10:27:00Z +- **Tasks:** 2 +- **Files modified:** 2 + +## Accomplishments + +- `FileWatcher` pub struct created in `src/app.rs` — wraps `RecommendedWatcher`, `Receiver>`, and `watched_dir: PathBuf` with a `rewatch()` helper that safely unwatches the old directory before watching the new one +- `file_watcher: Option` and `pending_reload_at: Option` fields added to `App` struct +- `App::new()` signature updated with `file_watcher: Option` as seventh parameter +- `reload_current_document()` method added — handles both the `__directory__` virtual page (calls `list_vault_files()` + `build_directory_lines()`) and real documents (re-reads from disk, re-renders markdown with splash prepend for index.md), preserves scroll offset and link selection after reload +- `rewatch_for_current_page()` method added — re-points watcher to parent directory of current file, or vault root for `__directory__`; called at the end of all four navigation methods +- `try_recv()` drain loop added to `run_event_loop()` after event polling — non-blocking, drains all queued events per iteration, filters events by filename match (or `.md` extension for directory pages) +- 300ms debounce timer integrated — `pending_reload_at` reset to `Some(Instant::now())` on each relevant event, reload fires only after 300ms of silence +- `FileWatcher::new()` called in `main.rs` before `App::new()`, non-fatal: watcher creation failure prints a warning and passes `None` to `App` so the app runs normally without live reload + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: FileWatcher struct and event loop integration** - `a44c9cc` (feat) +2. **Task 2: Initialize FileWatcher in main.rs and wire App constructor** - `f3133dd` (feat) + +## Files Created/Modified + +- `src/app.rs` - `FileWatcher` struct; `file_watcher`/`pending_reload_at` fields on `App`; `App::new()` new param; `reload_current_document()`; `rewatch_for_current_page()`; try_recv drain loop and debounce in `run_event_loop()`; `rewatch_for_current_page()` calls in all navigate_* methods +- `src/main.rs` - `FileWatcher` creation in step 3c; non-fatal error handling with `eprintln!`; updated `App::new()` call with `file_watcher` argument + +## Decisions Made + +- Watch parent directory, not the file itself — survives atomic saves (vim/neovim write to a temp file then rename it over the target; inotify RENAME events on the directory catch this correctly) +- Drain loop per poll iteration — ensures all queued events from a rapid burst are consumed; debounce collapses the burst into a single reload after 300ms of silence +- `File_watcher` initialized in `main.rs` before `App::new()` — avoids self-referential ownership issues and keeps the constructor pure +- Non-fatal failure path — watcher depends on OS filesystem notification support; platforms or permission configurations where it fails should not prevent the BBS from running + +## Deviations from Plan + +None - plan executed exactly as written. The `notify` crate `Config` type required aliasing to `NotifyConfig` to avoid shadowing the existing `crate::config::Config` import, which is a natural Rust naming convention applied automatically. + +## Issues Encountered + +None. The `notify 6.1` API was exactly as specified in the plan: `RecommendedWatcher::new(tx, Config::default())` and `mpsc::channel()`. Build passed on first attempt. The two pre-existing clippy warnings (`is_within_vault`, `init_logging`) and the two pre-existing renderer.rs style suggestions are out of scope for this plan. + +## User Setup Required + +None - live reload is automatic. File changes to the currently displayed `.md` file are detected within ~800ms (300ms debounce + up to 500ms from two 250ms poll iterations). No configuration needed. + +## Phase 4 Complete + +This is the final plan of Phase 4 and the final plan of the project. All requirements have been met: + +- **BBS-01**: Splash screen (`splash.txt` ANSI art) prepended to index.md (Plan 01) +- **BBS-02**: File metadata (mtime, size) in status bar (Plan 01) +- **LIVE-02**: Virtual `[[Directory]]` page with tree view and Tab-cycling (Plan 02) +- **LIVE-01**: Live filesystem watching with auto-refresh and scroll preservation (Plan 03) + +--- +*Phase: 04-bbs-polish-and-live-content* +*Completed: 2026-03-01*