Skip to content

Name the key on the row that needs it, and stop hardcoding keys - #16

Merged
navbytes merged 1 commit into
mainfrom
claude/message-summarization-feedback-4lpb90
Sep 9, 2026
Merged

navbytes merged 1 commit into
mainfrom
claude/message-summarization-feedback-4lpb90

Conversation

@navbytes

@navbytes navbytes commented Sep 9, 2026

Copy link
Copy Markdown
Owner

The keymap is vim's, which is transparent if you already know vim and opaque otherwise: nothing on screen said that the on a folded turn opens with za.

The row tells you the key

The selected row, and only the selected row, carries a right-aligned hint for the one action it affords:

│ ● T5 add a retry to the flaky test   ▸ 6 steps · ~12k · 1 ✗          ~13k
› ● T6 now make it pass on CI                              za fold      ~2k

za open / za fold on a turn · → expand / ⏎ switch on a branch · space crop on a fat tool result · u restore on a cropped one.

It is cursor chrome, not content, so it is passed into rowLine as an argument that the live-search path does not pass — / can never match it. It gives way to the row's own text rather than clipping it on a narrow terminal, and crop mode and the ? pane suppress it (both already own a key legend of their own).

l / opens a folded turn

vim's foldopen default includes hor: a horizontal move opens the fold under the cursor. Only opening — nothing in vim closes a fold by moving, so h / keep their branch meaning and za / zc stay the way to close one. On a turn row this key did nothing at all before (at depth 0 there is no branch to expand, and deeper the branch is already shown), which is what left it free to mean this.

Nothing names a key with a string literal any more

A hint is worthless if the key it names is not the key that is bound, so every key on screen now comes from keyLabel(command, keybinds): the ? pane, all five footers, goVerb, the inspector's Crop and branch lines, the zr / zm notices, the discard gate. UNDO_KEY is gone.

test/help.test.ts runs its whole suite twice — once on the defaults, once with every verb it documents rebound and branch unbound — which is exactly what a pane assembled from literals cannot survive. Three bugs it caught on the way in:

  • the decisions footer still advertised E export after export moved to ge in beta.1;
  • keyLabel wrote a modifier with no separator, so a rebind to ctrl+u displayed as ctrlu;
  • an unbound command left a headless branch — try something risky… instead of dropping its clause.

The footers fit the terminal

The tree footer was the one line on screen with no width budget — the header and the status line both had one — and it overflowed at about 97 columns, more with a long branch name. core/help.ts#footerLine drops verbs from the right until the line fits, keeping ? help last since it is the route to everything it dropped.

Drift guards

  • test/help.test.ts — the doubled suite above, plus unit tests for keyLabel, rowHint, and footerLine at every width from 40 to 200.
  • test/docs-links.test.ts — the guide's keybinds command list is now held against DEFAULT_KEYS. The list is a hand copy, and a name missing from it is a rebind nobody can discover, since an unknown command name is silently ignored.
  • test/e2e/tui.test.ts — the fold test is rewritten for the world beta.2 actually ships. The default posture opens one turn, not three, so its old assertions were stale; it now walks the real tree (gg, za, zm, l, zr) and checks the hint appears on the cursor's row and only there.

I mutation-checked the two guards that matter: restoring a literal c crop and restoring a headless clause each fail the rebind pass.

Verification

  • bun run typecheck, bun test (451 pass, 0 fail), bun run build — clean.
  • The fold e2e passed against the real TUI (CTREE_E2E=1, OpenCode 1.18.26 in a pty), including the hint and l-opens-a-fold. The full e2e suite was still running at the time of writing; I will report it on this PR if anything comes back red.

What to look at by hand

  1. /tree, move up to a folded turn — the row should read za open on the right, and the hint should move with the cursor.
  2. l on it opens it; h should not re-fold it.
  3. Narrow to ~90 columns: the footer sheds gs consumers, then u undo, and never wraps.
  4. "keybinds": { "fold_toggle": "f2", "crop": "none" } — the hint says f2 open, the pane says f2 fold this turn, and crop disappears from both the pane and the footer rather than showing a keyless crop —.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VkCWU9Ab6wYHmTke8vuVMv


Generated by Claude Code

The keymap is vim's, which is transparent if you know vim and opaque
otherwise: nothing on screen said that the ▸ on a folded turn opens with
`za`. The selected row — and only the selected row — now carries a
right-aligned hint for the one action it affords: `za open`, `za fold`,
`→ expand`, `⏎ switch`, `space crop`, `u restore`. It is cursor chrome,
passed into `rowLine` as an argument the live-search path does not pass,
so `/` never matches it, and it gives way to the row's own text rather
than clipping it on a narrow terminal.

One vim-faithful addition alongside it: `l`/`→` opens a folded turn, the
way vim's `foldopen` default (`hor`) opens a fold on a horizontal move.
Only opening — nothing in vim closes a fold by moving, so `h`/`←` keep
their branch meaning and `za`/`zc` stay the way to close one. On a turn
row this key did nothing at all before, which is what left it free.

Neither is any use if the key named is not the key bound, so every key
on screen now comes from `keyLabel(command, keybinds)`: the ? pane, all
five footers, `goVerb`, the inspector's Crop and branch lines, the zr/zm
notices, the discard gate. `test/help.test.ts` runs its whole suite
twice — once on the defaults, once with every verb it documents rebound
and `branch` unbound — which is what a pane assembled from string
literals cannot survive. Three bugs it caught on the way in:

- the decisions footer still advertised `E export` after export moved to
  `ge` in beta.1;
- `keyLabel` wrote a modifier with no separator, so a rebind to `ctrl+u`
  displayed as `ctrlu`;
- an unbound command left a headless `  branch — try something risky…`
  rather than dropping its clause.

And the tree footer was the one line on screen with no width budget (the
header and the status line both had one), overflowing at about 97
columns and more with a long branch name. `core/help.ts#footerLine` now
drops verbs from the right until it fits, keeping `? help` last since it
is the route to everything it dropped; it is unit-tested at every width
from 40 to 200.

`test/docs-links.test.ts` also holds the guide's `keybinds` command list
against DEFAULT_KEYS now — the list is a hand copy, and a name missing
from it is a rebind nobody can discover, since an unknown command name
is silently ignored.

The fold e2e is rewritten for the world beta.2 actually ships: the
default posture opens one turn, not three, so its old assertions were
stale. It now walks the real tree — gg, za, zm, l, zr — and checks the
row hint appears on the cursor's row and only there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkCWU9Ab6wYHmTke8vuVMv
@navbytes
navbytes merged commit f1b74cc into main Sep 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants