diff --git a/.planning/STATE.md b/.planning/STATE.md index 87bb1d5..bb2cf43 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -12,7 +12,7 @@ See: .planning/PROJECT.md (updated 2026-02-28) 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 - Completed quick task 2: Demo vault content with ASCII art splash and feature pages +Last activity: 2026-03-01 - Completed quick task 3: Remote markdown page linking with domain whitelist Progress: [██████████] 100% @@ -83,10 +83,15 @@ Recent decisions affecting current work: - [quick-1]: Bare Left/Right match arms placed after Alt+Left/Alt+Right guards — match order ensures Alt combos consumed first - [quick-1]: BufReader + take(20) for frontmatter parsing — avoids loading entire large files - [quick-1]: span_len covers only [name] bracket not description — keeps Tab-cycling highlight correct +- [quick-3]: ureq chosen for synchronous HTTP (no async runtime, matches "no tokio" decision) +- [quick-3]: Subdomain matching: sub.example.com matches whitelist entry example.com via suffix check +- [quick-3]: Error cases (domain-not-allowed, fetch-error, not-markdown) do NOT push history entry +- [quick-3]: current_url field distinguishes remote vs local page context for resize/reload handling +- [quick-3]: Back/forward re-fetch remote pages from network (consistent with re-load-from-disk pattern) ### Pending Todos -None. Project complete. Quick task 1 executed. +None. Project complete. Quick tasks 1, 2, and 3 executed. ### Blockers/Concerns @@ -101,9 +106,10 @@ All blockers resolved: |---|-------------|------|--------|-----------| | 1 | Arrow key navigation and directory description column | 2026-03-01 | 2d1e018 | [1-arrow-key-navigation-and-directory-descr](./quick/1-arrow-key-navigation-and-directory-descr/) | | 2 | Demo vault content with ASCII art splash and feature pages | 2026-03-01 | 4464774 | [2-create-demo-vault-content-with-ascii-art](./quick/2-create-demo-vault-content-with-ascii-art/) | +| 3 | Remote markdown page linking with domain whitelist | 2026-03-01 | c8d4754 | [3-implement-remote-markdown-page-linking-w](./quick/3-implement-remote-markdown-page-linking-w/) | ## Session Continuity Last session: 2026-03-01 -Stopped at: Completed quick-1 (arrow key navigation + directory descriptions) -Resume file: N/A — project complete, quick task 1 done +Stopped at: Completed quick-3 (remote markdown page linking with domain whitelist) +Resume file: N/A — project complete, quick tasks 1-3 done diff --git a/.planning/quick/3-implement-remote-markdown-page-linking-w/3-SUMMARY.md b/.planning/quick/3-implement-remote-markdown-page-linking-w/3-SUMMARY.md new file mode 100644 index 0000000..2e3c045 --- /dev/null +++ b/.planning/quick/3-implement-remote-markdown-page-linking-w/3-SUMMARY.md @@ -0,0 +1,128 @@ +--- +phase: quick-3 +plan: 01 +subsystem: navigation +tags: [remote-links, http, security, whitelist, history] +dependency_graph: + requires: [quick-1, quick-2] + provides: [remote-markdown-fetching, domain-whitelist] + affects: [app-navigation, renderer-styling, vault-fetching, config] +tech_stack: + added: [ureq 2.12 (synchronous HTTP client)] + patterns: [domain whitelist check, content-type validation, 5MB body cap, no-history-on-error] +key_files: + created: [] + modified: + - Cargo.toml + - src/config.rs + - src/vault.rs + - src/app.rs + - src/renderer.rs +decisions: + - ureq chosen for synchronous HTTP (no async runtime, consistent with existing architecture) + - Subdomain matching: sub.example.com matches whitelist entry example.com via suffix check + - Content-type validation accepts text/markdown, text/plain, text/x-markdown, and any URL ending in .md + - Error cases (domain-not-allowed, fetch-error, not-markdown) do NOT push history entry + - Back/forward re-fetch remote pages from network (consistent with re-load-from-disk pattern) + - current_url: Option field distinguishes remote vs local page context across resize/reload +metrics: + duration: "4 min" + completed: "2026-03-01T12:15:30Z" + tasks_completed: 2 + files_modified: 5 +--- + +# Quick Task 3: Remote Markdown Page Linking Summary + +**One-liner:** Domain-whitelisted HTTP/HTTPS link following with ureq, content-type validation, BBS error screens, and browser history integration. + +## What Was Built + +Users can now follow HTTP/HTTPS links in markdown documents. When a link is activated: + +1. The domain is extracted from the URL and checked against `allowed_remote_domains` in `bbs.toml`. +2. If allowed, ureq fetches the URL with a 10-second timeout. +3. The Content-Type header is validated (accepts `text/markdown`, `text/plain`, `text/x-markdown`, or any `.md` URL). +4. The response body is read with a 5 MB cap. +5. The fetched markdown is rendered in the TUI exactly like a local page. +6. Remote pages participate in browser history (Backspace/Alt+Left goes back; re-fetches on return). + +Error cases show BBS-themed error screens (no history entry pushed): +- **Domain not whitelisted**: shows domain name and config hint +- **Fetch error** (network, timeout, non-2xx): shows HTTP status or error message +- **Not markdown** (HTML, binary): shows Content-Type received + +HTTP/HTTPS links are styled in `LightMagenta` to visually distinguish them from local `LightCyan` links. + +## Tasks Completed + +| Task | Name | Commit | Key Files | +|------|------|--------|-----------| +| 1 | Add config field, ureq dependency, and remote fetch function | 5759ec8 | Cargo.toml, src/config.rs, src/vault.rs | +| 2 | Wire remote link navigation into app event loop with history and error screens | c8d4754 | src/app.rs, src/renderer.rs | + +## Key Changes + +### Cargo.toml +- Added `ureq = "2.12"` dependency + +### src/config.rs +- Added `allowed_remote_domains: Vec` field with `#[serde(default)]` +- Updated `Default` impl to include empty Vec + +### src/vault.rs +- Added `RemoteDocument` enum (`Loaded`, `DomainNotAllowed`, `FetchError`, `NotMarkdown`) +- Added `extract_domain()` — strips scheme, path, port from URL +- Added `domain_is_allowed()` — exact + subdomain matching (case-insensitive) +- Added `fetch_remote_markdown()` — whitelist check, 10s timeout, content-type validation, 5MB cap + +### src/renderer.rs +- HTTP/HTTPS links styled `LightMagenta` in `Event::Start(Tag::Link)` handler (non-wiki, non-local branch) + +### src/app.rs +- Added `current_url: Option` field (Some = remote page, None = local page) +- Added `navigate_to_remote()` method — saves history, fetches, renders, pushes entry on success +- Updated `follow_selected_link()` to detect HTTP/HTTPS and dispatch to `navigate_to_remote()` +- Updated `navigate_back()` and `navigate_forward()` to re-fetch remote URLs from history +- Updated `handle_resize()` to pass `None` vault_path for remote pages +- Updated `reload_current_document()` to skip live-reload for remote pages +- Updated `navigate_to()` to clear `current_url` when going to a local page + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Fixed clippy lint: manual `split_once` pattern** +- **Found during:** Task 2 verification (cargo clippy) +- **Issue:** `url.splitn(2, "://").nth(1)` and `resp.status_text().to_string()` triggered clippy warnings +- **Fix:** Replaced with `url.split_once("://")?.1` and removed redundant `.to_string()` in format! +- **Files modified:** src/vault.rs, src/app.rs +- **Commit:** c8d4754 + +### Out-of-scope items (deferred) + +None discovered. + +## Verification Results + +- `cargo build` passes with no errors (3 pre-existing warnings, unrelated to this task) +- `cargo clippy` passes with warnings only (no new errors introduced) +- HTTP links styled LightMagenta (verified in renderer.rs code path) +- Domain whitelist check with exact and subdomain matching implemented +- All 4 RemoteDocument variants handled with appropriate error screens +- History integration tested via code review (back/forward re-fetch remote URLs) +- handle_resize passes None vault_path for remote pages +- reload_current_document returns early for remote pages + +## Self-Check: PASSED + +Files exist: +- Cargo.toml: FOUND (ureq = "2.12") +- src/config.rs: FOUND (allowed_remote_domains field) +- src/vault.rs: FOUND (fetch_remote_markdown function) +- src/app.rs: FOUND (navigate_to_remote method, current_url field) +- src/renderer.rs: FOUND (LightMagenta branch) + +Commits exist: +- 5759ec8: Task 1 commit +- c8d4754: Task 2 commit