docs(quick-3): complete remote markdown page linking plan

This commit is contained in:
2026-03-01 13:16:32 +01:00
parent c8d4754340
commit 37b8352919
2 changed files with 138 additions and 4 deletions
+10 -4
View File
@@ -12,7 +12,7 @@ See: .planning/PROJECT.md (updated 2026-02-28)
Phase: 4 of 4 (BBS Polish and Live Content) — COMPLETE 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) 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 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% 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]: 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]: 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-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 ### Pending Todos
None. Project complete. Quick task 1 executed. None. Project complete. Quick tasks 1, 2, and 3 executed.
### Blockers/Concerns ### 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/) | | 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/) | | 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 ## Session Continuity
Last session: 2026-03-01 Last session: 2026-03-01
Stopped at: Completed quick-1 (arrow key navigation + directory descriptions) Stopped at: Completed quick-3 (remote markdown page linking with domain whitelist)
Resume file: N/A — project complete, quick task 1 done Resume file: N/A — project complete, quick tasks 1-3 done
@@ -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<String> 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<String>` 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<String>` 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