-
-
Notifications
You must be signed in to change notification settings - Fork 0
TUI Navigation
Coco's interactive TUI is keyboard-driven and chord-based. Whether you launch it as coco ui or coco log -i, every git surface — history, working tree, diffs, commit compose, branches, tags, stash — is reachable from any other surface through a unified navigation model.
This page is the source of truth for that model. The companion pages Coco UI and Interactive Log TUI cover surface-specific actions and command-line flags; everything navigation-related lives here.
-
Views are top-level destinations. There are fifteen:
history,status,diff,compose,branches,tags,stash,worktrees,pull-request,pull-request-triage,issues,conflicts,reflog,bisect, andchangelog. -
Chords jump you between views. They start with
g, followed by a single key (g h,g s,g d,g c,g b,g t,g z,g w,g p,g P,g i,g x,g r,g B). -
The navigation stack remembers where you came from. Pressing
<orEscpops back. Goinghistory → diff → compose → backreturns to diff, then back again returns to history. - Selection state survives navigation. The selected commit, selected branch, selected stash, and current compose draft are all preserved when you jump away and come back.
-
The chrome adapts. A
›-separated breadcrumb in the header reflects the navigation stack; the footer's right-side global slot stays the same across views; help and palette read the active view to show the right scope.
| View | Chord | What it shows |
|---|---|---|
| History | g h |
Commit graph/list with refs and metadata. gh also clears the navigation stack. |
| Status | g s |
Working-tree files (staged/unstaged/untracked) with stage/unstage/revert. |
| Diff | g d |
Hunks of the currently-selected commit (from history) or worktree file (from status). |
| Compose | g c |
Full-screen commit draft editor with summary/body cursors, AI draft, hook feedback. |
| Branches | g b |
Local branches with divergence info; checkout/delete/create-PR via workflow keys. |
| Tags | g t |
Tag list. |
| Stash | g z |
Stash list; apply/drop via workflow keys. (g s is reserved for status, hence g z.)
|
| Worktrees | g w |
Linked worktrees with current/dirty markers; remove via W. |
| Pull request | g p |
Dedicated PR action panel for the current branch (header, checks, reviews, body) with m merge / x close / a approve / R request changes / c comment / O open in browser. |
| PR triage | g P |
Multi-PR triage list with filter cycling and per-row actions. Capital P disambiguates from g p (single, current-branch panel). See Issue & PR Triage. |
| Issues | g i |
Issue triage list with filter cycling and per-row actions: comment, label, assign, close, reopen. See Issue & PR Triage. |
| Conflicts | g x |
Conflict resolution helper view, available during merge / rebase / cherry-pick / revert. Per-row keys s stage / u theirs / U ours / o edit / C continue. |
| Reflog | g r |
Chronological recovery log — every HEAD movement (commit / checkout / merge / reset / etc.) with relative time, action, hash, and message. Enter drills into the diff for the entry's hash. |
| Bisect | g B |
Bisect workflow surface (#784). Capital B disambiguates from g b (branches). Shows the current candidate, the parsed decision log, and the four action keys: g good / b bad / s skip / x reset. |
| Changelog | L |
Full-screen AI-generated changelog for the current branch (#914). Reached via L from history or branches rather than a g-prefixed chord. Per-branch cache; r regenerates, y yanks, E opens in $EDITOR, c kicks off create-PR seeded with the content. |
g g is a separate chord that jumps to the first commit in the active history list — it pre-dates the view chords. g H (uppercase) on a diff view applies the cursored hunk to the index (companion to bare H which applies to the worktree). g T (uppercase) on the history view opens a new-tag prompt rooted at the cursored commit.
A few uppercase keys kick off workflows that span multiple views. These aren't chords; press them once and the workflow takes over.
| Key | Where | Action |
|---|---|---|
C |
history / branches | Create a pull request for the current branch. Seeds the title + body from a generated changelog and opens a multi-line prompt for review. |
L |
history / branches |
Generate a changelog for the current branch in a full-screen surface. Per-branch cache; r regenerates, y yanks to clipboard, E opens in $EDITOR, c kicks off create-PR seeded with this content. |
S |
compose |
Split the staged set into multiple commits. Opens an overlay with the LLM-generated plan. y apply / r regenerate / < cancel. |
E |
compose / status / diff |
Open the current commit draft in $EDITOR. Round-trips through a temp file; on save the content is re-split into summary + body. Companion to lowercase e (inline edit). |
I |
compose / status / diff | Generate an AI commit draft from the staged set. |
B |
history (on a commit) |
Create a branch rooted at the cursored commit (git switch -c). |
+ |
branches view / sidebar branches tab |
Create a branch from HEAD (git switch -c). |
| Key | Action |
|---|---|
< |
Pop the navigation stack |
Esc |
Same as < when in a normal view; also closes filter/help/palette/confirmation modes |
The stack always has at least one frame (the root view), so pressing < from the root is a no-op rather than an exit. Use q or Ctrl+C to quit.
Some keys perform navigation based on what's selected:
| Trigger | Effect |
|---|---|
Enter on a commit (history view) |
Push diff scoped to that commit. The diff view inherits the selection so < returns to the same row. |
Enter on a file (status view) |
Push diff scoped to that file. < returns to status. |
Enter on a reflog row |
Push diff scoped to that entry's hash (#781). |
e from status or diff |
Push compose and start editing. From inside compose, e toggles edit mode without re-pushing. |
c from status or diff |
Push compose and run createManualCommit — the result (success or hook output) lands in compose so you can see it. |
Some flows span multiple views — you mark a state on one view and then act on it from a different one. The footer hints adapt to the flow state so the override is always discoverable.
Diff any two refs (branches, tags, or commits) without leaving the workstation:
| Step | Trigger | Effect |
|---|---|---|
| 1 |
m on a row in branches / tags / history |
Mark the cursored ref as the compare base. Status banner sticks: "Compare base: <label> — press enter on another ref to diff." |
| 2 |
m on the same ref again |
Toggle the base off (no Enter needed). |
| 3 | Navigate to a second ref on any of branches / tags / history | (no key required — just move) |
| 4 |
Enter on that second ref |
Push the diff view in compare mode, showing git diff <base>..<head>. |
| 5 |
< / Esc
|
Pop the diff. The compare base clears automatically when the diff is popped. |
The compare diff is read-only — no per-file cherry-pick or hunk-apply across arbitrary refs (those don't have a sensible mutate-from-here flow). Just scroll with j/k, toggle split mode with d, and back out with <.
While a base is set, the footer adapts on every compare-flow target view:
Branches: ↑/↓ branches · enter compare · m clear · esc back
Tags: ↑/↓ tags · enter compare · m clear · esc back
History: ↑/↓ move · enter compare · m clear · esc back
Outside the compare flow, m is unbound on those views except the dedicated PR view (where it triggers merge).
Bisect support pairs the g B view with a top-bar BISECTING badge so you can't lose the workflow. The TUI picks up an active bisect on its next refresh — start it from your shell with git bisect start <bad-ref> <good-ref> and re-open coco ui.
| Trigger | Effect |
|---|---|
g B |
Push the bisect view. Shows current candidate (HEAD), parsed decision log, and the action keys. The view is reachable even when bisect is inactive — the empty-state hint tells you how to start one. |
g (on the bisect view) |
Mark the current candidate as good (bug not yet present). Advances to the next candidate. |
b (on the bisect view) |
Mark the current candidate as bad (bug present). Advances to the next candidate. |
s (on the bisect view) |
Skip the current candidate (e.g. it doesn't build). Advances to the next candidate. |
x (on the bisect view) |
Reset the bisect — discards in-progress state. Routed through the y-confirm path. |
Inside the bisect view, g and b bypass the chord prefix — pressing g marks good rather than entering chord mode. The path back out is < / Esc (never a chord). Outside the view, g/b/s/x keep their existing semantics.
The status line surfaces git's own "Bisecting: N revisions left to test after this (roughly K steps)" line after each decision so you always know how far you have to go.
The palette is an interactive launcher, not a static reference. Press : to open it; type to filter, press Enter to run.
| Key | Action |
|---|---|
| Printable keys | Append to the fuzzy filter |
Backspace / Delete
|
Remove the last filter character |
Ctrl+U |
Clear the filter |
↑ / ↓, Ctrl+P / Ctrl+N
|
Move the selection cursor (clamped to filtered count) |
Enter |
Run the selected command (records as recent, then closes) |
Esc |
Close without running |
The palette enumerates every keybinding plus every workflow action (commit, delete-branch, ai-commit-summary, etc.). Recently-used items float to the top when the filter is empty; once a query is set, relevance ranking takes over and recent ordering is ignored.
Behind the scenes, palette execution maps each command id to the same events the keystroke would dispatch — palette and keymap stay in sync.
/ opens filter mode in the active view:
- History: ranked fuzzy matching across hash, date, author, message, and refs.
- Branches: substring match on branch names and upstreams.
- Tags: substring match on tag names and subjects.
- Stash: substring match on stash refs and messages.
While filtering, the active view's header shows N/M totals plus the filter text (5/12 local | filter: feat). Ctrl+U clears, Esc or Enter exits filter mode.
In other views (diff, compose, status), / opens filter mode but the surface doesn't currently apply it — typing closes the filter without effect. That's a polish opportunity for a follow-up; track it via the wiki/issue tracker.
Help is grouped into two sections so the right scope is always obvious:
-
Global — bindings that work from any view or focus (
?,:,q,r, focus nav, workflow actions, the navigation chords). -
This view (
<active>) — bindings filtered to what makes sense in the current view + focus, with the active view named in the section title so you always know which scope applies.
Esc or ? closes the overlay.
The footer has two slots that don't overlap:
-
Contextual (left, dimmed) — what changes by mode/view/focus, like
↑/↓ files · enter diff · space stage · z revert · e/c composein status. -
Global (right edge, dimmed) — persistent affordances anchored to the right:
g jump · < back · ? help · : cmds · q quit.
In special modes (filter / help open / palette open), the global slot trims down (q quit always present) since : and ? mean close in those contexts.
A few keys are context-routed across surfaces:
-
[/]— diff view: jump previous/next hunk or file. Sidebar focused: cycle sidebar tabs. Inspector focused (on short terminals where the inspector is tabbed): cycle inspector tabs. Each context owns its meaning of the key; the footer hints surface which one applies. -
←/→— sidebar focused: switch between Status / Branches / Tags / Stashes / Worktrees tabs. Vertical axis (↑/↓) navigates items within the active tab.
The header shows where you are in the navigation stack as a ›-separated trail:
| Stack | Breadcrumb |
|---|---|
[history] |
(empty — no breadcrumb shown at root) |
[history, diff] |
history › diff |
[status, diff] |
status › diff |
[history, status, diff] |
history › status › diff |
The breadcrumb is purely informational — there's no clickable navigation through it (the TUI is keyboard-only). Use < / Esc to walk it back.
- Four theme presets:
default,monochrome,catppuccin,gruvbox. Set via--theme <preset>orlogTui.theme.presetin.coco.config.json. -
NO_COLOR=1is honored end-to-end. Borders fall back to the terminal default and color emphasis is dropped without changing layout. - The chrome uses a small set of unicode glyphs (
›,↑/↓,·); layout is ASCII-only so a missing glyph never breaks columns. - Empty / loading copy is unified across views and points users at the next sensible action — no blank screens.
- The TUI is keyboard-only by design. Every action is reachable from the keymap or
:palette. Mouse input is not consumed.
The shell architecture landed in v0.34.0 (TUI shell epic, #747). The releases since have layered on a navigable, action-rich workstation. Highlights worth knowing if you came from an earlier version:
Wherever a list of items lives inside a named group, the group's title is now a first-class cursor target with its own canonical action. The same mental model carries across all four surfaces it applies to:
-
Sidebar.
↑at items index 0 promotes the cursor onto the active tab's header (Branches / Tags / Stashes / Worktrees).←/→scans neighboring tab headers;Enteron the header drills into the dedicated view. -
Status view. Files render under
▾ Staged (n)/▾ Unstaged (n)/▾ Untracked (n).←/→jumps between groups;↑at a group's first file promotes to the header;Enteron a header fires the batch action (Stage all unstaged,Unstage all staged,Stage all untrackedwith y-confirm). -
Inspector Actions.
[/]toggles to the Actions tab;↑/↓moves the cursor;Enterfires the cursored action (cherry-pick, revert, reset, yank, open in browser). -
Stash diffs.
diff --gitrows render as compact▾ <path>headers; the file the cursor is scrolled inside gets selection styling so the active context is always visible.
Header focus persists across ← / → switches in the sidebar so the user can scan tab → tab → drill in one fluid motion. All four surfaces use the same selection styling for their headers, so the visual cue is the same wherever you are.
- The right-hand inspector dropped its duplicative repo / branch / status trailer and added an
Actions:section listing the keystrokes available on the cursored entity. Destructive actions render with a[!]marker. - Inspector at rest is narrow (~22% of width); focusing it via
tabexpands to ~40% so the metadata, body preview, and action panel get the room they need. Mirrors the existing sidebar focus-expand pattern. - On short terminals (rows < 28), the inspector collapses into a tabbed
[Inspector] Actionslayout.[/]while the inspector is focused cycles tabs. - Cursoring a branch in the sidebar / branches view auto-jumps the history panel cursor to that branch's tip commit (debounced 150ms). Same for tags. A
Synced history to <ref>status confirms the jump even when the dedicated branches view obscures the history graph. - Current branch always pins to position 0 of the branches list regardless of sort mode. After a checkout, the cursor snaps back to row 0 so it lands on the just-checked-out branch.
- New keys on the history view:
Bcreate branch from cursored commit,gTcreate tag from cursored commit. The prompt itself is the affirmative gate. - Header label is
coco(wascoco ui).
- Pull-request panel via
g p: header / checks / reviews / body withm/x/a/R/c/Oaction keys. - History-view mutations:
Rrevert,Zreset (soft / mixed / hard mode prompt),iinteractive rebase. All routed through y-confirm or mode prompt. -
don a commit-diff or stash-diff toggles between unified and side-by-side rendering. Persists per-repo. Falls back to unified on terminals narrower than 120 cols. -
Hon a diff view applies the cursored hunk to the worktree (git apply).gHapplies to the index (git apply --cached). - Higher-fidelity commit graph: pattern junctions (
├╮/├╯), per-lane coloring, distinct merge / HEAD glyphs (◆/◉). - In-sidebar selection: when sidebar is focused,
←/→switch tabs and↑/↓(orj/k) navigate items. Per-entity ops (Enter checkout /aapply /Ddelete / etc.) fire from the sidebar without drilling into the dedicated view. - Multi-line input prompt for PR comments + review bodies. Enter inserts a newline;
Ctrl+Dsubmits. Single-line prompts (branch names, merge strategies) keep Enter as submit. - Branches list polish: dropped "even with X" noise, added relative timestamps (
today/Nd/Nw), four-state remote indicator (*/◌/≡/↕). - Stash diff header surfaces
@{N} <message> on <branch>instead of the raw timestamp ref.
-
y/Yyank the cursored identifier (commit hash, branch, tag, stash ref, file path) to the system clipboard from every promoted view. -
1/2/3on the status view toggle staged / unstaged / untracked visibility. -
/path:src/fooand/author:alicere-rungit logwith the matching flags so commits touching a path or by a given author show up without dropping to a shell.
gis now a chord prefix. Pressinggalone no longer toggles the graph immediately — it waits for the second key. The graph toggle moved to\. PreviouslygdidtoggleGraphas a side effect of starting aggchord, which caused a brief flicker on everyg-prefixed sequence.
If you had muscle memory for g, you can either retrain to \ or use : and search for "graph" to invoke it from the palette.
Navigation Within a view Modes
g h history j/k, ↑/↓ move ? help (toggles)
g s status gg / G top / bottom : palette (toggles)
g d diff n / N next / prev / search (toggles)
g c compose Tab focus next Esc close mode / back
g b branches ←/→ sidebar tab < back (nav stack)
g t tags [/] sidebar tab / Quit
g z stash inspector tab q, Ctrl+C
g w worktrees 1-5 sidebar jump
g p pull request \ toggle graph
g x conflicts r refresh
g r reflog d unified ↔ split (diff view)
g B bisect m mark compare base (branches/tags/history)
g/b/s/x bisect: good/bad/skip/reset (bisect view)
History view ops Diff view ops PR view ops
c cherry-pick H apply hunk m merge
R revert gH apply hunk to x close
Z reset (mode prompt) index a approve
i interactive rebase [/] file/hunk nav R request changes
B create branch here space stage/unstage c comment
gT create tag here z revert O open in browser
y/Y yank hash c cherry-pick file
O open in browser
For surface-specific actions (staging, hunks, commit compose, workflow keys like D/X/I), see the per-view sections in Coco UI.