Files
storkit/frontend/src/components/GatePanel.tsx
Dave 013b28d77f Story 26: Establish TDD workflow and quality gates
Add workflow engine with acceptance gates, test recording, and review
queue. Frontend displays gate status (blocked/ready), test summaries,
failing badges, and warnings. Proceed action is disabled when gates
are not met. Includes 13 unit tests (Vitest) and 9 E2E tests
(Playwright) covering all five acceptance criteria.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 12:54:04 +00:00

183 lines
3.9 KiB
TypeScript

interface GateState {
canAccept: boolean;
reasons: string[];
warning: string | null;
summary: {
total: number;
passed: number;
failed: number;
};
missingCategories: string[];
}
interface GatePanelProps {
gateState: GateState | null;
gateStatusLabel: string;
gateStatusColor: string;
isGateLoading: boolean;
gateError: string | null;
lastGateRefresh: Date | null;
onRefresh: () => void;
}
const formatTimestamp = (value: Date | null): string => {
if (!value) return "—";
return value.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
};
export function GatePanel({
gateState,
gateStatusLabel,
gateStatusColor,
isGateLoading,
gateError,
lastGateRefresh,
onRefresh,
}: GatePanelProps) {
return (
<div
style={{
border: "1px solid #333",
borderRadius: "10px",
padding: "12px 16px",
background: "#1f1f1f",
display: "flex",
flexDirection: "column",
gap: "8px",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "12px",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<div style={{ fontWeight: 600 }}>Workflow Gates</div>
<button
type="button"
onClick={onRefresh}
disabled={isGateLoading}
style={{
padding: "4px 10px",
borderRadius: "999px",
border: "1px solid #333",
background: isGateLoading ? "#2a2a2a" : "#2f2f2f",
color: isGateLoading ? "#777" : "#aaa",
cursor: isGateLoading ? "not-allowed" : "pointer",
fontSize: "0.75em",
fontWeight: 600,
}}
>
Refresh
</button>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "flex-end",
gap: "2px",
fontSize: "0.85em",
color: gateStatusColor,
}}
>
<div>{gateStatusLabel}</div>
<div style={{ fontSize: "0.8em", color: "#777" }}>
Updated {formatTimestamp(lastGateRefresh)}
</div>
</div>
</div>
{isGateLoading ? (
<div style={{ fontSize: "0.85em", color: "#aaa" }}>
Loading workflow gates...
</div>
) : gateError ? (
<div
style={{
fontSize: "0.85em",
color: "#ff7b72",
display: "flex",
alignItems: "center",
gap: "8px",
flexWrap: "wrap",
}}
>
<span>{gateError}</span>
<button
type="button"
onClick={onRefresh}
disabled={isGateLoading}
style={{
padding: "4px 10px",
borderRadius: "999px",
border: "1px solid #333",
background: isGateLoading ? "#2a2a2a" : "#2f2f2f",
color: isGateLoading ? "#777" : "#aaa",
cursor: isGateLoading ? "not-allowed" : "pointer",
fontSize: "0.75em",
fontWeight: 600,
}}
>
Retry
</button>
</div>
) : gateState ? (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "6px",
}}
>
<div style={{ fontSize: "0.85em", color: "#aaa" }}>
Summary: {gateState.summary.passed}/{gateState.summary.total}{" "}
passing, {gateState.summary.failed} failing
</div>
{gateState.missingCategories.length > 0 && (
<div style={{ fontSize: "0.85em", color: "#ffb86c" }}>
Missing: {gateState.missingCategories.join(", ")}
</div>
)}
{gateState.warning && (
<div style={{ fontSize: "0.85em", color: "#ffb86c" }}>
{gateState.warning}
</div>
)}
{gateState.reasons.length > 0 && (
<ul
style={{
margin: "0 0 0 16px",
padding: 0,
fontSize: "0.85em",
color: "#ccc",
}}
>
{gateState.reasons.map((reason) => (
<li key={`gate-reason-${reason}`}>{reason}</li>
))}
</ul>
)}
</div>
) : (
<div style={{ fontSize: "0.85em", color: "#aaa" }}>
No workflow data yet.
</div>
)}
</div>
);
}