huskies: merge 975

This commit is contained in:
dave
2026-05-13 13:39:24 +00:00
parent 5617da5c27
commit 6fc6c9fcb2
3 changed files with 148 additions and 7 deletions
@@ -0,0 +1,37 @@
/** Tests for GatewayPanel — verifies story id and name rendering in the gateway aggregate view. */
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import type { PipelineItem } from "../api/gateway";
import { StoryRow } from "./GatewayPanel";
describe("StoryRow", () => {
it("renders #id prefix before the story name", () => {
const item: PipelineItem = {
story_id: "42_story_add_feature",
name: "Add Feature",
stage: "current",
};
const { container } = render(<StoryRow item={item} />);
expect(container).toMatchSnapshot();
});
it("renders #id prefix for a backlogged story", () => {
const item: PipelineItem = {
story_id: "7_bug_fix_crash",
name: "Fix crash on startup",
stage: "qa",
};
const { container } = render(<StoryRow item={item} />);
expect(container).toMatchSnapshot();
});
it("renders name without id prefix when story_id has no leading number", () => {
const item: PipelineItem = {
story_id: "no-number-id",
name: "Mystery Story",
stage: "merge",
};
const { container } = render(<StoryRow item={item} />);
expect(container).toMatchSnapshot();
});
});
+39 -7
View File
@@ -63,9 +63,11 @@ const STAGE_LABELS: Record<string, string> = {
};
/// A single story row inside a project pipeline card.
function StoryRow({ item }: { item: PipelineItem }) {
/** Render one story row in a gateway-aggregate panel: `#<id> <name>` with stage badge. */
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];
return (
<div
@@ -91,6 +93,7 @@ function StoryRow({ item }: { item: PipelineItem }) {
{label}
</span>
<span style={{ color: "#e6edf3", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{idNum && <span style={{ color: "#8b949e", fontFamily: "monospace" }}>#{idNum}{" "}</span>}
{item.name}
</span>
</div>
@@ -110,7 +113,9 @@ function ProjectPipelineCard({
onSwitch: (name: string) => void;
}) {
const activeItems = pipeline.active ?? [];
const backlogItems = pipeline.backlog ?? [];
const backlogCount = pipeline.backlog_count ?? 0;
const remainingBacklog = backlogCount - backlogItems.length;
const hasError = Boolean(pipeline.error);
return (
@@ -131,7 +136,7 @@ function ProjectPipelineCard({
display: "flex",
alignItems: "center",
gap: "8px",
marginBottom: activeItems.length > 0 ? "8px" : 0,
marginBottom: activeItems.length > 0 || backlogItems.length > 0 ? "8px" : 0,
}}
>
<span style={{ fontWeight: 600, color: "#e6edf3" }}>{name}</span>
@@ -149,20 +154,47 @@ function ProjectPipelineCard({
active
</span>
)}
<span style={{ marginLeft: "auto", fontSize: "0.75em", color: "#6e7681" }}>
{backlogCount > 0 ? `${backlogCount} in backlog` : ""}
</span>
</div>
{hasError ? (
<div style={{ fontSize: "0.8em", color: "#f85149" }}>{pipeline.error}</div>
) : activeItems.length === 0 ? (
<div style={{ fontSize: "0.8em", color: "#6e7681" }}>No active stories</div>
) : activeItems.length === 0 && backlogItems.length === 0 ? (
<div style={{ fontSize: "0.8em", color: "#6e7681" }}>
{backlogCount > 0 ? `${backlogCount} in backlog` : "No active stories"}
</div>
) : (
<div>
{activeItems.map((item) => (
<StoryRow key={item.story_id} item={item} />
))}
{backlogItems.length > 0 && (
<div style={{ marginTop: activeItems.length > 0 ? "6px" : 0, borderTop: activeItems.length > 0 ? "1px solid #21262d" : "none", paddingTop: activeItems.length > 0 ? "6px" : 0 }}>
{backlogItems.map((item) => {
const idNum = item.story_id.match(/^(\d+)/)?.[1];
return (
<div
key={item.story_id}
style={{
display: "flex",
alignItems: "center",
gap: "6px",
padding: "2px 0",
fontSize: "0.82em",
color: "#6e7681",
}}
>
{idNum && <span style={{ fontFamily: "monospace", flexShrink: 0 }}>#{idNum}</span>}
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.name}</span>
</div>
);
})}
{remainingBacklog > 0 && (
<div style={{ fontSize: "0.75em", color: "#6e7681", paddingTop: "2px" }}>
and {remainingBacklog} more
</div>
)}
</div>
)}
</div>
)}
</div>
@@ -0,0 +1,72 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`StoryRow > renders #id prefix before the story name 1`] = `
<div>
<div
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
>
<span
style="padding: 1px 6px; border-radius: 10px; background: rgba(63, 185, 80, 0.133); color: rgb(63, 185, 80); border: 1px solid rgba(63, 185, 80, 0.267); white-space: nowrap; flex-shrink: 0;"
>
In Progress
</span>
<span
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
>
<span
style="color: rgb(139, 148, 158); font-family: monospace;"
>
#
42
</span>
Add Feature
</span>
</div>
</div>
`;
exports[`StoryRow > renders #id prefix for a backlogged story 1`] = `
<div>
<div
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
>
<span
style="padding: 1px 6px; border-radius: 10px; background: rgba(210, 166, 121, 0.133); color: rgb(210, 166, 121); border: 1px solid rgba(210, 166, 121, 0.267); white-space: nowrap; flex-shrink: 0;"
>
QA
</span>
<span
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
>
<span
style="color: rgb(139, 148, 158); font-family: monospace;"
>
#
7
</span>
Fix crash on startup
</span>
</div>
</div>
`;
exports[`StoryRow > renders name without id prefix when story_id has no leading number 1`] = `
<div>
<div
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
>
<span
style="padding: 1px 6px; border-radius: 10px; background: rgba(121, 192, 255, 0.133); color: rgb(121, 192, 255); border: 1px solid rgba(121, 192, 255, 0.267); white-space: nowrap; flex-shrink: 0;"
>
Merging
</span>
<span
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
>
Mystery Story
</span>
</div>
</div>
`;