fix(tui): the title bar's escape hatch outranks the document name (improvise-t14d)
`fill_line` composed the title bar's two segments by concatenating them and letting ratatui clip. ratatui clips the RIGHT, and the right segment is " ?:help :q quit " -- the only thing on screen telling a stuck user how to get help or get out. The left segment is decoration whose length is unbounded, so the affordance was the first thing sacrificed: measured through a TestBackend, a document named "Quarterly Regional Sales Analysis" stored as quarterly-regional-sales-2026.improv loses ":q quit" at 100 columns. Two smaller faults in the same three lines. The padding was `left.len()` -- BYTES -- and the title's own format string spells a "·", two bytes and one cell, so every document's right segment sat one cell in from the edge it is anchored to, before any non-ASCII name made it worse. And there was no truncation at all, so the failure was a mid-token clip of "?:" rather than a graceful drop. `Keeps` makes the priority a per-call-site decision rather than a global rule, because it genuinely differs: the title bar keeps the RIGHT (the hatch outranks the name), while `minibuffer_line` and `draw_status` keep the LEFT, which is what they already did -- there the left is the user's own typing plus the cursor, or the status message, and right priority would let a long message eat the text being edited. Truncation goes through ui::text::truncate, so widths are display cells throughout and an overflow is an ellipsis. Five tests, each rendered through a real TestBackend because the loss happens in ratatui's clip and a helper's return value cannot see it. Three faults injected, each caught by a distinct test; the reasoning and the injections are in context/test-evidence/tui.md and context/code/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:
@@ -154,6 +154,35 @@ composes both: the prompt and buffer on the left, any pending `status_msg`
|
||||
right-aligned. The alternatives and why they lost are in
|
||||
`context/test-evidence/tui.md`.
|
||||
|
||||
### `fill_line` — which segment yields is a per-call-site decision
|
||||
|
||||
Three rows in the TUI are one left segment and one right segment sharing a line:
|
||||
the title bar, `minibuffer_line` and `draw_status`. `fill_line` composes them,
|
||||
and it owes two things that it did not originally do — it must measure in
|
||||
**display cells** (it padded with `left.len()`, bytes, and the title's own `·`
|
||||
is two of them, so every document's right segment sat one cell in from its edge)
|
||||
and it must **truncate**, because concatenating and letting ratatui clip clips
|
||||
the *right*.
|
||||
|
||||
What clipping the right costs depends on the row, which is why `Keeps` is a
|
||||
parameter and not a rule:
|
||||
|
||||
- **The title bar keeps the right.** Its left segment is decoration whose length
|
||||
is unbounded — a document name plus a filename — and its right segment is the
|
||||
fixed `" ?:help :q quit "`, the only thing on screen telling a stuck user how
|
||||
to get out. Unconditional left priority made the escape hatch the first thing
|
||||
sacrificed, at 100 columns with an ordinary document name (improvise-t14d).
|
||||
- **Both bottom-bar rows keep the left**, which is what they already did. There
|
||||
the left is the user's own typing plus the cursor (`minibuffer_line`) or the
|
||||
status message (`draw_status`), and the right is a view badge or the message's
|
||||
own margin. Right priority there would let a long message eat the text being
|
||||
edited — the same mistake in the other direction.
|
||||
|
||||
So the rule is not "the right wins" but "the affordance wins over the
|
||||
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 `?:`.
|
||||
|
||||
### 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