docs(04-03): complete live reload plan — project complete

- 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
This commit is contained in:
2026-03-01 11:30:37 +01:00
parent f3133dd8dc
commit 2e0c9d2277
4 changed files with 157 additions and 29 deletions
+8 -8
View File
@@ -43,12 +43,12 @@ Requirements for initial release. Each maps to roadmap phases.
### BBS Aesthetic ### BBS Aesthetic
- [ ] **BBS-01**: User sees ANSI art splash screen on index.md - [x] **BBS-01**: User sees ANSI art splash screen on index.md
- [ ] **BBS-02**: User sees "last updated" file mtime on pages - [x] **BBS-02**: User sees "last updated" file mtime on pages
### Live Content ### 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 - [x] **LIVE-02**: User can browse a vault-wide directory listing
### Configuration ### Configuration
@@ -126,10 +126,10 @@ Which phases cover which requirements. Updated during roadmap creation.
| NAV-04 | Phase 3 | Complete | | NAV-04 | Phase 3 | Complete |
| NAV-10 | Phase 3 | Complete | | NAV-10 | Phase 3 | Complete |
| NAV-11 | Phase 3 | Complete | | NAV-11 | Phase 3 | Complete |
| BBS-01 | Phase 4 | Pending | | BBS-01 | Phase 4 | Complete (04-01) |
| BBS-02 | Phase 4 | Pending | | BBS-02 | Phase 4 | Complete (04-01) |
| LIVE-01 | Phase 4 | Pending | | LIVE-01 | Phase 4 | Complete (04-03) |
| LIVE-02 | Phase 4 | Complete | | LIVE-02 | Phase 4 | Complete (04-02) |
**Coverage:** **Coverage:**
- v1 requirements: 32 total - v1 requirements: 32 total
@@ -138,4 +138,4 @@ Which phases cover which requirements. Updated during roadmap creation.
--- ---
*Requirements defined: 2026-02-28* *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*
+5 -5
View File
@@ -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 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 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) - [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 ## Phase Details
@@ -75,11 +75,11 @@ Plans:
2. Each page shows the file's last-modified timestamp 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 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 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: Plans:
- [x] 04-01-PLAN.md — ANSI splash screen on index.md and file metadata in status bar - [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 - [x] 04-02-PLAN.md — Virtual directory listing via [[Directory]] wiki-link
- [ ] 04-03-PLAN.md — Live filesystem watching with auto-refresh - [x] 04-03-PLAN.md — Live filesystem watching with auto-refresh
## Progress ## Progress
@@ -91,4 +91,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4
| 1. Safety Foundation | 3/3 | Complete | 2026-02-28 | | 1. Safety Foundation | 3/3 | Complete | 2026-02-28 |
| 2. Vault Core and Rendering | 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 | | 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 |
+21 -16
View File
@@ -5,23 +5,23 @@
See: .planning/PROJECT.md (updated 2026-02-28) 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 **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 ## Current Position
Phase: 4 of 4 (BBS Polish and Live Content) — IN PROGRESS Phase: 4 of 4 (BBS Polish and Live Content) — COMPLETE
Plan: 2 of 3 in current phase (04-01, 04-02 done, 04-03 next) Plan: 3 of 3 in current phase (04-01, 04-02, 04-03 all done)
Status: 04-02 complete — [[Directory]] virtual page with tree view, Tab-cycling, history back/forward Status: PROJECT COMPLETE — live reload with 300ms debounce, scroll preservation, and non-fatal watcher failure handling
Last activity: 2026-03-01 — Plan 02 complete (DirEntry, list_vault_files, navigate_to_directory, build_directory_lines) 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 ## Performance Metrics
**Velocity:** **Velocity:**
- Total plans completed: 9 - Total plans completed: 10
- Average duration: 3.1 min - Average duration: 3.5 min
- Total execution time: 0.44 hours - Total execution time: 0.58 hours
**By Phase:** **By Phase:**
@@ -30,10 +30,10 @@ Progress: [█████████░] 87%
| 01-safety-foundation | 3 | 7 min | 2.3 min | | 01-safety-foundation | 3 | 7 min | 2.3 min |
| 02-vault-core-and-rendering | 3 | 9 min | 3.0 min | | 02-vault-core-and-rendering | 3 | 9 min | 3.0 min |
| 03-navigation-and-links | 2 | 7 min | 3.5 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:** **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 - Trend: Stable
*Updated after each plan completion* *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]: 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]: 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]: 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 ### Pending Todos
None. None. Project complete.
### Blockers/Concerns ### Blockers/Concerns
- **REND blocker resolved**: renderer.rs uses no Widget trait at all — `render_markdown()` returns `Vec<Line<'static>>` 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 - **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 ## Session Continuity
Last session: 2026-03-01 Last session: 2026-03-01
Stopped at: Completed 04-02-PLAN.md Stopped at: PROJECT COMPLETE — Completed 04-03-PLAN.md (final plan)
Resume file: .planning/phases/04-bbs-polish-and-live-content/04-03-PLAN.md Resume file: N/A — project complete
@@ -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<notify::Result<notify::Event>> 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<notify::Result<notify::Event>>`, and `watched_dir: PathBuf` with a `rewatch()` helper that safely unwatches the old directory before watching the new one
- `file_watcher: Option<FileWatcher>` and `pending_reload_at: Option<Instant>` fields added to `App` struct
- `App::new()` signature updated with `file_watcher: Option<FileWatcher>` 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*