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
+4 -1
View File
@@ -435,10 +435,13 @@ impl Plan<'_> {
}
}
/// A tuple, and each record on it: its 1-based position and the measures it writes.
type OnTuple<'a> = (&'a Vec<(String, String)>, Vec<(usize, Vec<&'a str>)>);
/// The tuples where two records write the same cell (improvise-9bit), with
/// only the records that do. Disjoint measures on one tuple sum nothing.
fn collisions_among(placements: &[Placement]) -> Vec<Collision> {
let mut tuples: Vec<(&Vec<(String, String)>, Vec<(usize, Vec<&str>)>)> = vec![];
let mut tuples: Vec<OnTuple> = vec![];
let mut index: HashMap<CellKey, usize> = HashMap::new();
for (at, placement) in placements.iter().enumerate() {
let Placement::Placed {