fix(maestro): preserve fixed card trailing fragments
This commit is contained in:
@@ -151,26 +151,27 @@ impl Sidebar {
|
||||
}
|
||||
let command = Span::styled(format!(" {}", s.command), muted);
|
||||
|
||||
// Reserve the trailing fragments so detail truncates before them;
|
||||
// the command is itself the final truncation target on narrow cards.
|
||||
// Reserve the triggered status fragments before detail and command.
|
||||
// If the row is exceptionally narrow, keep one column for the
|
||||
// unconditional command and truncate the status tail first. Detail
|
||||
// then receives only space left after the full command reservation;
|
||||
// when the full command cannot fit, it is the final truncation target.
|
||||
let head_w: usize = spans.iter().map(|sp| sp.width()).sum();
|
||||
let tail_w: usize = tail.iter().map(|sp| sp.width()).sum();
|
||||
let command_w = command.width();
|
||||
let available = width.saturating_sub(head_w);
|
||||
if tail_w + command_w > available {
|
||||
spans.extend(fit_to_width(vec![command], available));
|
||||
return spans;
|
||||
}
|
||||
let command_floor = usize::from(available > 0);
|
||||
let tail = fit_to_width(tail, available.saturating_sub(command_floor));
|
||||
let tail_w: usize = tail.iter().map(|sp| sp.width()).sum();
|
||||
let command_w = command.width().min(available.saturating_sub(tail_w));
|
||||
if let Some(detail) = s.activity_detail.as_deref().filter(|d| !d.is_empty()) {
|
||||
let budget = width.saturating_sub(head_w + tail_w);
|
||||
let budget = available.saturating_sub(tail_w + command_w);
|
||||
spans.extend(fit_to_width(
|
||||
vec![Span::styled(format!(" {detail}"), muted)],
|
||||
budget,
|
||||
));
|
||||
}
|
||||
spans.extend(tail);
|
||||
spans.push(command);
|
||||
fit_to_width(spans, width)
|
||||
spans.extend(fit_to_width(vec![command], command_w));
|
||||
spans
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,6 +784,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocked_warning_survives_before_a_truncated_command() {
|
||||
let mut s = session_named(1, "name");
|
||||
s.command = "a command that cannot fit".into();
|
||||
s.blocked_work = Some(InTheWay {
|
||||
uncommitted_files: 1,
|
||||
..Default::default()
|
||||
});
|
||||
let sb = Sidebar {
|
||||
sessions: vec![s],
|
||||
highlight: None,
|
||||
attached: Default::default(),
|
||||
};
|
||||
let all = rows(&draw(&sb, 29, 6));
|
||||
assert!(all[2].contains("⚠ 1 uncommitted file"), "{:?}", all[2]);
|
||||
let meta = content(&all[2]);
|
||||
assert!(meta.ends_with('…'), "command must truncate: {meta:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_detail_yields_to_status_and_command() {
|
||||
let mut s = session_named(1, "name");
|
||||
s.activity_detail = Some("a very long agent-reported detail".into());
|
||||
s.blocked_work = Some(InTheWay {
|
||||
uncommitted_files: 1,
|
||||
..Default::default()
|
||||
});
|
||||
let sb = Sidebar {
|
||||
sessions: vec![s],
|
||||
highlight: None,
|
||||
attached: Default::default(),
|
||||
};
|
||||
let all = rows(&draw(&sb, 40, 6));
|
||||
assert!(all[2].contains("⚠ 1 uncommitted file"), "{:?}", all[2]);
|
||||
assert!(
|
||||
all[2].contains("bash"),
|
||||
"command must survive: {:?}",
|
||||
all[2]
|
||||
);
|
||||
assert!(all[2].contains('…'), "detail must truncate: {:?}", all[2]);
|
||||
}
|
||||
|
||||
/// Reversed video makes a card unreadable against a background; cards
|
||||
/// use explicit backgrounds instead (docs/SIDEBAR.md, #108).
|
||||
#[test]
|
||||
|
||||
+11
-2
@@ -791,7 +791,8 @@ textarea {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1px;
|
||||
height: 40px;
|
||||
/* 24px action row + 16px meta row + 1px gap + 8px vertical padding. */
|
||||
height: 49px;
|
||||
}
|
||||
|
||||
.card-line1 {
|
||||
@@ -854,7 +855,8 @@ textarea {
|
||||
ellipsis so it never pushes the trailing fragments off the line. */
|
||||
.activity-detail {
|
||||
color: var(--faint);
|
||||
flex: 0 1 auto;
|
||||
/* Consume only the space left after the trailing status + command. */
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -865,12 +867,19 @@ textarea {
|
||||
signals severity and "exited" dims with the row. */
|
||||
.card-flag {
|
||||
color: var(--faint);
|
||||
flex: 0 0 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Status fragments stay intact. The unconditional command is the final
|
||||
truncation target when even the complete trailing set cannot fit. */
|
||||
.leaf-cmd {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
/* Actions live in line 1's flow at its right edge — never overlaid on the
|
||||
name (#107). They reserve their width even while hover-faded. */
|
||||
.leaf-actions {
|
||||
|
||||
Reference in New Issue
Block a user