feat: vim-style jump list and alternate buffer navigation #67
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
track:api
track:auto
track:core
track:deploy
track:infra
track:ui
v0.1.0
v0.1.1
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
barrettruth/delta#67
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Delta has solid vim-style motions within views (
j/k,gg/G,h/lin kanban/calendar) and view-switching hotkeys (Q/K/C), but there is no way to retrace your steps. Once you navigate away from what you were looking at, you have to manually reconstruct your path. Browser back/forward is unreliable in an SPA — it conflates app navigation with page-level history and breaks when views update via state rather than URL changes (e.g. opening a task detail dialog, selecting a calendar date, filtering by category).Vim solves this with two complementary mechanisms: the jump list (
<C-o>/<C-i>) and the alternate buffer (<C-^>). Both map cleanly onto delta's navigation model.Proposed features
1. Alternate buffer —
<C-6>(or<C-^>)In vim,
<C-^>switches to the "alternate file" — the last buffer you edited before the current one. It is always exactly one location; it is not a stack. Every time you navigate to a new buffer, the previous buffer becomes the alternate.In delta, this translates to: the last view you were in before the current one. The "view" includes enough state to restore context — not just the route, but any meaningful sub-state.
Examples:
Cto go to Calendar. Pressing<C-6>returns to Queue with the "Work" filter active.Qto go to Queue. Pressing<C-6>returns to Kanban (and optionally reopens task #42's detail).Enteron a day to view it in Queue.<C-6>returns to Calendar on April 2026 with that day selected.The alternate buffer is always a single pointer — it does not accumulate. Pressing
<C-6>twice returns you to where you started (it swaps current and alternate).2. Jump list —
<C-o>/<C-i>In vim, the jump list is an ordered stack of locations recorded whenever you make a "jump" — opening a file, searching, using
gg/G/marks/etc.<C-o>moves backward through the stack,<C-i>moves forward. The list has a cursor; going back and then navigating somewhere new truncates the forward history (same as browser history semantics).Key vim behaviors to replicate:
<C-o>and then making a new jump truncates everything forward of the current position.What constitutes a "jump" in delta
Not every state change is a jump. In vim, cursor movements within a file (
j/k/w/b) are not jumps, butgg,G,/search, and:edit fileare. The principle: jumps cross a meaningful boundary; motions move within one.Q/K/C(view switch):edit— switching buffersEnteron a task (open detail)gd/:edit— drilling into an entityEnteron a calendar day (view in queue)1-9(category filter):cdEscape(close task detail)j/k(move cursor)h/l(calendar day, kanban col)[m/]m/[w/]w(calendar nav)w/m(calendar view mode toggle)gg/G(jump to top/bottom of list)3. Future: marks —
m{a-z}/'{a-z}Lower priority, but worth noting for completeness. In vim,
m{a-z}sets a named mark at the current cursor position, and'{a-z}jumps to it. In delta, this would let you bookmark specific views or tasks:mawhile viewing task #42 → marka= task #42's detail'afrom anywhere → reopen task #42's detailmbin Queue filtered to "School" → markb= Queue + School filter'bfrom Calendar → jump to Queue with School filter''(double apostrophe) jumps to the position before the last jump — a simpler version of<C-o>that only goes back one step. This is effectively the same as the alternate buffer for most cases, but in vim it specifically means "position before last jump" rather than "last buffer."Marks are a stretch goal. The jump list and alternate buffer cover 90% of the navigation pain.
Technical notes
Navigation location type
A "location" needs to capture enough state to restore a view:
State management
The jump list and alternate buffer should be app-level state, separate from browser history. Reasons:
router.back()uses the browser history stack, which includes external navigations, page refreshes, and other noise. It is not a clean representation of in-app navigation.router.push()adds to browser history. The jump list must be orthogonal — pressing<C-o>should not also trigger a browser back.Implementation options:
React context +
useRef— simplest. ANavigationProviderwrapping the app holds the jump list array and alternate buffer pointer in refs (not state, to avoid re-renders on every push). ExposepushJump(),jumpBack(),jumpForward(),getAlternate(), andgoAlternate()via context. Navigation functions callrouter.push()internally when the path changes.Zustand store — if the app adopts Zustand later, the jump list fits naturally as a store slice. Same API surface.
sessionStorage— persist the jump list across page refreshes (but not across tabs). Useful if the user refreshes and wants to retain their navigation trail. Can be combined with option 1 — write-through tosessionStorageon every push.Option 1 is the right starting point.
sessionStoragepersistence can be layered on later.Integration with
global-keyboard.tsxThe
<C-o>,<C-i>, and<C-6>handlers belong inGlobalKeyboardsince they are view-agnostic. The currentVIEW_KEYShandler that callsrouter.push()would need to also callpushJump()before navigating. Same for category filter keys (1-9).View-specific jump triggers (e.g.
Enterto open task detail,Enteron calendar day) would callpushJump()from their respective components via the context.Interaction with
isInputFocused()<C-o>and<C-i>use theCtrlmodifier, so they are unlikely to conflict with text input. But<C-6>could conflict with some input fields. The existingisInputFocused()guard (currently duplicated in 4 files — tracked separately) should gate these bindings the same way it gates other shortcuts.Keymap help update
Add a "Navigation" section to
KeymapHelpwith<C-o>,<C-i>, and<C-6>.References
:help jumplist— vim's jump list documentation:help alternate-file— vim's alternate buffer (<C-^>) documentation:help mark-motions— vim's mark systemDO NOT IMPLEMENT MARKS.
Implemented in recent commits.
NavigationProviderinsrc/contexts/navigation.tsxprovidespushJump,jumpBack(Ctrl+o),jumpForward(Ctrl+i),goAlternate(Ctrl+6). Jump list has max 100 entries, duplicate collapsing, forward truncation on new jump. Integrated across queue, kanban, calendar views, andglobal-keyboard.tsx. Keymap help section added. Marks (m{a-z}) were scoped as a stretch goal and are not included.