huskies: progress 983 — differentiated icons for stuck-story states

Distinct icons in StagePanel/GatewayPanel/render.rs status output for
blocked-with-running-recovery (robot), blocked-with-queued-recovery (hourglass),
and blocked-cold (red circle). All 2822 tests pass.
This commit is contained in:
Timmy
2026-05-13 15:46:36 +01:00
parent 14a39b6205
commit c811672e18
3 changed files with 202 additions and 35 deletions
+62
View File
@@ -68,6 +68,67 @@ export function StoryRow({ item }: { item: PipelineItem }) {
const color = STAGE_COLORS[item.stage] ?? "#8b949e";
const label = STAGE_LABELS[item.stage] ?? item.stage;
const idNum = item.story_id.match(/^(\d+)/)?.[1];
const agentStatus = item.agent?.status;
const isStuck = item.blocked || (item.merge_failure != null && item.merge_failure !== "");
const recoveryBadge = isStuck
? agentStatus === "running"
? (
<span
data-testid={`recovery-badge-${item.story_id}`}
title="Recovery in progress — no human action needed"
style={{
fontSize: "0.75em",
fontWeight: 700,
color: "#e3b341",
background: "#2a1e00",
border: "1px solid #6e4f00",
borderRadius: "4px",
padding: "1px 5px",
flexShrink: 0,
}}
>
RECOVERING
</span>
)
: agentStatus === "pending"
? (
<span
data-testid={`recovery-badge-${item.story_id}`}
title="Recovery scheduled — waiting for a slot"
style={{
fontSize: "0.75em",
fontWeight: 700,
color: "#e3b341",
background: "#2a1e00",
border: "1px solid #6e4f00",
borderRadius: "4px",
padding: "1px 5px",
flexShrink: 0,
}}
>
QUEUED
</span>
)
: (
<span
data-testid={`recovery-badge-${item.story_id}`}
title={item.merge_failure ? "Merge failed — needs human" : "Blocked — awaiting human unblock"}
style={{
fontSize: "0.75em",
fontWeight: 700,
color: "#f85149",
background: "#2a1010",
border: "1px solid #6e1b1b",
borderRadius: "4px",
padding: "1px 5px",
flexShrink: 0,
}}
>
STUCK
</span>
)
: null;
return (
<div
@@ -92,6 +153,7 @@ export function StoryRow({ item }: { item: PipelineItem }) {
>
{label}
</span>
{recoveryBadge}
<span style={{ color: "#e6edf3", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{idNum && <span style={{ color: "#8b949e", fontFamily: "monospace" }}>#{idNum}{" "}</span>}
{item.name}
+111 -32
View File
@@ -345,19 +345,53 @@ export function StagePanel({
<>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 600, fontSize: "0.9em" }}>
{hasMergeFailure && (
<span
data-testid={`merge-failure-icon-${item.story_id}`}
title="Merge failed"
style={{
color: "#f85149",
marginRight: "6px",
fontStyle: "normal",
}}
>
</span>
)}
{hasMergeFailure && (() => {
const agentStatus = item.agent?.status;
if (agentStatus === "running") {
return (
<span
data-testid={`merge-failure-icon-${item.story_id}`}
title="Merge recovery in progress — no human action needed"
style={{
display: "inline-block",
color: "#e3b341",
marginRight: "6px",
animation: "spin 1s linear infinite",
}}
>
</span>
);
}
if (agentStatus === "pending") {
return (
<span
data-testid={`merge-failure-icon-${item.story_id}`}
title="Merge recovery scheduled — waiting for a slot"
style={{
color: "#e3b341",
marginRight: "6px",
fontStyle: "normal",
}}
>
</span>
);
}
return (
<span
data-testid={`merge-failure-icon-${item.story_id}`}
title="Merge failed — needs human"
style={{
color: "#f85149",
marginRight: "6px",
fontStyle: "normal",
}}
>
</span>
);
})()}
{mergesInFlight?.has(item.story_id) && (
<span
data-testid={`merge-in-flight-icon-${item.story_id}`}
@@ -396,25 +430,70 @@ export function StagePanel({
{typeLabel}
</span>
)}
{item.blocked && !item.merge_failure && (
<span
data-testid={`blocked-badge-${item.story_id}`}
title="Blocked — awaiting human unblock"
style={{
fontSize: "0.65em",
fontWeight: 700,
color: "#f85149",
background: "#2a1010",
border: "1px solid #6e1b1b",
borderRadius: "4px",
padding: "1px 4px",
marginRight: "8px",
letterSpacing: "0.05em",
}}
>
BLOCKED
</span>
)}
{item.blocked && !item.merge_failure && (() => {
const agentStatus = item.agent?.status;
if (agentStatus === "running") {
return (
<span
data-testid={`blocked-badge-${item.story_id}`}
title="Recovery in progress — no human action needed"
style={{
fontSize: "0.65em",
fontWeight: 700,
color: "#e3b341",
background: "#2a1e00",
border: "1px solid #6e4f00",
borderRadius: "4px",
padding: "1px 4px",
marginRight: "8px",
letterSpacing: "0.05em",
}}
>
RECOVERING
</span>
);
}
if (agentStatus === "pending") {
return (
<span
data-testid={`blocked-badge-${item.story_id}`}
title="Recovery scheduled — waiting for a slot"
style={{
fontSize: "0.65em",
fontWeight: 700,
color: "#e3b341",
background: "#2a1e00",
border: "1px solid #6e4f00",
borderRadius: "4px",
padding: "1px 4px",
marginRight: "8px",
letterSpacing: "0.05em",
}}
>
QUEUED
</span>
);
}
return (
<span
data-testid={`blocked-badge-${item.story_id}`}
title="Blocked — awaiting human unblock"
style={{
fontSize: "0.65em",
fontWeight: 700,
color: "#f85149",
background: "#2a1010",
border: "1px solid #6e1b1b",
borderRadius: "4px",
padding: "1px 4px",
marginRight: "8px",
letterSpacing: "0.05em",
}}
>
BLOCKED
</span>
);
})()}
{item.frozen && (
<span
data-testid={`frozen-badge-${item.story_id}`}