Move the fold marker left, and make the ? pane's keys the bright thing - #17
Merged
Merged
Conversation
A folded turn now reads `● ▸6 T5 add a retry to the flaky test 1✗ 2⚠`. The `▸6` sits between the glyph and the text, which is where an outline puts a disclosure control. It used to trail the preview as `▸ 6 steps · ~12k · 1 ✗ · 2 ⚠`, and on a real screen that failed twice over. The caret was nowhere near the ● it belonged to, so on a tree of folded turns nothing marked a collapsed row until you had read to the end of a preview that is usually clipped. And the `~12k` was the row's own token column again: `applyFolds` rolls the hidden steps' tokens into the turn, so the two figures are equal by construction, and two numbers of the same magnitude a few columns apart that always agree read as one number that has gone wrong. So `foldDigest` becomes two: `foldMark` (the caret and the count, on the left) and `foldFlags` (` 1✂ 2⚠ 1✗`, at the end of the text, in the same place and the same glyphs a step row already uses for its own). The tokens are printed once, in the column that already exists. No unit word on the marker: a bare count beside a caret is unambiguous, and "steps" was six columns the preview wanted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkCWU9Ab6wYHmTke8vuVMv
The pane's job is to let someone find one key without reading, and its colour was exactly inverted for that: the route coloured whole lines, keyed on whether the line was indented, so headings — which name no keys at all — were the one accent-coloured thing on screen and every line that named a key was muted. A user looking for "how do I branch" had no token-shaped thing to scan for and had to read at reading speed. So the pane is now a list of typed segments (key, name, label, heading, glyph, strong, text) that the route colours individually. Keys are the brightest and the only bold thing; headings accent; prose muted; the │ gutter always dim rather than taking its line's colour. helpLines() is still there as the joined-string API the drift tests assert against. Act and Views become the table they already were: every row in them was `<key> <name> — <purpose>` in a consistent order, so the em dash is now two columns aligned at render time and you can enter from either side — the keys if you think `gm`, the names if you think "merge". Widths are measured per section, so one f12 rebound in Act cannot shift Legend, and a key past 8 columns takes its own row ragged instead of pushing its whole section right. Move keeps its packed clauses on purpose. Tabulating twenty motion clauses would cost twenty rows to make vim's own keys — the ones this audience already has — the most prominent thing on the pane. It gets a dim static label column instead (rows / jumps / folds / search), as does Legend (rows / tree / cost / status / ⎇ / lanes); static strings, so no rebind can break their alignment. Nine of the pane's lines were clipping at 100 columns with the width test green, because a help row is `│ ` inside a padding-1 box and its budget is cols-4, while the test asserted ≤112 against the raw string. One line clipped even at 112. The pane is now laid out for the terminal it is on: clauses drop from the right, prose clips with …, nothing wraps, and the same 34 rows survive down to a 44-column terminal. Also: the footer names the section you are in, since the headings scroll off a short terminal; the legend draws its colours instead of spelling them in grey, which is what let the two longest lines fit; glyphs take the tree's own colours. Two bugs found on the way, both by the pane's e2e while the unit suite stayed green. Importing TextAttributes from @opentui/core added a runtime import the plugin had never had — peer dep only — and the host then failed to load the route entirely; the two bits are written out and pinned against the real enum by a test that also asserts the bundle imports only @opentui/solid. And moving the HELP memo to read size() left it above its own dependency's declaration: a createMemo body runs the moment it is created, so it threw on every render. The drift guard is tightened in the direction that matters: every bound verb must appear inside a `key` segment, not merely somewhere in the line's text, since a key that leaks into a prose run is invisible again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkCWU9Ab6wYHmTke8vuVMv
`ctx ▓░░░░ 84.7k/1M · low · 99% cached` was one band-coloured run — the label, the bar and the numbers all in `success` green — in both the tree header and the prompt line, and it was reported illegible on a light theme. That it read at all on a dark theme was luck. A theme guarantees `text` and `textMuted` are readable on its own background; it guarantees nothing of the kind about `success` / `warning` / `error`, which it picks to be distinguishable from *each other*. So the whole gauge's legibility rested on a colour no theme promises you can read. The rule now is: the bar carries the band, readable text never does. `ctx` is muted, the numbers and the band word take the theme's own text colour, and the bar splits muted-cached / band-fresh / subtle-empty. A filled bar survives a lower contrast ratio than glyph strokes do, and its colour is the signal rather than a decoration on text that already says the same thing. It lives in core/gauge.ts as a table rather than inline in the JSX, because the rule is about tokens rather than any theme's hex values and is therefore testable without a terminal — which is what the gauge's colours never were. Three neighbours had the same defect, all fixed the same way (the glyph carries the colour, the label beside it is read): the prompt line's `⎇ <branch> · `, which was also a category error since which branch you are on has nothing to do with the context band; its `▲ +24% (bash)` trend; and the sidebar card's `⎇ <branch>` and `✂ 2 crops · ~14k hidden`. test/gauge.test.ts holds the table to the rule, and greps both components for a readable string inside a band-coloured element — the shape that shipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkCWU9Ab6wYHmTke8vuVMv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two changes from reading a real screen of beta.2, plus a TUI-UX review of the
?pane.1. The fold marker moved to the row's left edge
▸38now sits between the●and the text: the row's own disclosure control, where an outline puts one. Two things were wrong with the trailing digest:●it belonged to, so on a tree of folded turns nothing marked a collapsed row until you had read to the end of a preview that is usually clipped.~23.5kwas the row's own token column again.applyFoldsrolls the hidden steps' tokens into the turn, so the two figures are equal by construction — two numbers of the same magnitude a few columns apart that always agree read as one number that has gone wrong.foldDigestbecomesfoldMark(the caret and the count, on the left) andfoldFlags(1✂ 2⚠ 1✗, at the end of the text, in the same place and glyphs a step row already uses for its own). The tokens are printed once. No unit word — a bare count beside a caret is unambiguous, and "steps" was six columns the preview wanted.2. The
?paneIts colour was exactly inverted
route.tsxcoloured whole lines, keyed on whether the line was indented:Every line that names a key is indented — so the headings, which name no keys at all, were the one accent-coloured thing on the pane, and every key was muted. Someone opening
?to find one key had no token-shaped thing to scan for and had to read the pane at reading speed.The pane is now a list of typed segments (
key,name,label,heading,glyph,strong,text) that the route colours individually. Keys are the brightest and the only bold thing; headings accent; prose muted; the│gutter always dim instead of taking its line's colour.helpLines()remains as the joined-string API the drift tests assert against.ActandViewsbecame the table they already wereEvery row in them was already
<key> <name> — <purpose>in a consistent order. The" — "is now two columns aligned at render time, so you can enter from either side — scan the keys if you thinkgm, the names if you think "merge". Widths are measured per section, so onef12rebound inActcannot shiftLegend, and a key past 8 columns takes its own row ragged rather than pushing its whole section right.Movedeliberately keeps its packed clauses: tabulating twenty motion clauses would cost twenty rows to make vim's own keys — the ones this audience already has — the most prominent thing on the pane. It gets a dim static label column instead (rows/jumps/folds/search), as doesLegend(rows/tree/cost/status/⎇/lanes). Those labels are static strings, so no rebind can break their alignment.Nine lines were clipping at 100 columns with the width test green
A help row is
│inside apadding={1}box, so its budget iscols - 4— which is what every other line on the screen uses — but the test asserted≤ 112against the raw string. One line clipped even at 112. The pane is now laid out for the terminal it is on: clauses drop from the right, prose clips with…, nothing wraps (§7.6), and the same 34 rows survive from 100 columns down to 44.Smaller
12–29 of 34 · Act · PgUp/PgDn scroll · q/esc back) — the pane is a three-page scroll on a short terminal and its headings scroll off. Zero extra rows.█ open · █ squashed · …in the branch colours, lane swatches in the lane colours. Naming a colour in muted grey cost six columns and asked the reader to take a monochrome word's word for it; showing it costs one — which is what let the pane's two longest lines fit.⚠warning,✗error,◆ ≣ ✂accent);nothing here rewrites your transcriptis bold as the safety claim it is; the*model*markdown asterisks became a bold run.Net: 34 rows before, 34 after.
Two bugs found on the way — both caught by the pane's e2e while the unit suite stayed green
@opentui/corebroke the whole route. UsingTextAttributes.BOLDadded a runtime import the plugin had never had (peer dep only), and the host then failed to load the route at all —/treesimply never opened. The two bits are written out as constants, pinned against the real enum by a test that also asserts the built bundle imports only@opentui/solid.createMemoin the temporal dead zone. MovingHELPto readsize()left it above its own dependency's declaration; acreateMemobody runs the moment it is created, so it threw on every render.Tests
test/help.test.ts— rewritten against the segment model. The drift guard is tightened in the direction that matters: every bound verb must appear inside akeysegment, not merely somewhere in the line's text, since a key that leaks into a prose run is invisible again. Mutation-checked: folding one key back into a prose run fails 6 tests. Width is asserted against the row budget at four widths (96 / 76 / 56 / 40), and narrowing must never drop a row.test/fold.test.ts—foldMark/foldFlags, including that the marker never repeats the token column.test/e2e/tui.test.ts— the fold test asserts the marker is between the●and the preview and is not a digest; the?pane test asserts the new table shape and that the footer names a section.bun run typecheck,bun test(462 pass) andbun run buildare clean. The full pty e2e suite was still running when I opened this; I'll report on the PR if anything comes back red.Known gap
docs/screenshots/help.png, linked from the README, now shows the old pane. It needs a fresh capture on a real terminal — I can't produce a PNG from here.🤖 Generated with Claude Code
https://claude.ai/code/session_01VkCWU9Ab6wYHmTke8vuVMv
Generated by Claude Code