fix(tui): one key table on screen, and it is the derived one (improvise-rr8n)
Two hand-written key tables were drawn BESIDE derived ones, in a different notation, and one of them was false. The tile bar's non-TileSelect hint read " Ctrl+^v<-> to move tiles" while Ctrl-Left/Right/Up/Down are bound in Normal to enter-tile-select -- pressing one ENTERS tile mode and moves nothing. It was the only persistent on-screen documentation of those keys, and it spelt Ctrl+ where key_label, and therefore every derived hint and help row, spells Ctrl-. The fix DELETES both of the tile bar's hints rather than rewriting one, which departs from the ticket's acceptance criteria and does so on the ticket's own evidence. rr8n proposed rewriting the string and suppressing the STATUS BAR's hint in TileSelect. But the derived status line is a strict superset of the tile bar's literal -- it carries Space, h/l and Tab, which the tile bar's omits, as the ticket's own finding 2 records -- and it cannot go stale, being read off the keymap. Suppressing it would have deleted information and kept the table that had just been shown to rot. In Normal the derived line already says T:tiles, so only the Ctrl-arrow spelling is lost, and it was never documented correctly. The wizard goes the other way and there the ticket is right. Its overlay draws correct per-step hints; the status bar drew one static line under it, wrong on four of eight steps, including telling the user Esc cancels on Done where Esc goes BACK. That line is derived too, but from the wrong input -- the wizard's real keys are resolved per step inside HandleWizardKey. So draw_status suppresses its hint fallback for that one mode. The rule, stated in context/code/tui.md: keep whichever table is derived from the thing it describes. Five tests. Four faults injected, each caught by a distinct test; the sharpest is suppressing the hint fallback unconditionally, which passes both tests aimed at the suppression and is caught only by the counterweight asserting TileSelect still advertises its keys. Two things that let the false hint survive are recorded: every_hint_key_resolves_in_its_mode cannot see a literal in a widget, and the widget's own characterization test renders at 80 columns, where the hint does not fit and is never drawn. Evidence: context/test-evidence/tui.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
@@ -183,6 +183,43 @@ decoration", and which is which is local. Truncation goes through
|
||||
`ui::text::truncate` (improvise-8l0c), so an overflow is a `…` rather than a
|
||||
mid-token clip of `?:`.
|
||||
|
||||
### One key table on screen, and it is the one derived from the keymap
|
||||
|
||||
The TUI stacks the tile bar (row 3) directly above the status bar (row 4), and
|
||||
the import wizard's overlay sits above the status bar too, so a mode can put
|
||||
**two** key tables on screen at once. When it does, they disagree — different
|
||||
notation, different content, and in one case a flat contradiction.
|
||||
|
||||
The rule is not "the overlay wins" or "the status bar wins". It is **keep
|
||||
whichever table is derived from the thing it describes** (improvise-rr8n):
|
||||
|
||||
- **Tile select: the status bar wins.** `hint_line(&mode)` is read off the
|
||||
keymap, so it cannot go stale, and it is a strict superset of what the tile
|
||||
bar's literal carried — `Space`, `h`/`l` and `Tab` were only ever on the
|
||||
derived line. The tile bar's own hints are therefore **deleted**, both of
|
||||
them, and `TileBar::render` now advertises no keys at all. The one it drew in
|
||||
Normal was false: it named `Ctrl+↑↓←→` as "to move tiles" while those four
|
||||
keys are bound to `enter-tile-select`, which moves nothing.
|
||||
- **The import wizard: the overlay wins.** `hint_line(&AppMode::ImportWizard)`
|
||||
is derived too, but from the *wrong input* — the wizard's real keys are
|
||||
resolved per step inside `HandleWizardKey`, and one static line off the mode's
|
||||
keymap is wrong on four of its eight steps, including telling the user `Esc`
|
||||
cancels on `Done` where `Esc` goes back. So `draw_status` suppresses its hint
|
||||
fallback for that one mode and lets the overlay's per-step hints stand alone.
|
||||
|
||||
Two things worth keeping from how this survived. `every_hint_key_resolves_in_its_mode`
|
||||
checks `AppMode::hint_text()` against the keymaps and **cannot see a string
|
||||
literal in a widget**, which is the same blind spot improvise-wk18 names for
|
||||
`help.rs`. And the widget's own characterization test renders at 80 columns,
|
||||
where the hint does not fit and is never drawn — so the test that pins this
|
||||
widget's output was structurally incapable of showing either hint. A fixture
|
||||
width is part of what a test can see.
|
||||
|
||||
The longer-term shape the ticket names is still open: the wizard's per-step keys
|
||||
belong in `advertised()` keyed by step, which would make them derived like
|
||||
everything else and let the status bar carry them correctly instead of being
|
||||
switched off.
|
||||
|
||||
### The caches are built here, per frame
|
||||
|
||||
`project::formulas_of`, `category_tree_of`, `view_list_of`, `tile_bar_of`,
|
||||
|
||||
Reference in New Issue
Block a user