huskies: merge 1056

This commit is contained in:
dave
2026-05-14 19:09:20 +00:00
parent 6d53382f8c
commit b64eb69aee
2 changed files with 122 additions and 8 deletions
@@ -1,4 +1,5 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import type { PipelineStageItem } from "../api/client";
import { StagePanel } from "./StagePanel";
@@ -489,6 +490,50 @@ describe("StagePanel", () => {
expect(icon).toBeInTheDocument();
expect(icon).toHaveTextContent("⏳");
});
it("renders gate output in a bounded box with expand and copy controls", () => {
const items: PipelineStageItem[] = [
{
story_id: "60_story_gate_output",
name: "Gate Output Story",
error: null,
merge_failure: "Quality gates failed: cargo test output here",
agent: null,
review_hold: null,
qa: null,
depends_on: null,
},
];
render(<StagePanel title="Merge" items={items} />);
expect(screen.getByTestId("gate-output-text")).toHaveTextContent(
"Quality gates failed: cargo test output here",
);
expect(screen.getByTestId("gate-output-toggle")).toBeInTheDocument();
expect(screen.getByTestId("gate-output-copy")).toBeInTheDocument();
});
it("expand toggle changes label from Expand to Collapse", async () => {
const user = userEvent.setup();
const items: PipelineStageItem[] = [
{
story_id: "61_story_expand",
name: "Expand Story",
error: null,
merge_failure: "A".repeat(1000),
agent: null,
review_hold: null,
qa: null,
depends_on: null,
},
];
render(<StagePanel title="Merge" items={items} />);
const toggle = screen.getByTestId("gate-output-toggle");
expect(toggle).toHaveTextContent("Expand");
await user.click(toggle);
expect(toggle).toHaveTextContent("Collapse");
await user.click(toggle);
expect(toggle).toHaveTextContent("Expand");
});
});
describe("StagePanel - defensive rendering", () => {
+77 -8
View File
@@ -5,6 +5,82 @@ import { useLozengeFly } from "./LozengeFlyContext";
const { useLayoutEffect, useRef, useState } = React;
/** Renders merge-failure gate output in a bounded scroll region with expand and copy controls. */
function GateOutputBox({ text }: { text: string }) {
const [expanded, setExpanded] = useState(false);
const [copied, setCopied] = useState(false);
const handleToggle = (e: React.MouseEvent) => {
e.stopPropagation();
setExpanded((prev) => !prev);
};
const handleCopy = (e: React.MouseEvent) => {
e.stopPropagation();
navigator.clipboard.writeText(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
});
};
const btnStyle: React.CSSProperties = {
background: "transparent",
border: "1px solid #444",
borderRadius: "4px",
color: "#aaa",
cursor: "pointer",
fontSize: "0.75em",
padding: "1px 6px",
lineHeight: 1.4,
};
return (
<div style={{ marginTop: "4px" }}>
<div
data-testid="gate-output-text"
style={{
fontSize: "0.8em",
color: "#f85149",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
fontFamily: "monospace",
background: "#1a0808",
borderRadius: "4px",
padding: "6px 8px",
maxHeight: expanded ? "none" : "10rem",
overflowY: expanded ? "visible" : "auto",
}}
>
{text}
</div>
<div
style={{
display: "flex",
gap: "6px",
marginTop: "4px",
}}
>
<button
type="button"
data-testid="gate-output-toggle"
onClick={handleToggle}
style={btnStyle}
>
{expanded ? "▲ Collapse" : "▼ Expand"}
</button>
<button
type="button"
data-testid="gate-output-copy"
onClick={handleCopy}
style={btnStyle}
>
{copied ? "✓ Copied" : "⎘ Copy"}
</button>
</div>
</div>
);
}
type WorkItemType = "story" | "bug" | "spike" | "refactor" | "unknown";
const TYPE_COLORS: Record<WorkItemType, string> = {
@@ -564,15 +640,8 @@ export function StagePanel({
{item.merge_failure && (
<div
data-testid={`merge-failure-reason-${item.story_id}`}
style={{
fontSize: "0.8em",
color: "#f85149",
marginTop: "4px",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}
>
{item.merge_failure}
<GateOutputBox text={item.merge_failure} />
</div>
)}
{item.depends_on && item.depends_on.length > 0 && (