fix(tui): an empty Pinned panel is not a panel
The rail spent four rows saying "No active work". That is density without meaning, and it takes those rows from the transcript. The old rule justified itself with "the user asked for the panel, so an empty panel collapses to a hint line, not a vanished rail". That premise was false for essentially everyone: the settings migration folded the default `sidebar_focus = "auto"` into `rail_panel = "pinned"` and persisted it, so any user with a settings.toml was handed this panel without ever choosing it — and the value stays in their file even after the migration was fixed. Tasks has always collapsed to zero rows on an empty projection. Pinned now does the same, using the `has_useful_content` predicate the work summary already exposed and nothing consulted. The panel reappears the instant there is a goal, a checklist, or live state. Test fallout is itself the argument: `idle_rail_app` could not be given content to keep the old assertions alive, because everything that gives the Pinned panel something to say — a hunt, todos, a running turn — also ends the idle empty state that draws the whale. "Idle app showing a populated Pinned strip" was never a reachable state. The two row-budget tests moved to the Agents panel, which genuinely is always-on while idle, and the placement test gained real content since it is about placement rather than emptiness. Receipts: cargo test -p codewhale-tui --bin codewhale-tui work_surface -> 69 passed; 0 failed cargo test -p codewhale-tui --bin codewhale-tui rail_strip -> 3 passed; 0 failed
This commit is contained in:
@@ -88,7 +88,7 @@ pub(crate) struct SidebarWorkSummary {
|
||||
}
|
||||
|
||||
impl SidebarWorkSummary {
|
||||
fn has_useful_content(&self) -> bool {
|
||||
pub(crate) fn has_useful_content(&self) -> bool {
|
||||
self.goal_objective
|
||||
.as_deref()
|
||||
.is_some_and(|s| !s.trim().is_empty())
|
||||
|
||||
@@ -20579,7 +20579,9 @@ fn has_idle_whale(rendered: &str) -> bool {
|
||||
|
||||
#[test]
|
||||
fn rail_strip_yields_its_rows_so_the_idle_whale_survives_at_24_rows() {
|
||||
let panel = crate::tui::work_surface::RailPanel::Pinned;
|
||||
// Agents, not Pinned: an empty Pinned panel now collapses on its own, so it
|
||||
// cannot exercise the row budget. Agents still holds its strip while idle.
|
||||
let panel = crate::tui::work_surface::RailPanel::Agents;
|
||||
// Below the threshold the always-on panel strip yields entirely and the
|
||||
// ocean keeps its full height. 24 rows is the release-evidence size the
|
||||
// rail regression took the whale away from.
|
||||
@@ -20687,7 +20689,8 @@ fn rail_strip_and_whale_swap_at_the_ambient_width() {
|
||||
// water. Unlike the height axis — where the same rule would make the
|
||||
// strip vanish as you drag the very axis it is measured in — this fires
|
||||
// on a deliberate horizontal resize with a visible payoff.
|
||||
let panel = crate::tui::work_surface::RailPanel::Pinned;
|
||||
// Agents for the same reason as above: Pinned self-collapses while idle.
|
||||
let panel = crate::tui::work_surface::RailPanel::Agents;
|
||||
let width_floor = crate::tui::underwater::AMBIENT_MIN_CHAT_WIDTH;
|
||||
let rows = 24_u16;
|
||||
|
||||
|
||||
@@ -624,7 +624,7 @@ mod tests {
|
||||
for top_height in [2_u16, 3] {
|
||||
let mut app = app();
|
||||
app.work_surface.placement = WorkSurfacePlacement::Top;
|
||||
app.work_surface.panel = super::RailPanel::Pinned;
|
||||
app.work_surface.panel = super::RailPanel::Agents;
|
||||
app.work_surface.top_height = top_height;
|
||||
app.composer_border = true;
|
||||
let budget = working_budget(&app, 40);
|
||||
@@ -1170,6 +1170,10 @@ mod tests {
|
||||
let mut app = app();
|
||||
app.work_surface.placement = placement;
|
||||
app.work_surface.panel = panel;
|
||||
// This test is about placement, not about the empty state. An
|
||||
// empty Pinned panel deliberately renders nothing, so give the
|
||||
// work summary content or Top would have no title to find.
|
||||
app.hunt.quarry = Some("ship the release".to_string());
|
||||
let area = ratatui::layout::Rect::new(0, 0, 100, 24);
|
||||
|
||||
// Render coverage, not yield coverage: a 24-row terminal with
|
||||
|
||||
@@ -51,13 +51,27 @@ pub fn height(app: &mut App, width: u16, terminal_height: u16, rail_budget: u16)
|
||||
collapse_strip(app);
|
||||
return 0;
|
||||
}
|
||||
// Non-Tasks panels always own a strip once selected: the user asked for
|
||||
// the panel, so an empty panel collapses to a hint line, not a vanished
|
||||
// rail. It still yields when the transcript has no rows to give.
|
||||
// Non-Tasks panels own a strip once selected — but only when they have
|
||||
// something to say. The old rule assumed "the user asked for the panel",
|
||||
// which was false for everyone: the settings migration folded the default
|
||||
// `sidebar_focus = "auto"` into `rail_panel = "pinned"`, so any user with a
|
||||
// settings.toml was handed this panel without choosing it. Four rows
|
||||
// spending themselves on the words "No active work" is density without
|
||||
// meaning, and it costs the transcript real estate the operator did want.
|
||||
//
|
||||
// Tasks has always collapsed to zero on an empty projection. This makes
|
||||
// Pinned behave the same way, which is the honest reading of the rule: an
|
||||
// empty panel is not a panel. It reappears the instant there is work.
|
||||
if app.work_surface.panel != RailPanel::Tasks {
|
||||
if app.work_surface.effective_placement != WorkSurfacePlacement::Top {
|
||||
return 0;
|
||||
}
|
||||
if app.work_surface.panel == RailPanel::Pinned
|
||||
&& !crate::tui::sidebar::sidebar_work_summary(app).has_useful_content()
|
||||
{
|
||||
collapse_strip(app);
|
||||
return 0;
|
||||
}
|
||||
// What the panel is actually asking for: its design height, or less
|
||||
// if the user set a shorter strip. `top_height` is a preference, not
|
||||
// a budget — a user who drags the divider to its 2-row minimum wants
|
||||
|
||||
Reference in New Issue
Block a user