docs(quick-3): complete remote markdown page linking plan
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user