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