huskies: merge 1045

This commit is contained in:
dave
2026-05-14 16:48:20 +00:00
parent 311883f45d
commit 4553df5b21
3 changed files with 131 additions and 11 deletions
+65 -4
View File
@@ -66,15 +66,36 @@ const STAGE_LABELS: Record<string, string> = {
archived: "Archived",
};
/// Derive a short label from a merge failure string based on the failure kind.
function mergeFailureKindLabel(failure: string): string {
if (failure.includes("Merge conflict") || failure.includes("CONFLICT")) {
return "ConflictDetected";
}
if (failure.includes("Quality gates failed") || failure.includes("gates failed")) {
return "GatesFailed";
}
if (failure.includes("no code changes") || failure.includes("empty diff")) {
return "EmptyDiff";
}
if (failure.includes("No commits")) {
return "NoCommits";
}
return "✕ FAILED";
}
/// A single story row inside a project pipeline card.
/** Render one story row in a gateway-aggregate panel: `#<id> <name>` with stage badge. */
export function StoryRow({ item }: { item: PipelineItem }) {
export function StoryRow({ item, mergeQueuePos }: { item: PipelineItem; mergeQueuePos?: number }) {
const isStuck = item.merge_failure != null || item.blocked;
const isMergeActive = item.stage === "merge" && !isStuck && item.agent?.status === "running";
let color: string;
let label: string;
if (isStuck) {
if (isMergeActive) {
color = "#58a6ff";
label = "▶ MERGING";
} else if (isStuck) {
const agentStatus = item.agent?.status;
if (agentStatus === "running") {
color = "#e3b341";
@@ -82,9 +103,24 @@ export function StoryRow({ item }: { item: PipelineItem }) {
} else if (agentStatus === "pending") {
color = "#e3b341";
label = "⏳ QUEUED";
} else if (item.merge_failure != null) {
color = "#f85149";
label = mergeFailureKindLabel(item.merge_failure);
} else {
color = "#f85149";
label = item.merge_failure != null ? "✕ FAILED" : "⊘ BLOCKED";
label = "⊘ BLOCKED";
}
} else if (item.stage === "merge" && item.agent?.status === "pending") {
color = "#e3b341";
label = "⏳ QUEUED";
} else if (item.stage === "merge") {
color = "#6e7681";
if (mergeQueuePos === 1) {
label = "NEXT IN QUEUE";
} else if (mergeQueuePos != null) {
label = `awaiting-slot (#${mergeQueuePos})`;
} else {
label = "awaiting-slot";
}
} else {
color = STAGE_COLORS[item.stage] ?? "#8b949e";
@@ -101,6 +137,10 @@ export function StoryRow({ item }: { item: PipelineItem }) {
gap: "8px",
padding: "4px 0",
fontSize: "0.82em",
background: isMergeActive ? "#58a6ff0a" : undefined,
borderRadius: isMergeActive ? "4px" : undefined,
paddingLeft: isMergeActive ? "4px" : undefined,
paddingRight: isMergeActive ? "4px" : undefined,
}}
>
<span
@@ -446,10 +486,12 @@ function ProjectStoryRow({
project,
item,
showProject,
mergeQueuePos,
}: {
project: string;
item: PipelineItem;
showProject: boolean;
mergeQueuePos?: number;
}) {
return (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
@@ -470,7 +512,7 @@ function ProjectStoryRow({
</span>
)}
<div style={{ flex: 1, minWidth: 0 }}>
<StoryRow item={item} />
<StoryRow item={item} mergeQueuePos={mergeQueuePos} />
</div>
</div>
);
@@ -503,6 +545,20 @@ function InProgressTabContent({
(s) => byStage[s].length > 0,
);
// Compute queue position among clean awaiting merge items (Stage::Merge, no failure, no running agent).
const mergeQueuePosMap = new Map<string, number>();
let queuePos = 0;
for (const { project, item } of byStage.merge) {
if (
!item.blocked &&
!item.merge_failure &&
item.agent?.status !== "running"
) {
queuePos += 1;
mergeQueuePosMap.set(`${project}:${item.story_id}`, queuePos);
}
}
if (allItems.length === 0) {
return (
<p style={{ color: "#6e7681", padding: "16px 0" }}>
@@ -538,6 +594,11 @@ function InProgressTabContent({
project={project}
item={item}
showProject={multiProject}
mergeQueuePos={
stage === "merge"
? mergeQueuePosMap.get(`${project}:${item.story_id}`)
: undefined
}
/>
))}
</div>