feat(tui): auto-fit the to-do strip to its step count
The top work strip rendered at a fixed persisted height regardless of content: a two-step plan floated in a band of blank water, and a long plan scrolled inside a short window (user request, 2026-07-23 session). height() now fits the content — the selectable rows plus the pinned progress receipt and the divider — and treats the persisted work_surface_top_height as a cap rather than a fixed size, together with the existing half-terminal cap. Drag-resizing the divider and the settings key keep working; they now bound how tall the strip may grow. The default cap rises from 3 to 8 so a typical plan is visible in full, matching the requested ceiling, while short terminals still clamp to half their height. Verified: cargo test -p codewhale-tui --bin codewhale-tui — work_surface 57/57 (new top_strip_auto_fits_step_count_up_to_caps pins content-fit, cap-wins, short-terminal, and empty cases), tui::ui 625/625, settings 104/104.
This commit is contained in:
@@ -448,7 +448,9 @@ impl Default for Settings {
|
||||
fancy_animations: true,
|
||||
ocean_treatment: "ombre".to_string(),
|
||||
work_surface_placement: "top".to_string(),
|
||||
work_surface_top_height: 3,
|
||||
// Cap, not fixed height: the top strip auto-fits its rows and
|
||||
// only grows to this many lines (user request, 2026-07-23).
|
||||
work_surface_top_height: 8,
|
||||
work_surface_side_width: 30,
|
||||
constrained_frame_rate: false,
|
||||
bracketed_paste: true,
|
||||
|
||||
@@ -493,6 +493,34 @@ mod tests {
|
||||
assert_eq!(app.work_surface.hitboxes[0].row_y, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_strip_auto_fits_step_count_up_to_caps() {
|
||||
// Two steps: divider + progress receipt + 2 rows = 4 lines, not a
|
||||
// fixed-height band of blank water.
|
||||
let mut two_steps = app();
|
||||
two_steps.work_surface.top_height = 8;
|
||||
add_todos(&mut two_steps, 2);
|
||||
assert_eq!(super::height(&mut two_steps, 100, 40, false), 4);
|
||||
|
||||
// Ten steps: content wants 12 lines, the default 8-line cap wins.
|
||||
let mut ten_steps = app();
|
||||
ten_steps.work_surface.top_height = 8;
|
||||
add_todos(&mut ten_steps, 10);
|
||||
assert_eq!(super::height(&mut ten_steps, 100, 40, false), 8);
|
||||
|
||||
// Short terminal: the half-terminal cap beats both content and the
|
||||
// configured cap.
|
||||
let mut short_terminal = app();
|
||||
short_terminal.work_surface.top_height = 8;
|
||||
add_todos(&mut short_terminal, 10);
|
||||
assert_eq!(super::height(&mut short_terminal, 100, 12, false), 6);
|
||||
|
||||
// Nothing to show: no strip at all.
|
||||
let mut empty = app();
|
||||
empty.work_surface.top_height = 8;
|
||||
assert_eq!(super::height(&mut empty, 100, 40, false), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minimum_top_surface_keeps_a_numbered_todo_selectable() {
|
||||
let mut app = app();
|
||||
|
||||
@@ -55,10 +55,22 @@ pub fn height(app: &mut App, width: u16, terminal_height: u16, classic_shell: bo
|
||||
if app.work_surface.effective_placement != WorkSurfacePlacement::Top {
|
||||
return 0;
|
||||
}
|
||||
// The strip auto-fits its content: the literal selectable list plus the
|
||||
// pinned progress receipt and the divider row. `top_height` (drag-resize
|
||||
// / settings) and half the terminal act as caps, so a two-step plan
|
||||
// takes two rows while an eight-step plan grows to show all eight —
|
||||
// never a fixed-height band of blank water.
|
||||
let terminal_cap = terminal_height
|
||||
.saturating_div(2)
|
||||
.clamp(super::model::TOP_HEIGHT_MIN, super::model::TOP_HEIGHT_MAX);
|
||||
app.work_surface.top_height.min(terminal_cap)
|
||||
let cap = app.work_surface.top_height.min(terminal_cap);
|
||||
let selectable = rows.iter().filter(|row| row.selectable).count();
|
||||
let progress = u16::from(top_todo_progress(app, &rows).is_some());
|
||||
let desired = u16::try_from(selectable)
|
||||
.unwrap_or(u16::MAX)
|
||||
.saturating_add(progress)
|
||||
.saturating_add(1);
|
||||
desired.clamp(super::model::TOP_HEIGHT_MIN, cap)
|
||||
}
|
||||
|
||||
/// Split the transcript slot for a side rail. Top placement consumes its own
|
||||
|
||||
Reference in New Issue
Block a user