Wizard text is clipped to the panel and measured in cells; the list header says what is shown (review B1, B2, Codex P2 on #304)

Every wizard write goes through Panel, which clips to the panel and saturates
its height arithmetic, so no step panics on a small terminal. The bottom row
truncates in cells, so a wrapped message with a multibyte character is drawn
whole. A scrolling list's range is named on its header row, where no item can
cover it. Tests: a multibyte message at every width, wrap bytes vs cells, a
no-panic sweep of every step at 20-100 x 3-20, the first item filling the list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Edward Langley
2026-09-24 19:11:42 -07:00
parent f00ed3e979
commit 22cbfa679e
7 changed files with 510 additions and 149 deletions
+20 -4
View File
@@ -569,10 +569,14 @@ one-line message therefore draws exactly as before, which is why the pinned
frames (`every_step_draws_what_it_drew_before`,
`the_formula_step_draws_its_transient_state`) did not move.
**Lists scroll by item, and say what they hide.** `draw_scrolling` draws the
items from `WizardCache::scroll` on, each one wrapped whole. It writes a
`▲ n more above` or `▼ n more below` row instead of cutting an item in half.
`ImportWizard::scroll` clamps to `len - 1`, so the last item can reach the top
**Lists scroll by item, and the header says what is shown.** `Panel::scrolling`
draws the items from `WizardCache::scroll` on, each one wrapped whole. The
list's own header row reads `Colliding records 3–9 of 14 (PgUp/PgDn):` whenever
some items are off screen. A first version put "more above" and "more below"
markers inside the list's rows. When the first item filled the list exactly,
the marker landed on the hint row and was drawn over (Codex, PR #304). The
header row belongs to no item, so no item can cover it, and no item loses a line
to make room for a marker. `ImportWizard::scroll` clamps to `len - 1`, so the last item can reach the top
and the list can never scroll away. It is by item, not by line, because the
wizard state cannot know the panel width that line-level clamping needs. The
residual is honest. A **single** item taller than the whole list area, roughly
@@ -580,5 +584,17 @@ residual is honest. A **single** item taller than the whole list area, roughly
wizard warning is that long today: a collision tuple is bounded by twelve
categories plus labels, but a label value is not bounded.
**Every write is clipped to the panel.** The widget draws through `Panel`,
whose `put` writes only inside the panel's inner rectangle, and its height
arithmetic saturates. Before this change, the duplicates step wrote at an
unbounded row and ratatui panicked at 80×10, and four older steps panicked on
`u16` underflow at heights of 3 to 5 (review B2 on PR #304). The bottom row's
`set_bottom_line` now truncates in cells through `ui::text::truncate`. It had
used the widget's byte-measured `truncate`, so a wrapped line holding a `←`
was cut one character early, with `…`, although it fit (review B1). The local
byte `truncate` still draws field names and labels. It is byte-identical on
ASCII, which keeps every pinned frame, and those rows are labels rather than
messages.
Other TUI surfaces still truncate text a user needs. They are out of this
ticket's scope and reported in its hand-back rather than fixed here.