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:
@@ -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*
|
||||
Reference in New Issue
Block a user