huskies: merge 1059

This commit is contained in:
dave
2026-05-14 19:04:29 +00:00
parent 7d7e02f7b0
commit 6d53382f8c
6 changed files with 121 additions and 4 deletions
+73
View File
@@ -0,0 +1,73 @@
/** React error boundary that catches render-time exceptions and shows a
* recoverable error UI instead of a white screen. */
import * as React from "react";
interface Props {
children: React.ReactNode;
}
interface State {
error: Error | null;
}
/** Catches uncaught render exceptions in its subtree and displays a message. */
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error: Error): State {
return { error };
}
handleReset = () => {
this.setState({ error: null });
};
render() {
if (this.state.error) {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: "100vh",
background: "#0d1117",
color: "#e6edf3",
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
gap: "16px",
padding: "32px",
textAlign: "center",
}}
>
<div style={{ fontSize: "2em" }}></div>
<div style={{ fontWeight: 600, fontSize: "1.1em" }}>
Something went wrong
</div>
<div style={{ color: "#8b949e", fontSize: "0.9em", maxWidth: "480px" }}>
{this.state.error.message}
</div>
<button
type="button"
onClick={this.handleReset}
style={{
padding: "8px 18px",
borderRadius: "6px",
border: "1px solid #30363d",
background: "#21262d",
color: "#e6edf3",
cursor: "pointer",
fontSize: "0.9em",
}}
>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
@@ -490,3 +490,39 @@ describe("StagePanel", () => {
expect(icon).toHaveTextContent("⏳"); expect(icon).toHaveTextContent("⏳");
}); });
}); });
describe("StagePanel - defensive rendering", () => {
it("renders without exception when a story is missing its name field", () => {
const items = [
{
story_id: "60_story_no_name",
name: undefined as unknown as string,
error: null,
merge_failure: null,
agent: null,
review_hold: null,
qa: null,
depends_on: null,
},
];
expect(() => render(<StagePanel title="Current" items={items} />)).not.toThrow();
expect(screen.getByTestId("card-60_story_no_name")).toBeInTheDocument();
});
it("renders without exception when a story is missing its story_id field", () => {
const items = [
{
story_id: undefined as unknown as string,
name: "Orphaned Story",
error: null,
merge_failure: null,
agent: null,
review_hold: null,
qa: null,
depends_on: null,
},
];
expect(() => render(<StagePanel title="Current" items={items} />)).not.toThrow();
expect(screen.getByText("Orphaned Story")).toBeInTheDocument();
});
});
+4 -2
View File
@@ -313,8 +313,10 @@ export function StagePanel({
}} }}
> >
{items.map((item) => { {items.map((item) => {
const itemNumber = item.story_id.match(/^(\d+)/)?.[1]; const itemNumber = item.story_id?.match(/^(\d+)/)?.[1];
const itemType = getWorkItemType(item.story_id); const itemType = item.story_id
? getWorkItemType(item.story_id)
: "unknown";
const borderColor = TYPE_COLORS[itemType]; const borderColor = TYPE_COLORS[itemType];
const typeLabel = TYPE_LABELS[itemType]; const typeLabel = TYPE_LABELS[itemType];
const hasMergeFailure = Boolean(item.merge_failure); const hasMergeFailure = Boolean(item.merge_failure);
@@ -258,7 +258,7 @@ export function WorkItemDetailPanel({
{error} {error}
</div> </div>
)} )}
{!loading && !error && content !== null && ( {!loading && !error && content != null && (
<div <div
data-testid="detail-panel-content" data-testid="detail-panel-content"
className="markdown-body" className="markdown-body"
@@ -24,6 +24,9 @@ export const STATUS_COLORS: Record<AgentStatusValue, string> = {
* them again inside the markdown body creates duplicate information. * them again inside the markdown body creates duplicate information.
*/ */
export function stripDisplayContent(content: string): string { export function stripDisplayContent(content: string): string {
// Guard: content may be undefined/null at runtime if the server response is
// missing the field (e.g. a tombstoned story returns an error object).
if (!content) return "";
let text = content; let text = content;
// Strip YAML front matter (--- ... ---) // Strip YAML front matter (--- ... ---)
if (text.startsWith("---")) { if (text.startsWith("---")) {
+3
View File
@@ -1,9 +1,12 @@
import * as React from "react"; import * as React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import App from "./App"; import App from "./App";
import { ErrorBoundary } from "./components/ErrorBoundary";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode> <React.StrictMode>
<ErrorBoundary>
<App /> <App />
</ErrorBoundary>
</React.StrictMode>, </React.StrictMode>,
); );