- Create 03-02-SUMMARY.md documenting HistoryEntry, navigate_to/back/forward, Tab-cycling, breadcrumb - Update STATE.md: Phase 3 complete, plan 2/2 done, decisions added, session updated - Update ROADMAP.md: Phase 3 marked complete, 2/2 plans checked - Mark requirements NAV-03, NAV-04, NAV-11 complete in REQUIREMENTS.md
8.8 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-navigation-and-links | 02 | app |
|
|
|
|
|
|
Phase 03 Plan 02: Navigation, History, Tab-Cycling, and Breadcrumb Status Bar Summary
One-liner: Browser-style navigation history with Tab/Shift-Tab link cycling, Enter-to-follow, Backspace/Alt+arrows back/forward, draw-time REVERSED link highlight, and breadcrumb status bar wired into the App event loop.
What Was Built
Task 1: Navigation state, history, link cycling, and navigate_to (commit d705313)
Implemented the complete navigation system in src/app.rs and updated src/main.rs.
HistoryEntry struct (new, in src/app.rs):
struct HistoryEntry {
path: String, // vault-relative path
scroll_offset: u16, // scroll position at time of navigation away
selected_link: Option<usize>, // selected link index at time of navigation
}
Phase 3 fields added to App:
history: Vec<HistoryEntry>— browser-style navigation stackhistory_index: usize— current position in historylink_records: Vec<crate::renderer::LinkRecord>— links from current renderselected_link: Option<usize>— currently highlighted linkcurrent_path: String— vault-relative path for breadcrumb and link resolution
App::new() signature updated:
Now accepts link_records: Vec<LinkRecord> and current_path: String. Initializes history with one entry for the initial page at scroll 0, no selected link.
Navigation methods:
navigate_to(vault_relative): Saves current state to history, truncates forward history (browser fork), loads + renders new document, pushes new history entry. On Missing/ReadError, shows error screen without pushing to history.navigate_back(): Decrementshistory_index, saves current scroll/link state, re-loads and re-renders target document with restored scroll and link selection.navigate_forward(): Incrementshistory_index, same re-load pattern as navigate_back.follow_selected_link(): Readslink_records[selected_link], dispatches toresolve_wiki_link(for wiki-links) orresolve_standard_link(for standard links), then callsnavigate_to.select_next_link()/select_prev_link(): Cycle with wrap-around (% len), then callscroll_to_selected_link().scroll_to_selected_link(): If selected link is outside viewport, centers it:link_line.saturating_sub(half), clamped tomax_scroll().
Key bindings added (in handle_key, after 'q', before scroll keys):
Tab → select_next_link()
BackTab → select_prev_link()
Enter → follow_selected_link()
Backspace → navigate_back()
Alt+Left → navigate_back()
Alt+Right → navigate_forward()
handle_resize() updated: Now captures link_records from re-render, validates selected_link still in range, uses Some(&self.config.vault_path) for render-time broken wiki-link detection.
main.rs updated:
render_markdowncall now passesSome(&app_config.vault_path)for broken wiki-link detection at startup- Destructures
(lines, link_records)from render result App::new()call includesinitial_link_recordsand"index.md".to_string()
Task 2: Draw-time link selection and breadcrumb status bar (included in d705313)
Both draw-time features were implemented together with Task 1 in the single coherent rewrite.
Draw-time REVERSED modifier (in draw() → DocumentState::Loaded branch):
- Calls
lines.clone()to get a mutable copy for the current frame only (stored lines unchanged) - Walks
line.spans.iter_mut(), tracking cumulativecol(chars-count) - Applies
Modifier::REVERSEDto any span whose column range overlaps[record.col_offset, record.col_offset + record.span_len) - Uses both exact-range and overlap checks to handle multi-span link brackets
build_breadcrumb(vault_relative) helper (private fn, bottom of app.rs):
- Iterates
Path::components(), strips.mdsuffix from each component - Joins with
>separator - Examples:
"index.md"→"index","guides/getting-started.md"→"guides > getting-started"
draw_status_bar() rewritten:
- Left side:
{breadcrumb}frombuild_breadcrumb(&self.current_path) - Quit prompt mode: breadcrumb left + yellow bold REVERSED warning right (existing behavior preserved)
- Normal mode right side (space-separated):
< Backifhistory_index > 0Link N/Mifselected_linkis SomeForward >ifhistory_index < history.len() - 1- Keyboard hints:
Tab:Links Enter:Go Bksp:Back q:Quit(or Ctrl+C×2 for login shell)
#[allow(dead_code)]removed fromconfigfield (now actively used)
Verification Results
cargo buildsucceeds — zero errors, 2 pre-existing warnings (init_logging and is_within_vault — unrelated to this plan)- All required functions exist in src/app.rs: HistoryEntry, navigate_to, navigate_back, navigate_forward, follow_selected_link, select_next_link, select_prev_link, scroll_to_selected_link, build_breadcrumb
- main.rs passes link_records and current_path to App::new()
- Key bindings in correct match order: Ctrl+C > q > Tab > BackTab > Enter > Backspace > Alt+Left > Alt+Right > j/k/Down/Up > PgDn/PgUp > _ catch-all
Deviations from Plan
Implementation Notes
1. Tasks 1 and 2 implemented together:
- Found during: Implementation
- Note: The draw-time REVERSED modifier and breadcrumb status bar are tightly coupled to the same
Appstruct fields (link_records, selected_link, history, history_index, current_path) added in Task 1. Implementing them in a single coherent rewrite produced cleaner code than two separate partial passes. - Impact: Single commit covers both tasks. All required functionality is present.
2. Span overlap detection extended:
- Found during: Task 2 implementation
- Issue: The plan's span-walking code only checked
col >= col_offset && col < col_offset + span_len, which misses spans that start beforecol_offsetbut extend into the link range. - Fix: Added a second condition
col < col_offset + span_len && col + span_chars > col_offsetto catch overlapping spans. - Files modified: src/app.rs (draw method)
Self-Check: PASSED
Files verified:
src/app.rs: containsstruct HistoryEntry,navigate_to,navigate_back,navigate_forward,follow_selected_link,select_next_link,select_prev_link,scroll_to_selected_link,build_breadcrumb, REVERSED modifier in draw(), breadcrumb in draw_status_bar()src/main.rs: passesSome(&app_config.vault_path)to render_markdown, destructureslink_records, callsApp::new()with 6 arguments
Commits verified:
d705313: feat(03-02): add navigation history, link cycling, and navigate_to to App