Browser-style back / forward navigation history for the note pane (the center display), so the user can retrace their reading path. Clicking a similar-note card, a backlink, a wikilink, a tree entry, or a search result all move the center document forward; a Back control returns to the previous one, Forward re-advances. Pure frontend — no façade, host, or b2-core change (every target is already read through the existing read_note / explain_resource commands).
Why
Discovery is the whole point of B2 — you follow "similar & unlinked" and backlinks from note to note. Today every hop is one-way: there's no way to step back to where you came from without re-finding it in the tree or search. Browser-grade back/forward makes exploratory reading fluid.
Behavior spec
- What a history entry is: the document shown in the center pane, i.e. an
openNote or openResource target — { kind: "note" | "resource", path }. History tracks the pane's document regardless of how it was reached (tree click, wikilink, backlink, similar card, search result, live-preview link).
- Push on genuine navigation only. Every real navigation pushes an entry and truncates any forward entries (the browser model: navigating after going back discards the forward branch). Suppress consecutive duplicates (re-opening the already-current document is a no-op for history).
- Do not push on in-place content updates. Re-reading the same note after a save, applying a write report, or reconciling an external on-disk change all mutate
state.current/state.currentResource for the same document — these must not create history entries (ui/src/main.ts ~608, ~869, ~892, ~985, ~1005).
- Back / Forward reuse the load path without pushing. They move a cursor within the stack and load the entry there, going through the same edit-mode guard as normal navigation: flush + leave edit mode first via
closeEditor(), and abort (keep the editor + buffer) on a write conflict — identical to how openNote/openResource already behave.
- Vault switch clears history.
switchVault already resets the center document and side pane; the stack (and cursor) reset with it — old vault paths are meaningless in the new one.
- Dead targets: if a back/forward target has since been deleted or renamed, the read fails → generic actionable toast (repo error policy); drop that entry from the stack so navigation isn't wedged on a dead node.
- Session-scoped, in-memory. History starts empty on launch and is not persisted (browser-tab semantics). Persisting across restarts and per-document scroll restoration are explicit non-goals for this slice (possible follow-ups).
UI
- Two chrome buttons — ◀ Back / ▶ Forward — disabled at the ends of the stack, styled like the existing topbar icon buttons (
.btn.icon-btn, e.g. the vault switcher / settings gear). Browser-convention placement is top-left (near the B2 brand); the note-pane bar is an alternative.
- Keyboard: ⌘[ / ⌘] (macOS browser convention; also accept ⌘← / ⌘→), wired alongside the existing shortcuts in the
keydown handler (⌘, / Esc / ⌘S). Mouse back/forward buttons (auxclick) are a nice-to-have.
Implementation sketch (grounded)
There is already a single choke point: every center-pane change goes through openNote(ref) (ui/src/main.ts:135) or openResource(path) (:174) — reached from .wikilink (:1100/:653), [data-open] (:1161), and [data-open-resource] (:1147). ([data-open-system] at :1154 opens a file in the OS default app — not a center-pane nav — and is excluded.)
Suggested shape:
- Extract the "load this document into the pane" core out of
openNote/openResource (everything after the closeEditor() guard). Back/forward call the core directly.
- Keep the wrappers as the user-navigation entry points: guard → push entry (truncate-forward, dedupe) → load core.
- Hold the stack + cursor as module-locals in
main.ts (like editorView / save-chain flags), and paint the buttons' enabled/disabled state with a targeted paintNav() (the paintReindex / paintEditor pattern) rather than widening AppState.
- Optional: cap the stack length (e.g. last ~100 entries) to bound growth.
The history-stack logic (push / truncate-forward / dedupe / back / forward / clear) is pure and unit-testable in isolation if/when a ui/ test harness is added.
Scope
- In: back/forward over notes and resources; buttons + keyboard; edit-mode flush; vault-switch clear; dead-target pruning.
- Out (non-goals): cross-restart persistence; per-document scroll restoration; multiple tabs/panes; a visible history dropdown.
Ref: ui/src/main.ts (openNote / openResource choke point, the keydown handler, switchVault); planning/specs/completed/desktop-ui-mvp.md (thin-adapter / one-document-owns-the-pane); crates/b2-desktop/CLAUDE.md (why this stays a pure ui/ change).
Browser-style back / forward navigation history for the note pane (the center display), so the user can retrace their reading path. Clicking a similar-note card, a backlink, a wikilink, a tree entry, or a search result all move the center document forward; a Back control returns to the previous one, Forward re-advances. Pure frontend — no façade, host, or
b2-corechange (every target is already read through the existingread_note/explain_resourcecommands).Why
Discovery is the whole point of B2 — you follow "similar & unlinked" and backlinks from note to note. Today every hop is one-way: there's no way to step back to where you came from without re-finding it in the tree or search. Browser-grade back/forward makes exploratory reading fluid.
Behavior spec
openNoteoropenResourcetarget —{ kind: "note" | "resource", path }. History tracks the pane's document regardless of how it was reached (tree click, wikilink, backlink, similar card, search result, live-preview link).state.current/state.currentResourcefor the same document — these must not create history entries (ui/src/main.ts~608, ~869, ~892, ~985, ~1005).closeEditor(), and abort (keep the editor + buffer) on a write conflict — identical to howopenNote/openResourcealready behave.switchVaultalready resets the center document and side pane; the stack (and cursor) reset with it — old vault paths are meaningless in the new one.UI
.btn.icon-btn, e.g. the vault switcher / settings gear). Browser-convention placement is top-left (near theB2brand); the note-pane bar is an alternative.keydownhandler (⌘, / Esc / ⌘S). Mouse back/forward buttons (auxclick) are a nice-to-have.Implementation sketch (grounded)
There is already a single choke point: every center-pane change goes through
openNote(ref)(ui/src/main.ts:135) oropenResource(path)(:174) — reached from.wikilink(:1100/:653),[data-open](:1161), and[data-open-resource](:1147). ([data-open-system]at:1154opens a file in the OS default app — not a center-pane nav — and is excluded.)Suggested shape:
openNote/openResource(everything after thecloseEditor()guard). Back/forward call the core directly.main.ts(likeeditorView/ save-chain flags), and paint the buttons' enabled/disabled state with a targetedpaintNav()(thepaintReindex/paintEditorpattern) rather than wideningAppState.The history-stack logic (push / truncate-forward / dedupe / back / forward / clear) is pure and unit-testable in isolation if/when a
ui/test harness is added.Scope
Ref:
ui/src/main.ts(openNote/openResourcechoke point, thekeydownhandler,switchVault);planning/specs/completed/desktop-ui-mvp.md(thin-adapter / one-document-owns-the-pane);crates/b2-desktop/CLAUDE.md(why this stays a pureui/change).