feat(ui): browser-style back/forward history for the center pane (#52) - #53
Conversation
Pure frontend, per the issue: no facade, host, or b2-core change. The
history stack tracks the pane's document — notes and resources alike,
regardless of how each was reached — as {kind, path} entries with a
cursor, held as module-locals in main.ts (the editorView pattern).
- openNote/openResource split into a guard->push wrapper and a load
core (loadNote/loadResource); back/forward call the core directly.
The history commit runs at read-success with the canonical path in
hand, so a wikilink followed by title dedupes against a tree click
on the same note, a dead target never enters the stack, and rapid
navigations can't interleave stack updates out of order.
- push truncates the forward branch (browser model), suppresses
consecutive duplicates, and caps at 100 entries; in-place content
updates (save re-read, write report, external reconcile) never push
because they don't go through the wrappers.
- back/forward go through the same closeEditor() guard as any
navigation: flush + leave edit mode, abort on a write conflict. A
dead target toasts the generic read error and is pruned (by
identity) so navigation isn't wedged on it.
- vault switch clears the stack; also clears a lingering resource card
(switchVault reset current but not currentResource).
- chrome: Back/Forward icon-btn pair by the brand, disabled at the
stack's ends via a targeted paintNav (the paintReindex pattern).
- keyboard: Mod+[ / Mod+] (plus Mod+arrow aliases); suppressed while
editing (CodeMirror owns Mod-[/] for indent) and arrows yield to
caret movement in text fields. Mouse back/forward via auxclick.
Verified end-to-end headless (built bundle + mocked Tauri IPC): boot
states, push/truncate/dedupe, resource entries, keyboard chords and
their text-entry carve-outs, dead-target pruning, edit-mode flush and
conflict-abort — 34/34 checks green; tsc + vite build clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUB5nCNA67G9tMGNozezN3
📝 WalkthroughWalkthroughThe UI adds shared note/resource loading, bounded center-pane navigation history, back/forward controls, keyboard and mouse navigation inputs, edit-mode guards, failed-target pruning, and vault-switch history clearing. ChangesCenter-pane navigation
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant User
participant NavigationControls
participant navGo
participant loadNote
participant API
User->>NavigationControls: click, shortcut, or mouse navigation
NavigationControls->>navGo: navigate by delta
navGo->>loadNote: load note history entry
loadNote->>API: api.readNote(ref)
API-->>loadNote: note data or error
loadNote-->>navGo: success or failure
navGo-->>NavigationControls: update history state
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ui/src/main.ts`:
- Around line 1505-1512: Update the auxclick handler to apply the same
state.settingsOpen || state.linkTarget guard used by the keyboard navigation
handler before calling navGo. Keep preventing the back/forward event and
preserve existing navigation behavior when neither modal state is active.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: dfabc648-109b-49b5-911f-3c0db9fd3f85
📒 Files selected for processing (2)
ui/src/main.tsui/style.css
| // Mouse back/forward buttons (W3C numbering: 3 back, 4 forward) walk the history | ||
| // too. `auxclick` fires only for non-primary buttons, so this never doubles the | ||
| // click delegation above. | ||
| document.addEventListener("auxclick", (e) => { | ||
| if (e.button !== 3 && e.button !== 4) return; | ||
| e.preventDefault(); | ||
| void navGo(e.button === 3 ? -1 : 1); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Mouse back/forward navigation skips the modal guards the keyboard path enforces.
The keyboard handler explicitly checks state.settingsOpen || state.linkTarget before calling navGo (Line 1495), since keyboard events bypass modal-backdrop hit-testing. auxclick has the exact same issue — it doesn't go through DOM click delegation either, so a physical mouse back/forward button press will still call navGo and swap the underlying pane while a settings or link modal is open, leaving the user looking at stale modal content over a changed document once it's closed.
🖱️ Proposed fix
document.addEventListener("auxclick", (e) => {
if (e.button !== 3 && e.button !== 4) return;
+ if (state.settingsOpen || state.linkTarget) return;
e.preventDefault();
void navGo(e.button === 3 ? -1 : 1);
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Mouse back/forward buttons (W3C numbering: 3 back, 4 forward) walk the history | |
| // too. `auxclick` fires only for non-primary buttons, so this never doubles the | |
| // click delegation above. | |
| document.addEventListener("auxclick", (e) => { | |
| if (e.button !== 3 && e.button !== 4) return; | |
| e.preventDefault(); | |
| void navGo(e.button === 3 ? -1 : 1); | |
| }); | |
| // Mouse back/forward buttons (W3C numbering: 3 back, 4 forward) walk the history | |
| // too. `auxclick` fires only for non-primary buttons, so this never doubles the | |
| // click delegation above. | |
| document.addEventListener("auxclick", (e) => { | |
| if (e.button !== 3 && e.button !== 4) return; | |
| if (state.settingsOpen || state.linkTarget) return; | |
| e.preventDefault(); | |
| void navGo(e.button === 3 ? -1 : 1); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ui/src/main.ts` around lines 1505 - 1512, Update the auxclick handler to
apply the same state.settingsOpen || state.linkTarget guard used by the keyboard
navigation handler before calling navGo. Keep preventing the back/forward event
and preserve existing navigation behavior when neither modal state is active.
Pure frontend, per the issue: no facade, host, or b2-core change. The
history stack tracks the pane's document — notes and resources alike,
regardless of how each was reached — as {kind, path} entries with a
cursor, held as module-locals in main.ts (the editorView pattern).
core (loadNote/loadResource); back/forward call the core directly.
The history commit runs at read-success with the canonical path in
hand, so a wikilink followed by title dedupes against a tree click
on the same note, a dead target never enters the stack, and rapid
navigations can't interleave stack updates out of order.
consecutive duplicates, and caps at 100 entries; in-place content
updates (save re-read, write report, external reconcile) never push
because they don't go through the wrappers.
navigation: flush + leave edit mode, abort on a write conflict. A
dead target toasts the generic read error and is pruned (by
identity) so navigation isn't wedged on it.
(switchVault reset current but not currentResource).
stack's ends via a targeted paintNav (the paintReindex pattern).
editing (CodeMirror owns Mod-[/] for indent) and arrows yield to
caret movement in text fields. Mouse back/forward via auxclick.
Verified end-to-end headless (built bundle + mocked Tauri IPC): boot
states, push/truncate/dedupe, resource entries, keyboard chords and
their text-entry carve-outs, dead-target pruning, edit-mode flush and
conflict-abort — 34/34 checks green; tsc + vite build clean.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01EUB5nCNA67G9tMGNozezN3
Summary by CodeRabbit
New Features
Bug Fixes