Skip to content

Commit 02929dd

Browse files
author
CodeWhale Bot
committed
fix(tui): keep the turn clock when the session half is suppressed (#6084)
1 parent f8f3211 commit 02929dd

3 files changed

Lines changed: 60 additions & 15 deletions

File tree

crates/tui/src/tui/phase_strip.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -804,11 +804,16 @@ struct PostureItem {
804804
/// can open, the hint names a chord you can press right now (`Esc to
805805
/// interrupt`, `Enter again to send now`). An 80-column row carrying the
806806
/// filesystem-scope notice cannot hold all of it, and losing the affordance
807-
/// to keep the stopwatch is the wrong trade. The turn half goes before the
808-
/// session half: the transcript's active row and the spinner also show the
809-
/// turn is alive, while the session total is stated nowhere else. Above
810-
/// them the context-cap warning, which is not a hint but the reason the
811-
/// next turn will not start at all.
807+
/// to keep the stopwatch is the wrong trade. When both halves would paint,
808+
/// the turn half goes before the session half: the transcript's active row
809+
/// and the spinner also show the turn is alive, while the session total is
810+
/// stated nowhere else. When the session half is suppressed (#6041) or
811+
/// otherwise absent, the turn half sheds at the session-clock rung instead
812+
/// so the ladder does not abandon the only clock at the turn-only rung
813+
/// while a both-clocks row would still be stating a stopwatch (#6084).
814+
/// The hint and counts still outrank it (#5914). Above them the
815+
/// context-cap warning, which is not a hint but the reason the next turn
816+
/// will not start at all.
812817
const SHED_TURN_CLOCK: u8 = 1;
813818
const SHED_SESSION_CLOCK: u8 = 2;
814819
const SHED_HINT: u8 = 3;
@@ -854,7 +859,17 @@ fn posture_items(footer: &TidelineFooter<'_>, shed: u8) -> Vec<PostureItem> {
854859
count_index: None,
855860
});
856861
}
857-
if let Some((clock, ink)) = footer.turn_clock.filter(|_| shed < SHED_TURN_CLOCK) {
862+
// When no session half will paint, shed the turn clock at the session
863+
// rung so a width that would keep the session half (and drop the turn)
864+
// still keeps the only informative clock (#6084). Hint and counts still
865+
// outrank it (#5914). With both halves present the turn half still goes
866+
// first.
867+
let turn_shed = if footer.session_clock.is_none() {
868+
SHED_SESSION_CLOCK
869+
} else {
870+
SHED_TURN_CLOCK
871+
};
872+
if let Some((clock, ink)) = footer.turn_clock.filter(|_| shed < turn_shed) {
858873
items.push(PostureItem {
859874
text: footer.sym(clock),
860875
ink,

crates/tui/src/tui/phase_strip/tideline_tests.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,29 @@ fn a_session_that_has_not_worked_states_no_clock() {
640640
);
641641
}
642642

643+
/// #6084: when the session half will not paint, the shed ladder must not
644+
/// drop the turn clock at the turn-only rung — that would leave no clock
645+
/// at all. Find a width where both-clocks already sheds the turn half while
646+
/// keeping the session half; with only the turn clock, that width must keep
647+
/// it (shed at the session-clock rung instead).
648+
#[test]
649+
fn absent_session_clock_keeps_turn_past_the_turn_only_rung() {
650+
let mut first_turn = working();
651+
first_turn.session_clock = None;
652+
let both = working();
653+
let width = (8..=160u16)
654+
.find(|&w| {
655+
let text = draw(w, 3, &both.widget(&UI_THEME));
656+
text.contains("worked 41m 12s") && !text.contains("working 1m 15s")
657+
})
658+
.expect("both-clocks fixture must shed turn before session at some width");
659+
let text = draw(width, 3, &first_turn.widget(&UI_THEME));
660+
assert!(
661+
text.contains("working 1m 15s"),
662+
"{width}: with no session half, the turn clock must survive the turn-only shed rung (#6084): {text}"
663+
);
664+
}
665+
643666
/// Narrow terminals shed both clock halves before the hint and the counts,
644667
/// the turn half before the session half, and neither reading is ever
645668
/// painted half-truncated.

crates/tui/src/tui/ui/frame/one_owner_tests.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,12 @@ fn count_rows_containing(rows: &[String], needle: &str) -> usize {
136136
/// frame: the context reading, the mode and permission chips, the model,
137137
/// the cost, the agent count, and the help hint.
138138
///
139-
/// 160 columns joins the blocker sizes so the working clock's two halves
140-
/// are asserted at a width that holds both; at 80 and 120 they shed by
141-
/// design (#5914) and the row is asserted for what it does keep.
139+
/// 160 columns joins the blocker sizes so both working-clock halves can
140+
/// paint together when the session half is present; at 80 and 120 the
141+
/// clocks shed by design (#5914) against the pinned scope notice. The
142+
/// #6084 shed-order fix (sole turn clock uses the session rung) is pinned
143+
/// in `phase_strip::tideline_tests`, where the narrower permission chip
144+
/// exposes the width band the one-owner fixture's notice collapses.
142145
#[test]
143146
fn composed_frame_paints_each_fact_in_exactly_one_row() {
144147
for (width, height) in [(80u16, 24u16), (120, 32), (160, 40)] {
@@ -207,14 +210,18 @@ fn composed_frame_paints_each_fact_in_exactly_one_row() {
207210
// The bar carries the working clock (#5914) — how long the current
208211
// turn has been doing what it is doing, and how long the session has
209212
// worked. Both halves shed before the hint and the counts, so a
210-
// narrow row keeps the affordances and drops the stopwatch: the
211-
// session half needs 120 columns here, the turn half 160. Each
212-
// paints in exactly one row wherever it paints. The metrics line
213-
// carries no repository, branch or provider.
213+
// narrow row keeps the affordances and drops the stopwatch. When
214+
// both would paint, the session half needs ~120 columns here and
215+
// the turn half ~160; each paints in exactly one row wherever it
216+
// paints. The metrics line carries no repository, branch or provider.
214217
// First turn: the turn half names the phase and stays; the session
215218
// reading is the identical duration, so it is suppressed rather than
216-
// stated twice (#6041) — the turn half is the one that needs 160
217-
// columns, so a narrower row keeps the affordances and no stopwatch.
219+
// stated twice (#6041). With no session half to paint, the turn
220+
// clock sheds at the session rung (#6084) rather than first — but
221+
// against this fixture's pinned scope notice, turn+counts+hint is
222+
// still just over a 120-column budget, so the hint wins here and
223+
// the turn half needs ~160. The shed-order contract itself lives
224+
// in tideline_tests.
218225
let turn_needle = "sub-agents underway 1m 15s";
219226
if width >= 160 {
220227
assert!(rows[posture].contains(turn_needle), "{}", rows[posture]);

0 commit comments

Comments
 (0)