diff
May 24, 08:48 PMdiff-artifact/v1{
"artifact": {
"files": [
{
"path": "apps/web/app/api/ci-failures/[id]/route.ts",
"hunks": [
{
"lines": [
{
"type": "context",
"content": "export async function GET(_request: Request, context: { params: { id: string } }): Promise<Response> {",
"newLineNumber": 3,
"oldLineNumber": 3
},
{
"type": "context",
"content": " return forwardDashboardApiMutation({",
"newLineNumber": 4,
"oldLineNumber": 4
},
{
"type": "context",
"content": " method: \"GET\",",
"newLineNumber": 5,
"oldLineNumber": 5
},
{
"type": "deletion",
"content": " path: `/api/ci-failures/${encodeURIComponent(context.params.id)}`",
"newLineNumber": null,
"oldLineNumber": 6
},
{
"type": "addition",
"content": " path: `/api/ci-failures/${encodeURIComponent(decodeRouteSegment(context.params.id))}`",
"newLineNumber": 6,
"oldLineNumber": null
},
{
"type": "context",
"content": " });",
"newLineNumber": 7,
"oldLineNumber": 7
},
{
"type": "context",
"content": "}",
"newLineNumber": 8,
"oldLineNumber": 8
},
{
"type": "addition",
"content": "",
"newLineNumber": 9,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function decodeRouteSegment(value: string): string {",
"newLineNumber": 10,
"oldLineNumber": null
},
{
"type": "addition",
"content": " try {",
"newLineNumber": 11,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return decodeURIComponent(value);",
"newLineNumber": 12,
"oldLineNumber": null
},
{
"type": "addition",
"content": " } catch {",
"newLineNumber": 13,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return value;",
"newLineNumber": 14,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 15,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 16,
"oldLineNumber": null
}
],
"newStart": 3,
"oldStart": 3,
"newLineCount": 14,
"oldLineCount": 6,
"sectionHeader": "import { forwardDashboardApiMutation } from \"../../../../lib/dashboard-api-proxy"
}
],
"patch": "@@ -3,6 +3,14 @@ import { forwardDashboardApiMutation } from \"../../../../lib/dashboard-api-proxy\n export async function GET(_request: Request, context: { params: { id: string } }): Promise<Response> {\n return forwardDashboardApiMutation({\n method: \"GET\",\n- path: `/api/ci-failures/${encodeURIComponent(context.params.id)}`\n+ path: `/api/ci-failures/${encodeURIComponent(decodeRouteSegment(context.params.id))}`\n });\n }\n+\n+function decodeRouteSegment(value: string): string {\n+ try {\n+ return decodeURIComponent(value);\n+ } catch {\n+ return value;\n+ }\n+}",
"status": "modified",
"language": "typescript",
"additions": 9,
"deletions": 1,
"sizeBytes": 479,
"previousPath": null,
"changedNewLines": [
6,
9,
10,
11,
12,
13,
14,
15,
16
],
"headContentSha256": "ffa9de49f100e1ba291c0e8acdbe0ded4fbe201593c9085719466842b612a29d"
},
{
"path": "apps/web/app/ci-failures/[id]/loading.tsx",
"hunks": [
{
"lines": [
{
"type": "addition",
"content": "import { DashboardShell } from \"../../../components/dashboard/dashboard-shell\";",
"newLineNumber": 1,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { CiFailureDetailView } from \"../../../components/dashboard/ci-failures-view\";",
"newLineNumber": 2,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 3,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export default function CiFailureDetailLoading() {",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 5,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <DashboardShell activeItem=\"CI Failures\">",
"newLineNumber": 6,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <CiFailureDetailView state={{ status: \"loading\" }} />",
"newLineNumber": 7,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </DashboardShell>",
"newLineNumber": 8,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 9,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 10,
"oldLineNumber": null
}
],
"newStart": 1,
"oldStart": 0,
"newLineCount": 10,
"oldLineCount": 0,
"sectionHeader": ""
}
],
"patch": "@@ -0,0 +1,10 @@\n+import { DashboardShell } from \"../../../components/dashboard/dashboard-shell\";\n+import { CiFailureDetailView } from \"../../../components/dashboard/ci-failures-view\";\n+\n+export default function CiFailureDetailLoading() {\n+ return (\n+ <DashboardShell activeItem=\"CI Failures\">\n+ <CiFailureDetailView state={{ status: \"loading\" }} />\n+ </DashboardShell>\n+ );\n+}",
"status": "added",
"language": "typescript",
"additions": 10,
"deletions": 0,
"sizeBytes": 364,
"previousPath": null,
"changedNewLines": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
],
"headContentSha256": "9a6433590ed5736c95f9e4ec7ede588ef809f7a50a1506fbc0641c7aa6a92c64"
},
{
"path": "apps/web/app/ci-failures/[id]/page.tsx",
"hunks": [
{
"lines": [
{
"type": "addition",
"content": "import { DashboardShell } from \"../../../components/dashboard/dashboard-shell\";",
"newLineNumber": 1,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { CiFailureDetailView } from \"../../../components/dashboard/ci-failures-view\";",
"newLineNumber": 2,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { loadCiFailureDetailState } from \"../../../lib/dashboard-data\";",
"newLineNumber": 3,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export const dynamic = \"force-dynamic\";",
"newLineNumber": 5,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 6,
"oldLineNumber": null
},
{
"type": "addition",
"content": "interface CiFailureDetailPageProps {",
"newLineNumber": 7,
"oldLineNumber": null
},
{
"type": "addition",
"content": " params: {",
"newLineNumber": 8,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: string;",
"newLineNumber": 9,
"oldLineNumber": null
},
{
"type": "addition",
"content": " };",
"newLineNumber": 10,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 11,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 12,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export default async function CiFailureDetailPage({ params }: CiFailureDetailPageProps) {",
"newLineNumber": 13,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const state = await loadCiFailureDetailState(params.id);",
"newLineNumber": 14,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 15,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 16,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <DashboardShell activeItem=\"CI Failures\">",
"newLineNumber": 17,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <CiFailureDetailView state={state} />",
"newLineNumber": 18,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </DashboardShell>",
"newLineNumber": 19,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 20,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 21,
"oldLineNumber": null
}
],
"newStart": 1,
"oldStart": 0,
"newLineCount": 21,
"oldLineCount": 0,
"sectionHeader": ""
}
],
"patch": "@@ -0,0 +1,21 @@\n+import { DashboardShell } from \"../../../components/dashboard/dashboard-shell\";\n+import { CiFailureDetailView } from \"../../../components/dashboard/ci-failures-view\";\n+import { loadCiFailureDetailState } from \"../../../lib/dashboard-data\";\n+\n+export const dynamic = \"force-dynamic\";\n+\n+interface CiFailureDetailPageProps {\n+ params: {\n+ id: string;\n+ };\n+}\n+\n+export default async function CiFailureDetailPage({ params }: CiFailureDetailPageProps) {\n+ const state = await loadCiFailureDetailState(params.id);\n+\n+ return (\n+ <DashboardShell activeItem=\"CI Failures\">\n+ <CiFailureDetailView state={state} />\n+ </DashboardShell>\n+ );\n+}",
"status": "added",
"language": "typescript",
"additions": 21,
"deletions": 0,
"sizeBytes": 633,
"previousPath": null,
"changedNewLines": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21
],
"headContentSha256": "d8e91b853e15e943bd733e4fa0fcbe6ffacc6ff56bfcbae09a88e4f109482b36"
},
{
"path": "apps/web/app/ci-failures/loading.tsx",
"hunks": [
{
"lines": [
{
"type": "addition",
"content": "import { DashboardShell } from \"../../components/dashboard/dashboard-shell\";",
"newLineNumber": 1,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { CiFailuresView } from \"../../components/dashboard/ci-failures-view\";",
"newLineNumber": 2,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 3,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export default function CiFailuresLoading() {",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 5,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <DashboardShell activeItem=\"CI Failures\">",
"newLineNumber": 6,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <CiFailuresView state={{ status: \"loading\" }} />",
"newLineNumber": 7,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </DashboardShell>",
"newLineNumber": 8,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 9,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 10,
"oldLineNumber": null
}
],
"newStart": 1,
"oldStart": 0,
"newLineCount": 10,
"oldLineCount": 0,
"sectionHeader": ""
}
],
"patch": "@@ -0,0 +1,10 @@\n+import { DashboardShell } from \"../../components/dashboard/dashboard-shell\";\n+import { CiFailuresView } from \"../../components/dashboard/ci-failures-view\";\n+\n+export default function CiFailuresLoading() {\n+ return (\n+ <DashboardShell activeItem=\"CI Failures\">\n+ <CiFailuresView state={{ status: \"loading\" }} />\n+ </DashboardShell>\n+ );\n+}",
"status": "added",
"language": "typescript",
"additions": 10,
"deletions": 0,
"sizeBytes": 343,
"previousPath": null,
"changedNewLines": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
],
"headContentSha256": "bd7a2b3fa36c95f5dd06c2700ef02d2a4a5fd473ae442dc245b78ca9f67994a6"
},
{
"path": "apps/web/app/ci-failures/page.tsx",
"hunks": [
{
"lines": [
{
"type": "addition",
"content": "import { DashboardShell } from \"../../components/dashboard/dashboard-shell\";",
"newLineNumber": 1,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { CiFailuresView } from \"../../components/dashboard/ci-failures-view\";",
"newLineNumber": 2,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { loadCiFailuresState } from \"../../lib/dashboard-data\";",
"newLineNumber": 3,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export const dynamic = \"force-dynamic\";",
"newLineNumber": 5,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 6,
"oldLineNumber": null
},
{
"type": "addition",
"content": "interface CiFailuresPageProps {",
"newLineNumber": 7,
"oldLineNumber": null
},
{
"type": "addition",
"content": " searchParams?: Record<string, string | string[] | undefined>;",
"newLineNumber": 8,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 9,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 10,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export default async function CiFailuresPage({ searchParams = {} }: CiFailuresPageProps) {",
"newLineNumber": 11,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const state = await loadCiFailuresState(searchParams);",
"newLineNumber": 12,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 13,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 14,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <DashboardShell activeItem=\"CI Failures\">",
"newLineNumber": 15,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <CiFailuresView state={state} />",
"newLineNumber": 16,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </DashboardShell>",
"newLineNumber": 17,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 18,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 19,
"oldLineNumber": null
}
],
"newStart": 1,
"oldStart": 0,
"newLineCount": 19,
"oldLineCount": 0,
"sectionHeader": ""
}
],
"patch": "@@ -0,0 +1,19 @@\n+import { DashboardShell } from \"../../components/dashboard/dashboard-shell\";\n+import { CiFailuresView } from \"../../components/dashboard/ci-failures-view\";\n+import { loadCiFailuresState } from \"../../lib/dashboard-data\";\n+\n+export const dynamic = \"force-dynamic\";\n+\n+interface CiFailuresPageProps {\n+ searchParams?: Record<string, string | string[] | undefined>;\n+}\n+\n+export default async function CiFailuresPage({ searchParams = {} }: CiFailuresPageProps) {\n+ const state = await loadCiFailuresState(searchParams);\n+\n+ return (\n+ <DashboardShell activeItem=\"CI Failures\">\n+ <CiFailuresView state={state} />\n+ </DashboardShell>\n+ );\n+}",
"status": "added",
"language": "typescript",
"additions": 19,
"deletions": 0,
"sizeBytes": 634,
"previousPath": null,
"changedNewLines": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19
],
"headContentSha256": "4cd74216b8a84fa28b23a68301b5cf12cd445e3ba5bcf76d47d1800f645bfab2"
},
{
"path": "apps/web/components/dashboard/ci-failures-view.tsx",
"hunks": [
{
"lines": [
{
"type": "addition",
"content": "import React from \"react\";",
"newLineNumber": 1,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import {",
"newLineNumber": 2,
"oldLineNumber": null
},
{
"type": "addition",
"content": " REVIEW_RUN_STATUSES,",
"newLineNumber": 3,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type CiFailureDetailResponse,",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type CiFailureFailedJob,",
"newLineNumber": 5,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type CiFailureListFilters,",
"newLineNumber": 6,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type CiFailureListItem,",
"newLineNumber": 7,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type CiFailureListResponse,",
"newLineNumber": 8,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type ReviewRunArtifact,",
"newLineNumber": 9,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type ReviewRunLogExcerpt",
"newLineNumber": 10,
"oldLineNumber": null
},
{
"type": "addition",
"content": "} from \"@firmcode/shared\";",
"newLineNumber": 11,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import type { ViewState } from \"../../lib/view-state\";",
"newLineNumber": 12,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { formatDateTime } from \"./format\";",
"newLineNumber": 13,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { StatusBadge } from \"./status-badge\";",
"newLineNumber": 14,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 15,
"oldLineNumber": null
},
{
"type": "addition",
"content": "interface CiFailuresViewProps {",
"newLineNumber": 16,
"oldLineNumber": null
},
{
"type": "addition",
"content": " state: ViewState<CiFailureListResponse>;",
"newLineNumber": 17,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 18,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 19,
"oldLineNumber": null
},
{
"type": "addition",
"content": "interface CiFailureDetailViewProps {",
"newLineNumber": 20,
"oldLineNumber": null
},
{
"type": "addition",
"content": " state: ViewState<CiFailureDetailResponse>;",
"newLineNumber": 21,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 22,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 23,
"oldLineNumber": null
},
{
"type": "addition",
"content": "type CollapsedLogExcerpt = ReviewRunLogExcerpt & { collapsed: true };",
"newLineNumber": 24,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 25,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export function CiFailuresView({ state }: CiFailuresViewProps) {",
"newLineNumber": 26,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const filters = getStateFilters(state);",
"newLineNumber": 27,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 28,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 29,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"space-y-4\">",
"newLineNumber": 30,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div>",
"newLineNumber": 31,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"text-sm font-medium text-accent\">CI Failures</p>",
"newLineNumber": 32,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h1 className=\"mt-1 text-2xl font-semibold tracking-normal text-primary\">Broken checks queue</h1>",
"newLineNumber": 33,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-2 max-w-2xl text-sm leading-6 text-secondary\">",
"newLineNumber": 34,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Review failed workflows by repository, pull request, root cause, flaky suspicion, and redacted evidence.",
"newLineNumber": 35,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </p>",
"newLineNumber": 36,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 37,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <CiFailureFilters filters={filters} />",
"newLineNumber": 38,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {state.status === \"loading\" ? <CiFailuresLoadingState /> : null}",
"newLineNumber": 39,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {state.status === \"error\" ? <CiFailuresErrorState message={state.message} /> : null}",
"newLineNumber": 40,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {state.status === \"empty\" ? <CiFailuresEmptyState /> : null}",
"newLineNumber": 41,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {state.status === \"populated\" ? <CiFailuresQueue data={state.data} /> : null}",
"newLineNumber": 42,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 43,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 44,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 45,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 46,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export function CiFailureDetailView({ state }: CiFailureDetailViewProps) {",
"newLineNumber": 47,
"oldLineNumber": null
},
{
"type": "addition",
"content": " if (state.status === \"loading\") {",
"newLineNumber": 48,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return <CiFailureDetailLoadingState />;",
"newLineNumber": 49,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 50,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 51,
"oldLineNumber": null
},
{
"type": "addition",
"content": " if (state.status === \"error\") {",
"newLineNumber": 52,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return <CiFailuresErrorState message={state.message} />;",
"newLineNumber": 53,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 54,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 55,
"oldLineNumber": null
},
{
"type": "addition",
"content": " if (state.status === \"empty\") {",
"newLineNumber": 56,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return <CiFailuresErrorState message=\"The CI failure could not be found.\" />;",
"newLineNumber": 57,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 58,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 59,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return <CiFailureDetailContent detail={state.data} />;",
"newLineNumber": 60,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 61,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 62,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function getStateFilters(state: ViewState<CiFailureListResponse>): CiFailureListFilters {",
"newLineNumber": 63,
"oldLineNumber": null
},
{
"type": "addition",
"content": " if (state.status === \"populated\") {",
"newLineNumber": 64,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return state.data.filters;",
"newLineNumber": 65,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 66,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 67,
"oldLineNumber": null
},
{
"type": "addition",
"content": " if (state.status === \"empty\" && state.data !== undefined) {",
"newLineNumber": 68,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return state.data.filters;",
"newLineNumber": 69,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 70,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 71,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return {};",
"newLineNumber": 72,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 73,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 74,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailureFilters({ filters }: { filters: CiFailureListFilters }) {",
"newLineNumber": 75,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 76,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <form className=\"grid gap-3 rounded-lg border border-border bg-surface p-4 md:grid-cols-7\" action=\"/ci-failures\">",
"newLineNumber": 77,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <label className=\"flex flex-col gap-1 text-sm font-medium text-primary md:col-span-2\">",
"newLineNumber": 78,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Repository",
"newLineNumber": 79,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <input",
"newLineNumber": 80,
"oldLineNumber": null
},
{
"type": "addition",
"content": " className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"",
"newLineNumber": 81,
"oldLineNumber": null
},
{
"type": "addition",
"content": " defaultValue={filters.repository ?? \"\"}",
"newLineNumber": 82,
"oldLineNumber": null
},
{
"type": "addition",
"content": " name=\"repository\"",
"newLineNumber": 83,
"oldLineNumber": null
},
{
"type": "addition",
"content": " placeholder=\"owner/repo\"",
"newLineNumber": 84,
"oldLineNumber": null
},
{
"type": "addition",
"content": " />",
"newLineNumber": 85,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </label>",
"newLineNumber": 86,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <FilterSelect label=\"Run status\" name=\"status\" options={REVIEW_RUN_STATUSES} value={filters.status} />",
"newLineNumber": 87,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">",
"newLineNumber": 88,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Flaky suspected",
"newLineNumber": 89,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <select",
"newLineNumber": 90,
"oldLineNumber": null
},
{
"type": "addition",
"content": " className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"",
"newLineNumber": 91,
"oldLineNumber": null
},
{
"type": "addition",
"content": " defaultValue={filters.flaky === undefined ? \"\" : String(filters.flaky)}",
"newLineNumber": 92,
"oldLineNumber": null
},
{
"type": "addition",
"content": " name=\"flaky\"",
"newLineNumber": 93,
"oldLineNumber": null
},
{
"type": "addition",
"content": " >",
"newLineNumber": 94,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <option value=\"\">Any</option>",
"newLineNumber": 95,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <option value=\"true\">Suspected</option>",
"newLineNumber": 96,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <option value=\"false\">Not suspected</option>",
"newLineNumber": 97,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </select>",
"newLineNumber": 98,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </label>",
"newLineNumber": 99,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">",
"newLineNumber": 100,
"oldLineNumber": null
},
{
"type": "addition",
"content": " From",
"newLineNumber": 101,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <input",
"newLineNumber": 102,
"oldLineNumber": null
},
{
"type": "addition",
"content": " className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"",
"newLineNumber": 103,
"oldLineNumber": null
},
{
"type": "addition",
"content": " defaultValue={filters.dateFrom ?? \"\"}",
"newLineNumber": 104,
"oldLineNumber": null
},
{
"type": "addition",
"content": " name=\"dateFrom\"",
"newLineNumber": 105,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type=\"date\"",
"newLineNumber": 106,
"oldLineNumber": null
},
{
"type": "addition",
"content": " />",
"newLineNumber": 107,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </label>",
"newLineNumber": 108,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">",
"newLineNumber": 109,
"oldLineNumber": null
},
{
"type": "addition",
"content": " To",
"newLineNumber": 110,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <input",
"newLineNumber": 111,
"oldLineNumber": null
},
{
"type": "addition",
"content": " className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"",
"newLineNumber": 112,
"oldLineNumber": null
},
{
"type": "addition",
"content": " defaultValue={filters.dateTo ?? \"\"}",
"newLineNumber": 113,
"oldLineNumber": null
},
{
"type": "addition",
"content": " name=\"dateTo\"",
"newLineNumber": 114,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type=\"date\"",
"newLineNumber": 115,
"oldLineNumber": null
},
{
"type": "addition",
"content": " />",
"newLineNumber": 116,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </label>",
"newLineNumber": 117,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"flex items-end\">",
"newLineNumber": 118,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <button className=\"w-full rounded-md bg-accent px-3 py-2 text-sm font-medium text-white\" type=\"submit\">",
"newLineNumber": 119,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Apply filters",
"newLineNumber": 120,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </button>",
"newLineNumber": 121,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 122,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </form>",
"newLineNumber": 123,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 124,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 125,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 126,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function FilterSelect({",
"newLineNumber": 127,
"oldLineNumber": null
},
{
"type": "addition",
"content": " label,",
"newLineNumber": 128,
"oldLineNumber": null
},
{
"type": "addition",
"content": " name,",
"newLineNumber": 129,
"oldLineNumber": null
},
{
"type": "addition",
"content": " options,",
"newLineNumber": 130,
"oldLineNumber": null
},
{
"type": "addition",
"content": " value",
"newLineNumber": 131,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}: {",
"newLineNumber": 132,
"oldLineNumber": null
},
{
"type": "addition",
"content": " label: string;",
"newLineNumber": 133,
"oldLineNumber": null
},
{
"type": "addition",
"content": " name: string;",
"newLineNumber": 134,
"oldLineNumber": null
},
{
"type": "addition",
"content": " options: readonly string[];",
"newLineNumber": 135,
"oldLineNumber": null
},
{
"type": "addition",
"content": " value: string | undefined;",
"newLineNumber": 136,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}) {",
"newLineNumber": 137,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 138,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">",
"newLineNumber": 139,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {label}",
"newLineNumber": 140,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <select className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\" defaultValue={value ?? \"\"} name={name}>",
"newLineNumber": 141,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <option value=\"\">Any</option>",
"newLineNumber": 142,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {options.map((option) => (",
"newLineNumber": 143,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <option key={option} value={option}>",
"newLineNumber": 144,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {formatLabel(option)}",
"newLineNumber": 145,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </option>",
"newLineNumber": 146,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 147,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </select>",
"newLineNumber": 148,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </label>",
"newLineNumber": 149,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 150,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 151,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 152,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailuresQueue({ data }: { data: CiFailureListResponse }) {",
"newLineNumber": 153,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 154,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"overflow-hidden rounded-lg border border-border bg-surface\">",
"newLineNumber": 155,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"flex flex-col gap-1 border-b border-border px-4 py-3 sm:flex-row sm:items-center sm:justify-between\">",
"newLineNumber": 156,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h2 className=\"text-sm font-semibold text-primary\">Failed workflows and jobs</h2>",
"newLineNumber": 157,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"font-mono text-xs text-secondary\">",
"newLineNumber": 158,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Showing {data.pagination.returned} of {data.pagination.limit}",
"newLineNumber": 159,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </p>",
"newLineNumber": 160,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 161,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"grid divide-y divide-border md:hidden\">",
"newLineNumber": 162,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {data.ciFailures.map((failure) => (",
"newLineNumber": 163,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <CiFailureCard key={failure.id} failure={failure} />",
"newLineNumber": 164,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 165,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 166,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"hidden overflow-x-auto md:block\">",
"newLineNumber": 167,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <table className=\"min-w-full divide-y divide-border text-sm\">",
"newLineNumber": 168,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <thead className=\"bg-subtle text-left text-xs font-semibold uppercase text-secondary\">",
"newLineNumber": 169,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <tr>",
"newLineNumber": 170,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Repository</th>",
"newLineNumber": 171,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">PR</th>",
"newLineNumber": 172,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Failed workflow/job</th>",
"newLineNumber": 173,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Root cause summary</th>",
"newLineNumber": 174,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Flaky suspected</th>",
"newLineNumber": 175,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Suggested fix</th>",
"newLineNumber": 176,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Created</th>",
"newLineNumber": 177,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </tr>",
"newLineNumber": 178,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </thead>",
"newLineNumber": 179,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <tbody className=\"divide-y divide-border\">",
"newLineNumber": 180,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {data.ciFailures.map((failure) => (",
"newLineNumber": 181,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <CiFailureRow key={failure.id} failure={failure} />",
"newLineNumber": 182,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 183,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </tbody>",
"newLineNumber": 184,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </table>",
"newLineNumber": 185,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 186,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 187,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 188,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 189,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 190,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailureRow({ failure }: { failure: CiFailureListItem }) {",
"newLineNumber": 191,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 192,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <tr>",
"newLineNumber": 193,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3\">",
"newLineNumber": 194,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"font-medium text-primary\">{failure.repositoryFullName}</div>",
"newLineNumber": 195,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"mt-1 block font-mono text-xs text-accent\" href={`/review-runs/${encodeURIComponent(failure.reviewRunId)}`}>",
"newLineNumber": 196,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {failure.reviewRunId.slice(0, 8)}",
"newLineNumber": 197,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 198,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </td>",
"newLineNumber": 199,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3\">",
"newLineNumber": 200,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"font-medium text-accent\" href={`/pull-requests/${encodeURIComponent(failure.pullRequestId)}`}>",
"newLineNumber": 201,
"oldLineNumber": null
},
{
"type": "addition",
"content": " #{failure.pullRequestNumber}",
"newLineNumber": 202,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 203,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-1 max-w-xs truncate text-xs text-secondary\">{failure.pullRequestTitle}</div>",
"newLineNumber": 204,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </td>",
"newLineNumber": 205,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3\">",
"newLineNumber": 206,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <FailedJobSummary job={failure.failedJob} />",
"newLineNumber": 207,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </td>",
"newLineNumber": 208,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3 max-w-sm text-sm leading-5 text-primary\">",
"newLineNumber": 209,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"text-accent\" href={`/ci-failures/${encodeURIComponent(failure.id)}`}>",
"newLineNumber": 210,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {failure.rootCauseSummary}",
"newLineNumber": 211,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 212,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </td>",
"newLineNumber": 213,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3\">",
"newLineNumber": 214,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <FlakyBadge suspected={failure.flakySuspected} />",
"newLineNumber": 215,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </td>",
"newLineNumber": 216,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3 max-w-xs text-sm leading-5 text-secondary\">{failure.suggestedFix ?? \"No suggested fix stored.\"}</td>",
"newLineNumber": 217,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3 text-xs text-secondary\">{formatDateTime(failure.createdAt)}</td>",
"newLineNumber": 218,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </tr>",
"newLineNumber": 219,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 220,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 221,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 222,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailureCard({ failure }: { failure: CiFailureListItem }) {",
"newLineNumber": 223,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 224,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <article className=\"p-4\">",
"newLineNumber": 225,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"flex items-start justify-between gap-3\">",
"newLineNumber": 226,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"min-w-0\">",
"newLineNumber": 227,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"font-medium text-accent\" href={`/ci-failures/${encodeURIComponent(failure.id)}`}>",
"newLineNumber": 228,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {failure.failedJob.jobName}",
"newLineNumber": 229,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 230,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-1 truncate text-sm text-secondary\">",
"newLineNumber": 231,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {failure.repositoryFullName} / PR #{failure.pullRequestNumber}",
"newLineNumber": 232,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </p>",
"newLineNumber": 233,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 234,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <FlakyBadge suspected={failure.flakySuspected} />",
"newLineNumber": 235,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 236,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-3 text-sm leading-6 text-primary\">{failure.rootCauseSummary}</p>",
"newLineNumber": 237,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <dl className=\"mt-3 grid grid-cols-2 gap-3 text-sm\">",
"newLineNumber": 238,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Workflow\" value={failure.failedJob.workflowName ?? \"Unknown\"} />",
"newLineNumber": 239,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Created\" value={formatDateTime(failure.createdAt)} />",
"newLineNumber": 240,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Suggested fix\" value={failure.suggestedFix ?? \"No suggested fix stored.\"} />",
"newLineNumber": 241,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Status\" value={formatLabel(failure.status)} />",
"newLineNumber": 242,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </dl>",
"newLineNumber": 243,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </article>",
"newLineNumber": 244,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 245,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 246,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 247,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailureDetailContent({ detail }: { detail: CiFailureDetailResponse }) {",
"newLineNumber": 248,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 249,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"space-y-5\">",
"newLineNumber": 250,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-border bg-surface p-5\">",
"newLineNumber": 251,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"flex flex-col gap-3 md:flex-row md:items-start md:justify-between\">",
"newLineNumber": 252,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"min-w-0\">",
"newLineNumber": 253,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"text-sm font-medium text-accent\">CI Failure</p>",
"newLineNumber": 254,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h1 className=\"mt-1 text-2xl font-semibold tracking-normal text-primary\">{detail.failedJob.jobName}</h1>",
"newLineNumber": 255,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-2 text-sm text-secondary\">",
"newLineNumber": 256,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {detail.repositoryFullName} / PR #{detail.pullRequestNumber}",
"newLineNumber": 257,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </p>",
"newLineNumber": 258,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 259,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"flex flex-wrap items-center gap-2 md:justify-end\">",
"newLineNumber": 260,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <StatusBadge status={detail.status} />",
"newLineNumber": 261,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <FlakyBadge suspected={detail.flakySuspected} />",
"newLineNumber": 262,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"rounded-md border border-border px-3 py-2 text-sm font-medium text-primary\" href=\"/ci-failures\">",
"newLineNumber": 263,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Back to queue",
"newLineNumber": 264,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 265,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 266,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 267,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 268,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 269,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"grid gap-3 md:grid-cols-4\" aria-label=\"CI failure metrics\">",
"newLineNumber": 270,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metric label=\"Failed jobs\" value={String(detail.failedJobs.length)} />",
"newLineNumber": 271,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metric label=\"Redacted excerpts\" value={String(detail.logExcerpts.length)} />",
"newLineNumber": 272,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metric label=\"Suggested fixes\" value={String(detail.suggestedFixes.length)} />",
"newLineNumber": 273,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metric label=\"Created\" value={formatDateTime(detail.createdAt)} />",
"newLineNumber": 274,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 275,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 276,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"grid gap-5 xl:grid-cols-[minmax(0,1fr)_340px]\">",
"newLineNumber": 277,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"space-y-5\">",
"newLineNumber": 278,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <SummarySection detail={detail} />",
"newLineNumber": 279,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <SuggestedFixesSection fixes={detail.suggestedFixes} fallback={detail.suggestedFix} />",
"newLineNumber": 280,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <FailedJobsSection jobs={detail.failedJobs} />",
"newLineNumber": 281,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <LogExcerptsSection excerpts={detail.logExcerpts} />",
"newLineNumber": 282,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <UnavailableLogsSection notes={detail.unavailableLogNotes} />",
"newLineNumber": 283,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 284,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <RelatedLinksPanel detail={detail} />",
"newLineNumber": 285,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 286,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 287,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 288,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 289,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 290,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function SummarySection({ detail }: { detail: CiFailureDetailResponse }) {",
"newLineNumber": 291,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 292,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-border bg-surface p-4\">",
"newLineNumber": 293,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <SectionHeader title=\"Failure Summary\" subtitle=\"Stored CI explanation summary\" />",
"newLineNumber": 294,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-3 text-sm leading-6 text-primary\">{detail.rootCauseSummary}</p>",
"newLineNumber": 295,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h3 className=\"mt-4 text-sm font-semibold text-primary\">Likely root cause</h3>",
"newLineNumber": 296,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-2 text-sm leading-6 text-primary\">{detail.rootCause}</p>",
"newLineNumber": 297,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 298,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 299,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 300,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 301,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function SuggestedFixesSection({",
"newLineNumber": 302,
"oldLineNumber": null
},
{
"type": "addition",
"content": " fallback,",
"newLineNumber": 303,
"oldLineNumber": null
},
{
"type": "addition",
"content": " fixes",
"newLineNumber": 304,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}: {",
"newLineNumber": 305,
"oldLineNumber": null
},
{
"type": "addition",
"content": " fallback: string | null;",
"newLineNumber": 306,
"oldLineNumber": null
},
{
"type": "addition",
"content": " fixes: CiFailureDetailResponse[\"suggestedFixes\"];",
"newLineNumber": 307,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}) {",
"newLineNumber": 308,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const visibleFixes = fixes.length > 0 ? fixes : fallback === null ? [] : [{ id: \"fallback-fix\", text: fallback }];",
"newLineNumber": 309,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 310,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 311,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-border bg-surface p-4\">",
"newLineNumber": 312,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <SectionHeader title=\"Suggested Fixes\" subtitle={`${visibleFixes.length} stored recommendations`} />",
"newLineNumber": 313,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {visibleFixes.length === 0 ? <p className=\"mt-3 text-sm text-secondary\">No suggested fixes were stored for this failure.</p> : null}",
"newLineNumber": 314,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <ol className=\"mt-3 grid gap-2\">",
"newLineNumber": 315,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {visibleFixes.map((fix) => (",
"newLineNumber": 316,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <li key={fix.id} className=\"rounded-md border border-border bg-subtle p-3 text-sm leading-6 text-primary\">",
"newLineNumber": 317,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {fix.text}",
"newLineNumber": 318,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </li>",
"newLineNumber": 319,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 320,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </ol>",
"newLineNumber": 321,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 322,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 323,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 324,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 325,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function FailedJobsSection({ jobs }: { jobs: CiFailureFailedJob[] }) {",
"newLineNumber": 326,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 327,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"overflow-hidden rounded-lg border border-border bg-surface\">",
"newLineNumber": 328,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <SectionHeader title=\"Failed Jobs\" subtitle={`${jobs.length} failed workflow jobs`} padded />",
"newLineNumber": 329,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"overflow-x-auto\">",
"newLineNumber": 330,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <table className=\"min-w-full divide-y divide-border text-sm\">",
"newLineNumber": 331,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <thead className=\"bg-subtle text-left text-xs font-semibold uppercase text-secondary\">",
"newLineNumber": 332,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <tr>",
"newLineNumber": 333,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Workflow/job</th>",
"newLineNumber": 334,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Step</th>",
"newLineNumber": 335,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Category</th>",
"newLineNumber": 336,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Conclusion</th>",
"newLineNumber": 337,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <th className=\"px-4 py-3\">Provider link</th>",
"newLineNumber": 338,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </tr>",
"newLineNumber": 339,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </thead>",
"newLineNumber": 340,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <tbody className=\"divide-y divide-border\">",
"newLineNumber": 341,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {jobs.map((job) => (",
"newLineNumber": 342,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <tr key={job.id}>",
"newLineNumber": 343,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3\">",
"newLineNumber": 344,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <FailedJobSummary job={job} />",
"newLineNumber": 345,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </td>",
"newLineNumber": 346,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3 text-primary\">{job.stepName ?? \"Unknown step\"}</td>",
"newLineNumber": 347,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3 text-primary\">{formatLabel(job.category)}</td>",
"newLineNumber": 348,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3 text-primary\">{formatLabel(job.conclusion)}</td>",
"newLineNumber": 349,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <td className=\"px-4 py-3\">",
"newLineNumber": 350,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {job.detailsUrl === null ? (",
"newLineNumber": 351,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <span className=\"text-secondary\">Not available</span>",
"newLineNumber": 352,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ) : (",
"newLineNumber": 353,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"text-accent\" href={job.detailsUrl}>",
"newLineNumber": 354,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Check run",
"newLineNumber": 355,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 356,
"oldLineNumber": null
},
{
"type": "addition",
"content": " )}",
"newLineNumber": 357,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </td>",
"newLineNumber": 358,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </tr>",
"newLineNumber": 359,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 360,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </tbody>",
"newLineNumber": 361,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </table>",
"newLineNumber": 362,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 363,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 364,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 365,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 366,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 367,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function LogExcerptsSection({ excerpts }: { excerpts: CollapsedLogExcerpt[] }) {",
"newLineNumber": 368,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 369,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-border bg-surface p-4\">",
"newLineNumber": 370,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <SectionHeader title=\"Redacted Log Excerpts\" subtitle={`${excerpts.length} collapsed excerpts available by default`} />",
"newLineNumber": 371,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-3 grid gap-3\">",
"newLineNumber": 372,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {excerpts.length === 0 ? <p className=\"text-sm text-secondary\">No redacted log excerpts were stored for this failure.</p> : null}",
"newLineNumber": 373,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {excerpts.map((excerpt) => (",
"newLineNumber": 374,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <details key={excerpt.id} className=\"rounded-md border border-border bg-subtle p-3\">",
"newLineNumber": 375,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <summary className=\"cursor-pointer text-sm font-medium text-accent\">",
"newLineNumber": 376,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {excerpt.title} ({excerpt.redacted ? \"redacted\" : \"metadata only\"})",
"newLineNumber": 377,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {excerpt.truncated ? \" / truncated\" : \"\"}",
"newLineNumber": 378,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </summary>",
"newLineNumber": 379,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <pre className=\"mt-3 max-h-72 overflow-auto rounded-md border border-border bg-slate-950 p-3 text-xs leading-5 text-slate-100\">",
"newLineNumber": 380,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {excerpt.excerpt}",
"newLineNumber": 381,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </pre>",
"newLineNumber": 382,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </details>",
"newLineNumber": 383,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 384,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 385,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 386,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 387,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 388,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 389,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function UnavailableLogsSection({ notes }: { notes: unknown[] }) {",
"newLineNumber": 390,
"oldLineNumber": null
},
{
"type": "addition",
"content": " if (notes.length === 0) {",
"newLineNumber": 391,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return null;",
"newLineNumber": 392,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 393,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 394,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 395,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-amber-200 bg-amber-50 p-4\">",
"newLineNumber": 396,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h2 className=\"text-base font-semibold text-amber-800\">Unavailable Logs</h2>",
"newLineNumber": 397,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <ul className=\"mt-3 grid gap-2 text-sm leading-6 text-amber-800\">",
"newLineNumber": 398,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {notes.map((note, index) => (",
"newLineNumber": 399,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <li key={index}>{typeof note === \"string\" ? note : JSON.stringify(note)}</li>",
"newLineNumber": 400,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 401,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </ul>",
"newLineNumber": 402,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 403,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 404,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 405,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 406,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function RelatedLinksPanel({ detail }: { detail: CiFailureDetailResponse }) {",
"newLineNumber": 407,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 408,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <aside className=\"rounded-lg border border-border bg-surface p-4 xl:sticky xl:top-24 xl:self-start\">",
"newLineNumber": 409,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h2 className=\"text-base font-semibold text-primary\">Related Links</h2>",
"newLineNumber": 410,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <dl className=\"mt-4 grid gap-3 text-sm\">",
"newLineNumber": 411,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Repository\" value={detail.repositoryFullName} />",
"newLineNumber": 412,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Pull request\" value={`#${detail.pullRequestNumber} ${detail.pullRequestTitle}`} />",
"newLineNumber": 413,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Review run\" value={detail.relatedReviewRun.id.slice(0, 8)} monospace />",
"newLineNumber": 414,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <Metadata label=\"Run created\" value={formatDateTime(detail.relatedReviewRun.createdAt)} />",
"newLineNumber": 415,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </dl>",
"newLineNumber": 416,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-4 flex flex-wrap gap-2\">",
"newLineNumber": 417,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"rounded-md border border-border px-3 py-2 text-sm font-medium text-primary\" href={`/review-runs/${detail.relatedReviewRun.id}`}>",
"newLineNumber": 418,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Review run",
"newLineNumber": 419,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 420,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"rounded-md border border-border px-3 py-2 text-sm font-medium text-primary\" href={`/pull-requests/${detail.pullRequestId}`}>",
"newLineNumber": 421,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Pull request",
"newLineNumber": 422,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 423,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 424,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"mt-5 border-t border-border pt-4\">",
"newLineNumber": 425,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h3 className=\"text-sm font-semibold text-primary\">Artifacts</h3>",
"newLineNumber": 426,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-3 grid gap-3\">",
"newLineNumber": 427,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {detail.relatedArtifacts.map((artifact) => (",
"newLineNumber": 428,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <ArtifactLink key={artifact.id} artifact={artifact} />",
"newLineNumber": 429,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 430,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 431,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 432,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </aside>",
"newLineNumber": 433,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 434,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 435,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 436,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function ArtifactLink({ artifact }: { artifact: ReviewRunArtifact }) {",
"newLineNumber": 437,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 438,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"rounded-md border border-border bg-subtle p-3\">",
"newLineNumber": 439,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"flex items-center justify-between gap-3\">",
"newLineNumber": 440,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <span className=\"font-mono text-xs font-semibold text-primary\">{artifact.artifactType}</span>",
"newLineNumber": 441,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <span className=\"font-mono text-xs text-secondary\">{formatDateTime(artifact.createdAt)}</span>",
"newLineNumber": 442,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 443,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {artifact.rawAccessAllowed && artifact.rawAccessUrl !== null ? (",
"newLineNumber": 444,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <a className=\"mt-2 inline-flex rounded-md border border-border bg-surface px-2 py-1 text-xs font-medium text-accent\" href={artifact.rawAccessUrl}>",
"newLineNumber": 445,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Raw artifact",
"newLineNumber": 446,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </a>",
"newLineNumber": 447,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ) : (",
"newLineNumber": 448,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <button",
"newLineNumber": 449,
"oldLineNumber": null
},
{
"type": "addition",
"content": " className=\"mt-2 cursor-not-allowed rounded-md border border-border bg-surface px-2 py-1 text-xs font-medium text-secondary\"",
"newLineNumber": 450,
"oldLineNumber": null
},
{
"type": "addition",
"content": " type=\"button\"",
"newLineNumber": 451,
"oldLineNumber": null
},
{
"type": "addition",
"content": " disabled",
"newLineNumber": 452,
"oldLineNumber": null
},
{
"type": "addition",
"content": " >",
"newLineNumber": 453,
"oldLineNumber": null
},
{
"type": "addition",
"content": " Raw artifact restricted",
"newLineNumber": 454,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </button>",
"newLineNumber": 455,
"oldLineNumber": null
},
{
"type": "addition",
"content": " )}",
"newLineNumber": 456,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 457,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 458,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 459,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 460,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function FailedJobSummary({ job }: { job: CiFailureFailedJob }) {",
"newLineNumber": 461,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 462,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"min-w-0\">",
"newLineNumber": 463,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"font-medium text-primary\">{job.workflowName ?? \"Unknown workflow\"}</div>",
"newLineNumber": 464,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-1 truncate font-mono text-xs text-secondary\">{job.jobName}</div>",
"newLineNumber": 465,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 466,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 467,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 468,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 469,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function FlakyBadge({ suspected }: { suspected: boolean }) {",
"newLineNumber": 470,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const className = suspected",
"newLineNumber": 471,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ? \"border-amber-200 bg-amber-50 text-amber-700\"",
"newLineNumber": 472,
"oldLineNumber": null
},
{
"type": "addition",
"content": " : \"border-slate-200 bg-slate-100 text-slate-700\";",
"newLineNumber": 473,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 474,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 475,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <span className={`inline-flex rounded-md border px-2 py-1 text-xs font-medium ${className}`}>",
"newLineNumber": 476,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {suspected ? \"Suspected\" : \"Not suspected\"}",
"newLineNumber": 477,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </span>",
"newLineNumber": 478,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 479,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 480,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 481,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function Metric({ label, value }: { label: string; value: string }) {",
"newLineNumber": 482,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 483,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"rounded-lg border border-border bg-surface p-4\">",
"newLineNumber": 484,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"text-xs font-medium uppercase text-secondary\">{label}</p>",
"newLineNumber": 485,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-2 break-words font-mono text-base font-semibold leading-6 text-primary\">{value}</p>",
"newLineNumber": 486,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 487,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 488,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 489,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 490,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function Metadata({ label, value, monospace = false }: { label: string; value: string; monospace?: boolean }) {",
"newLineNumber": 491,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 492,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div>",
"newLineNumber": 493,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <dt className=\"text-xs font-medium uppercase text-secondary\">{label}</dt>",
"newLineNumber": 494,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <dd className={`mt-1 break-words text-primary ${monospace ? \"font-mono text-xs\" : \"text-sm\"}`}>{value}</dd>",
"newLineNumber": 495,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 496,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 497,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 498,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 499,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function SectionHeader({ padded = false, subtitle, title }: { padded?: boolean; subtitle: string; title: string }) {",
"newLineNumber": 500,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 501,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className={padded ? \"border-b border-border px-4 py-3\" : \"\"}>",
"newLineNumber": 502,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h2 className=\"text-base font-semibold text-primary\">{title}</h2>",
"newLineNumber": 503,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-1 text-sm text-secondary\">{subtitle}</p>",
"newLineNumber": 504,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 505,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 506,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 507,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 508,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailuresLoadingState() {",
"newLineNumber": 509,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 510,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-border bg-surface p-4\" aria-label=\"Loading CI failures\">",
"newLineNumber": 511,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"h-5 w-48 rounded bg-subtle\" />",
"newLineNumber": 512,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-4 grid gap-3\">",
"newLineNumber": 513,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {[\"one\", \"two\", \"three\", \"four\"].map((key) => (",
"newLineNumber": 514,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div key={key} className=\"h-16 rounded-md bg-subtle\" />",
"newLineNumber": 515,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 516,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 517,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 518,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 519,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 520,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 521,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailureDetailLoadingState() {",
"newLineNumber": 522,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 523,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"space-y-4\" aria-label=\"Loading CI failure detail\">",
"newLineNumber": 524,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"rounded-lg border border-border bg-surface p-5\">",
"newLineNumber": 525,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"h-6 w-64 rounded bg-subtle\" />",
"newLineNumber": 526,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-4 h-4 w-96 max-w-full rounded bg-subtle\" />",
"newLineNumber": 527,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 528,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"grid gap-3 md:grid-cols-4\">",
"newLineNumber": 529,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {[\"one\", \"two\", \"three\", \"four\"].map((key) => (",
"newLineNumber": 530,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div key={key} className=\"h-24 rounded-lg border border-border bg-surface p-4\">",
"newLineNumber": 531,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"h-4 w-24 rounded bg-subtle\" />",
"newLineNumber": 532,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <div className=\"mt-3 h-6 w-16 rounded bg-subtle\" />",
"newLineNumber": 533,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 534,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ))}",
"newLineNumber": 535,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 536,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </div>",
"newLineNumber": 537,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 538,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 539,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 540,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailuresErrorState({ message }: { message: string }) {",
"newLineNumber": 541,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 542,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-red-200 bg-red-50 p-4\">",
"newLineNumber": 543,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h2 className=\"text-sm font-semibold text-red-800\">CI failures could not be loaded</h2>",
"newLineNumber": 544,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-2 text-sm leading-6 text-red-700\">{message}</p>",
"newLineNumber": 545,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 546,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 547,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 548,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 549,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function CiFailuresEmptyState() {",
"newLineNumber": 550,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return (",
"newLineNumber": 551,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <section className=\"rounded-lg border border-border bg-surface p-6\">",
"newLineNumber": 552,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <h2 className=\"text-lg font-semibold text-primary\">No CI failures match these filters</h2>",
"newLineNumber": 553,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <p className=\"mt-2 max-w-xl text-sm leading-6 text-secondary\">",
"newLineNumber": 554,
"oldLineNumber": null
},
{
"type": "addition",
"content": " CI failures appear here after Firmcode stores redacted CI explanations for failed workflow jobs.",
"newLineNumber": 555,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </p>",
"newLineNumber": 556,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </section>",
"newLineNumber": 557,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 558,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 559,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 560,
"oldLineNumber": null
},
{
"type": "addition",
"content": "function formatLabel(value: string): string {",
"newLineNumber": 561,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return value",
"newLineNumber": 562,
"oldLineNumber": null
},
{
"type": "addition",
"content": " .split(\"_\")",
"newLineNumber": 563,
"oldLineNumber": null
},
{
"type": "addition",
"content": " .map((part) => part.charAt(0).toUpperCase() + part.slice(1))",
"newLineNumber": 564,
"oldLineNumber": null
},
{
"type": "addition",
"content": " .join(\" \");",
"newLineNumber": 565,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 566,
"oldLineNumber": null
}
],
"newStart": 1,
"oldStart": 0,
"newLineCount": 566,
"oldLineCount": 0,
"sectionHeader": ""
}
],
"patch": "@@ -0,0 +1,566 @@\n+import React from \"react\";\n+import {\n+ REVIEW_RUN_STATUSES,\n+ type CiFailureDetailResponse,\n+ type CiFailureFailedJob,\n+ type CiFailureListFilters,\n+ type CiFailureListItem,\n+ type CiFailureListResponse,\n+ type ReviewRunArtifact,\n+ type ReviewRunLogExcerpt\n+} from \"@firmcode/shared\";\n+import type { ViewState } from \"../../lib/view-state\";\n+import { formatDateTime } from \"./format\";\n+import { StatusBadge } from \"./status-badge\";\n+\n+interface CiFailuresViewProps {\n+ state: ViewState<CiFailureListResponse>;\n+}\n+\n+interface CiFailureDetailViewProps {\n+ state: ViewState<CiFailureDetailResponse>;\n+}\n+\n+type CollapsedLogExcerpt = ReviewRunLogExcerpt & { collapsed: true };\n+\n+export function CiFailuresView({ state }: CiFailuresViewProps) {\n+ const filters = getStateFilters(state);\n+\n+ return (\n+ <div className=\"space-y-4\">\n+ <div>\n+ <p className=\"text-sm font-medium text-accent\">CI Failures</p>\n+ <h1 className=\"mt-1 text-2xl font-semibold tracking-normal text-primary\">Broken checks queue</h1>\n+ <p className=\"mt-2 max-w-2xl text-sm leading-6 text-secondary\">\n+ Review failed workflows by repository, pull request, root cause, flaky suspicion, and redacted evidence.\n+ </p>\n+ </div>\n+ <CiFailureFilters filters={filters} />\n+ {state.status === \"loading\" ? <CiFailuresLoadingState /> : null}\n+ {state.status === \"error\" ? <CiFailuresErrorState message={state.message} /> : null}\n+ {state.status === \"empty\" ? <CiFailuresEmptyState /> : null}\n+ {state.status === \"populated\" ? <CiFailuresQueue data={state.data} /> : null}\n+ </div>\n+ );\n+}\n+\n+export function CiFailureDetailView({ state }: CiFailureDetailViewProps) {\n+ if (state.status === \"loading\") {\n+ return <CiFailureDetailLoadingState />;\n+ }\n+\n+ if (state.status === \"error\") {\n+ return <CiFailuresErrorState message={state.message} />;\n+ }\n+\n+ if (state.status === \"empty\") {\n+ return <CiFailuresErrorState message=\"The CI failure could not be found.\" />;\n+ }\n+\n+ return <CiFailureDetailContent detail={state.data} />;\n+}\n+\n+function getStateFilters(state: ViewState<CiFailureListResponse>): CiFailureListFilters {\n+ if (state.status === \"populated\") {\n+ return state.data.filters;\n+ }\n+\n+ if (state.status === \"empty\" && state.data !== undefined) {\n+ return state.data.filters;\n+ }\n+\n+ return {};\n+}\n+\n+function CiFailureFilters({ filters }: { filters: CiFailureListFilters }) {\n+ return (\n+ <form className=\"grid gap-3 rounded-lg border border-border bg-surface p-4 md:grid-cols-7\" action=\"/ci-failures\">\n+ <label className=\"flex flex-col gap-1 text-sm font-medium text-primary md:col-span-2\">\n+ Repository\n+ <input\n+ className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"\n+ defaultValue={filters.repository ?? \"\"}\n+ name=\"repository\"\n+ placeholder=\"owner/repo\"\n+ />\n+ </label>\n+ <FilterSelect label=\"Run status\" name=\"status\" options={REVIEW_RUN_STATUSES} value={filters.status} />\n+ <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">\n+ Flaky suspected\n+ <select\n+ className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"\n+ defaultValue={filters.flaky === undefined ? \"\" : String(filters.flaky)}\n+ name=\"flaky\"\n+ >\n+ <option value=\"\">Any</option>\n+ <option value=\"true\">Suspected</option>\n+ <option value=\"false\">Not suspected</option>\n+ </select>\n+ </label>\n+ <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">\n+ From\n+ <input\n+ className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"\n+ defaultValue={filters.dateFrom ?? \"\"}\n+ name=\"dateFrom\"\n+ type=\"date\"\n+ />\n+ </label>\n+ <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">\n+ To\n+ <input\n+ className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\"\n+ defaultValue={filters.dateTo ?? \"\"}\n+ name=\"dateTo\"\n+ type=\"date\"\n+ />\n+ </label>\n+ <div className=\"flex items-end\">\n+ <button className=\"w-full rounded-md bg-accent px-3 py-2 text-sm font-medium text-white\" type=\"submit\">\n+ Apply filters\n+ </button>\n+ </div>\n+ </form>\n+ );\n+}\n+\n+function FilterSelect({\n+ label,\n+ name,\n+ options,\n+ value\n+}: {\n+ label: string;\n+ name: string;\n+ options: readonly string[];\n+ value: string | undefined;\n+}) {\n+ return (\n+ <label className=\"flex flex-col gap-1 text-sm font-medium text-primary\">\n+ {label}\n+ <select className=\"rounded-md border border-border bg-surface px-3 py-2 text-sm text-primary\" defaultValue={value ?? \"\"} name={name}>\n+ <option value=\"\">Any</option>\n+ {options.map((option) => (\n+ <option key={option} value={option}>\n+ {formatLabel(option)}\n+ </option>\n+ ))}\n+ </select>\n+ </label>\n+ );\n+}\n+\n+function CiFailuresQueue({ data }: { data: CiFailureListResponse }) {\n+ return (\n+ <section className=\"overflow-hidden rounded-lg border border-border bg-surface\">\n+ <div className=\"flex flex-col gap-1 border-b border-border px-4 py-3 sm:flex-row sm:items-center sm:justify-between\">\n+ <h2 className=\"text-sm font-semibold text-primary\">Failed workflows and jobs</h2>\n+ <p className=\"font-mono text-xs text-secondary\">\n+ Showing {data.pagination.returned} of {data.pagination.limit}\n+ </p>\n+ </div>\n+ <div className=\"grid divide-y divide-border md:hidden\">\n+ {data.ciFailures.map((failure) => (\n+ <CiFailureCard key={failure.id} failure={failure} />\n+ ))}\n+ </div>\n+ <div className=\"hidden overflow-x-auto md:block\">\n+ <table className=\"min-w-full divide-y divide-border text-sm\">\n+ <thead className=\"bg-subtle text-left text-xs font-semibold uppercase text-secondary\">\n+ <tr>\n+ <th className=\"px-4 py-3\">Repository</th>\n+ <th className=\"px-4 py-3\">PR</th>\n+ <th className=\"px-4 py-3\">Failed workflow/job</th>\n+ <th className=\"px-4 py-3\">Root cause summary</th>\n+ <th className=\"px-4 py-3\">Flaky suspected</th>\n+ <th className=\"px-4 py-3\">Suggested fix</th>\n+ <th className=\"px-4 py-3\">Created</th>\n+ </tr>\n+ </thead>\n+ <tbody className=\"divide-y divide-border\">\n+ {data.ciFailures.map((failure) => (\n+ <CiFailureRow key={failure.id} failure={failure} />\n+ ))}\n+ </tbody>\n+ </table>\n+ </div>\n+ </section>\n+ );\n+}\n+\n+function CiFailureRow({ failure }: { failure: CiFailureListItem }) {\n+ return (\n+ <tr>\n+ <td className=\"px-4 py-3\">\n+ <div className=\"font-medium text-primary\">{failure.repositoryFullName}</div>\n+ <a className=\"mt-1 block font-mono text-xs text-accent\" href={`/review-runs/${encodeURIComponent(failure.reviewRunId)}`}>\n+ {failure.reviewRunId.slice(0, 8)}\n+ </a>\n+ </td>\n+ <td className=\"px-4 py-3\">\n+ <a className=\"font-medium text-accent\" href={`/pull-requests/${encodeURIComponent(failure.pullRequestId)}`}>\n+ #{failure.pullRequestNumber}\n+ </a>\n+ <div className=\"mt-1 max-w-xs truncate text-xs text-secondary\">{failure.pullRequestTitle}</div>\n+ </td>\n+ <td className=\"px-4 py-3\">\n+ <FailedJobSummary job={failure.failedJob} />\n+ </td>\n+ <td className=\"px-4 py-3 max-w-sm text-sm leading-5 text-primary\">\n+ <a className=\"text-accent\" href={`/ci-failures/${encodeURIComponent(failure.id)}`}>\n+ {failure.rootCauseSummary}\n+ </a>\n+ </td>\n+ <td className=\"px-4 py-3\">\n+ <FlakyBadge suspected={failure.flakySuspected} />\n+ </td>\n+ <td className=\"px-4 py-3 max-w-xs text-sm leading-5 text-secondary\">{failure.suggestedFix ?? \"No suggested fix stored.\"}</td>\n+ <td className=\"px-4 py-3 text-xs text-secondary\">{formatDateTime(failure.createdAt)}</td>\n+ </tr>\n+ );\n+}\n+\n+function CiFailureCard({ failure }: { failure: CiFailureListItem }) {\n+ return (\n+ <article className=\"p-4\">\n+ <div className=\"flex items-start justify-between gap-3\">\n+ <div className=\"min-w-0\">\n+ <a className=\"font-medium text-accent\" href={`/ci-failures/${encodeURIComponent(failure.id)}`}>\n+ {failure.failedJob.jobName}\n+ </a>\n+ <p className=\"mt-1 truncate text-sm text-secondary\">\n+ {failure.repositoryFullName} / PR #{failure.pullRequestNumber}\n+ </p>\n+ </div>\n+ <FlakyBadge suspected={failure.flakySuspected} />\n+ </div>\n+ <p className=\"mt-3 text-sm leading-6 text-primary\">{failure.rootCauseSummary}</p>\n+ <dl className=\"mt-3 grid grid-cols-2 gap-3 text-sm\">\n+ <Metadata label=\"Workflow\" value={failure.failedJob.workflowName ?? \"Unknown\"} />\n+ <Metadata label=\"Created\" value={formatDateTime(failure.createdAt)} />\n+ <Metadata label=\"Suggested fix\" value={failure.suggestedFix ?? \"No suggested fix stored.\"} />\n+ <Metadata label=\"Status\" value={formatLabel(failure.status)} />\n+ </dl>\n+ </article>\n+ );\n+}\n+\n+function CiFailureDetailContent({ detail }: { detail: CiFailureDetailResponse }) {\n+ return (\n+ <div className=\"space-y-5\">\n+ <section className=\"rounded-lg border border-border bg-surface p-5\">\n+ <div className=\"flex flex-col gap-3 md:flex-row md:items-start md:justify-between\">\n+ <div className=\"min-w-0\">\n+ <p className=\"text-sm font-medium text-accent\">CI Failure</p>\n+ <h1 className=\"mt-1 text-2xl font-semibold tracking-normal text-primary\">{detail.failedJob.jobName}</h1>\n+ <p className=\"mt-2 text-sm text-secondary\">\n+ {detail.repositoryFullName} / PR #{detail.pullRequestNumber}\n+ </p>\n+ </div>\n+ <div className=\"flex flex-wrap items-center gap-2 md:justify-end\">\n+ <StatusBadge status={detail.status} />\n+ <FlakyBadge suspected={detail.flakySuspected} />\n+ <a className=\"rounded-md border border-border px-3 py-2 text-sm font-medium text-primary\" href=\"/ci-failures\">\n+ Back to queue\n+ </a>\n+ </div>\n+ </div>\n+ </section>\n+\n+ <section className=\"grid gap-3 md:grid-cols-4\" aria-label=\"CI failure metrics\">\n+ <Metric label=\"Failed jobs\" value={String(detail.failedJobs.length)} />\n+ <Metric label=\"Redacted excerpts\" value={String(detail.logExcerpts.length)} />\n+ <Metric label=\"Suggested fixes\" value={String(detail.suggestedFixes.length)} />\n+ <Metric label=\"Created\" value={formatDateTime(detail.createdAt)} />\n+ </section>\n+\n+ <div className=\"grid gap-5 xl:grid-cols-[minmax(0,1fr)_340px]\">\n+ <div className=\"space-y-5\">\n+ <SummarySection detail={detail} />\n+ <SuggestedFixesSection fixes={detail.suggestedFixes} fallback={detail.suggestedFix} />\n+ <FailedJobsSection jobs={detail.failedJobs} />\n+ <LogExcerptsSection excerpts={detail.logExcerpts} />\n+ <UnavailableLogsSection notes={detail.unavailableLogNotes} />\n+ </div>\n+ <RelatedLinksPanel detail={detail} />\n+ </div>\n+ </div>\n+ );\n+}\n+\n+function SummarySection({ detail }: { detail: CiFailureDetailResponse }) {\n+ return (\n+ <section className=\"rounded-lg border border-border bg-surface p-4\">\n+ <SectionHeader title=\"Failure Summary\" subtitle=\"Stored CI explanation summary\" />\n+ <p className=\"mt-3 text-sm leading-6 text-primary\">{detail.rootCauseSummary}</p>\n+ <h3 className=\"mt-4 text-sm font-semibold text-primary\">Likely root cause</h3>\n+ <p className=\"mt-2 text-sm leading-6 text-primary\">{detail.rootCause}</p>\n+ </section>\n+ );\n+}\n+\n+function SuggestedFixesSection({\n+ fallback,\n+ fixes\n+}: {\n+ fallback: string | null;\n+ fixes: CiFailureDetailResponse[\"suggestedFixes\"];\n+}) {\n+ const visibleFixes = fixes.length > 0 ? fixes : fallback === null ? [] : [{ id: \"fallback-fix\", text: fallback }];\n+\n+ return (\n+ <section className=\"rounded-lg border border-border bg-surface p-4\">\n+ <SectionHeader title=\"Suggested Fixes\" subtitle={`${visibleFixes.length} stored recommendations`} />\n+ {visibleFixes.length === 0 ? <p className=\"mt-3 text-sm text-secondary\">No suggested fixes were stored for this failure.</p> : null}\n+ <ol className=\"mt-3 grid gap-2\">\n+ {visibleFixes.map((fix) => (\n+ <li key={fix.id} className=\"rounded-md border border-border bg-subtle p-3 text-sm leading-6 text-primary\">\n+ {fix.text}\n+ </li>\n+ ))}\n+ </ol>\n+ </section>\n+ );\n+}\n+\n+function FailedJobsSection({ jobs }: { jobs: CiFailureFailedJob[] }) {\n+ return (\n+ <section className=\"overflow-hidden rounded-lg border border-border bg-surface\">\n+ <SectionHeader title=\"Failed Jobs\" subtitle={`${jobs.length} failed workflow jobs`} padded />\n+ <div className=\"overflow-x-auto\">\n+ <table className=\"min-w-full divide-y divide-border text-sm\">\n+ <thead className=\"bg-subtle text-left text-xs font-semibold uppercase text-secondary\">\n+ <tr>\n+ <th className=\"px-4 py-3\">Workflow/job</th>\n+ <th className=\"px-4 py-3\">Step</th>\n+ <th className=\"px-4 py-3\">Category</th>\n+ <th className=\"px-4 py-3\">Conclusion</th>\n+ <th className=\"px-4 py-3\">Provider link</th>\n+ </tr>\n+ </thead>\n+ <tbody className=\"divide-y divide-border\">\n+ {jobs.map((job) => (\n+ <tr key={job.id}>\n+ <td className=\"px-4 py-3\">\n+ <FailedJobSummary job={job} />\n+ </td>\n+ <td className=\"px-4 py-3 text-primary\">{job.stepName ?? \"Unknown step\"}</td>\n+ <td className=\"px-4 py-3 text-primary\">{formatLabel(job.category)}</td>\n+ <td className=\"px-4 py-3 text-primary\">{formatLabel(job.conclusion)}</td>\n+ <td className=\"px-4 py-3\">\n+ {job.detailsUrl === null ? (\n+ <span className=\"text-secondary\">Not available</span>\n+ ) : (\n+ <a className=\"text-accent\" href={job.detailsUrl}>\n+ Check run\n+ </a>\n+ )}\n+ </td>\n+ </tr>\n+ ))}\n+ </tbody>\n+ </table>\n+ </div>\n+ </section>\n+ );\n+}\n+\n+function LogExcerptsSection({ excerpts }: { excerpts: CollapsedLogExcerpt[] }) {\n+ return (\n+ <section className=\"rounded-lg border border-border bg-surface p-4\">\n+ <SectionHeader title=\"Redacted Log Excerpts\" subtitle={`${excerpts.length} collapsed excerpts available by default`} />\n+ <div className=\"mt-3 grid gap-3\">\n+ {excerpts.length === 0 ? <p className=\"text-sm text-secondary\">No redacted log excerpts were stored for this failure.</p> : null}\n+ {excerpts.map((excerpt) => (\n+ <details key={excerpt.id} className=\"rounded-md border border-border bg-subtle p-3\">\n+ <summary className=\"cursor-pointer text-sm font-medium text-accent\">\n+ {excerpt.title} ({excerpt.redacted ? \"redacted\" : \"metadata only\"})\n+ {excerpt.truncated ? \" / truncated\" : \"\"}\n+ </summary>\n+ <pre className=\"mt-3 max-h-72 overflow-auto rounded-md border border-border bg-slate-950 p-3 text-xs leading-5 text-slate-100\">\n+ {excerpt.excerpt}\n+ </pre>\n+ </details>\n+ ))}\n+ </div>\n+ </section>\n+ );\n+}\n+\n+function UnavailableLogsSection({ notes }: { notes: unknown[] }) {\n+ if (notes.length === 0) {\n+ return null;\n+ }\n+\n+ return (\n+ <section className=\"rounded-lg border border-amber-200 bg-amber-50 p-4\">\n+ <h2 className=\"text-base font-semibold text-amber-800\">Unavailable Logs</h2>\n+ <ul className=\"mt-3 grid gap-2 text-sm leading-6 text-amber-800\">\n+ {notes.map((note, index) => (\n+ <li key={index}>{typeof note === \"string\" ? note : JSON.stringify(note)}</li>\n+ ))}\n+ </ul>\n+ </section>\n+ );\n+}\n+\n+function RelatedLinksPanel({ detail }: { detail: CiFailureDetailResponse }) {\n+ return (\n+ <aside className=\"rounded-lg border border-border bg-surface p-4 xl:sticky xl:top-24 xl:self-start\">\n+ <h2 className=\"text-base font-semibold text-primary\">Related Links</h2>\n+ <dl className=\"mt-4 grid gap-3 text-sm\">\n+ <Metadata label=\"Repository\" value={detail.repositoryFullName} />\n+ <Metadata label=\"Pull request\" value={`#${detail.pullRequestNumber} ${detail.pullRequestTitle}`} />\n+ <Metadata label=\"Review run\" value={detail.relatedReviewRun.id.slice(0, 8)} monospace />\n+ <Metadata label=\"Run created\" value={formatDateTime(detail.relatedReviewRun.createdAt)} />\n+ </dl>\n+ <div className=\"mt-4 flex flex-wrap gap-2\">\n+ <a className=\"rounded-md border border-border px-3 py-2 text-sm font-medium text-primary\" href={`/review-runs/${detail.relatedReviewRun.id}`}>\n+ Review run\n+ </a>\n+ <a className=\"rounded-md border border-border px-3 py-2 text-sm font-medium text-primary\" href={`/pull-requests/${detail.pullRequestId}`}>\n+ Pull request\n+ </a>\n+ </div>\n+ <section className=\"mt-5 border-t border-border pt-4\">\n+ <h3 className=\"text-sm font-semibold text-primary\">Artifacts</h3>\n+ <div className=\"mt-3 grid gap-3\">\n+ {detail.relatedArtifacts.map((artifact) => (\n+ <ArtifactLink key={artifact.id} artifact={artifact} />\n+ ))}\n+ </div>\n+ </section>\n+ </aside>\n+ );\n+}\n+\n+function ArtifactLink({ artifact }: { artifact: ReviewRunArtifact }) {\n+ return (\n+ <div className=\"rounded-md border border-border bg-subtle p-3\">\n+ <div className=\"flex items-center justify-between gap-3\">\n+ <span className=\"font-mono text-xs font-semibold text-primary\">{artifact.artifactType}</span>\n+ <span className=\"font-mono text-xs text-secondary\">{formatDateTime(artifact.createdAt)}</span>\n+ </div>\n+ {artifact.rawAccessAllowed && artifact.rawAccessUrl !== null ? (\n+ <a className=\"mt-2 inline-flex rounded-md border border-border bg-surface px-2 py-1 text-xs font-medium text-accent\" href={artifact.rawAccessUrl}>\n+ Raw artifact\n+ </a>\n+ ) : (\n+ <button\n+ className=\"mt-2 cursor-not-allowed rounded-md border border-border bg-surface px-2 py-1 text-xs font-medium text-secondary\"\n+ type=\"button\"\n+ disabled\n+ >\n+ Raw artifact restricted\n+ </button>\n+ )}\n+ </div>\n+ );\n+}\n+\n+function FailedJobSummary({ job }: { job: CiFailureFailedJob }) {\n+ return (\n+ <div className=\"min-w-0\">\n+ <div className=\"font-medium text-primary\">{job.workflowName ?? \"Unknown workflow\"}</div>\n+ <div className=\"mt-1 truncate font-mono text-xs text-secondary\">{job.jobName}</div>\n+ </div>\n+ );\n+}\n+\n+function FlakyBadge({ suspected }: { suspected: boolean }) {\n+ const className = suspected\n+ ? \"border-amber-200 bg-amber-50 text-amber-700\"\n+ : \"border-slate-200 bg-slate-100 text-slate-700\";\n+\n+ return (\n+ <span className={`inline-flex rounded-md border px-2 py-1 text-xs font-medium ${className}`}>\n+ {suspected ? \"Suspected\" : \"Not suspected\"}\n+ </span>\n+ );\n+}\n+\n+function Metric({ label, value }: { label: string; value: string }) {\n+ return (\n+ <div className=\"rounded-lg border border-border bg-surface p-4\">\n+ <p className=\"text-xs font-medium uppercase text-secondary\">{label}</p>\n+ <p className=\"mt-2 break-words font-mono text-base font-semibold leading-6 text-primary\">{value}</p>\n+ </div>\n+ );\n+}\n+\n+function Metadata({ label, value, monospace = false }: { label: string; value: string; monospace?: boolean }) {\n+ return (\n+ <div>\n+ <dt className=\"text-xs font-medium uppercase text-secondary\">{label}</dt>\n+ <dd className={`mt-1 break-words text-primary ${monospace ? \"font-mono text-xs\" : \"text-sm\"}`}>{value}</dd>\n+ </div>\n+ );\n+}\n+\n+function SectionHeader({ padded = false, subtitle, title }: { padded?: boolean; subtitle: string; title: string }) {\n+ return (\n+ <div className={padded ? \"border-b border-border px-4 py-3\" : \"\"}>\n+ <h2 className=\"text-base font-semibold text-primary\">{title}</h2>\n+ <p className=\"mt-1 text-sm text-secondary\">{subtitle}</p>\n+ </div>\n+ );\n+}\n+\n+function CiFailuresLoadingState() {\n+ return (\n+ <section className=\"rounded-lg border border-border bg-surface p-4\" aria-label=\"Loading CI failures\">\n+ <div className=\"h-5 w-48 rounded bg-subtle\" />\n+ <div className=\"mt-4 grid gap-3\">\n+ {[\"one\", \"two\", \"three\", \"four\"].map((key) => (\n+ <div key={key} className=\"h-16 rounded-md bg-subtle\" />\n+ ))}\n+ </div>\n+ </section>\n+ );\n+}\n+\n+function CiFailureDetailLoadingState() {\n+ return (\n+ <div className=\"space-y-4\" aria-label=\"Loading CI failure detail\">\n+ <div className=\"rounded-lg border border-border bg-surface p-5\">\n+ <div className=\"h-6 w-64 rounded bg-subtle\" />\n+ <div className=\"mt-4 h-4 w-96 max-w-full rounded bg-subtle\" />\n+ </div>\n+ <div className=\"grid gap-3 md:grid-cols-4\">\n+ {[\"one\", \"two\", \"three\", \"four\"].map((key) => (\n+ <div key={key} className=\"h-24 rounded-lg border border-border bg-surface p-4\">\n+ <div className=\"h-4 w-24 rounded bg-subtle\" />\n+ <div className=\"mt-3 h-6 w-16 rounded bg-subtle\" />\n+ </div>\n+ ))}\n+ </div>\n+ </div>\n+ );\n+}\n+\n+function CiFailuresErrorState({ message }: { message: string }) {\n+ return (\n+ <section className=\"rounded-lg border border-red-200 bg-red-50 p-4\">\n+ <h2 className=\"text-sm font-semibold text-red-800\">CI failures could not be loaded</h2>\n+ <p className=\"mt-2 text-sm leading-6 text-red-700\">{message}</p>\n+ </section>\n+ );\n+}\n+\n+function CiFailuresEmptyState() {\n+ return (\n+ <section className=\"rounded-lg border border-border bg-surface p-6\">\n+ <h2 className=\"text-lg font-semibold text-primary\">No CI failures match these filters</h2>\n+ <p className=\"mt-2 max-w-xl text-sm leading-6 text-secondary\">\n+ CI failures appear here after Firmcode stores redacted CI explanations for failed workflow jobs.\n+ </p>\n+ </section>\n+ );\n+}\n+\n+function formatLabel(value: string): string {\n+ return value\n+ .split(\"_\")\n+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n+ .join(\" \");\n+}",
"status": "added",
"language": "typescript",
"additions": 566,
"deletions": 0,
"sizeBytes": 22339,
"previousPath": null,
"changedNewLines": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
124,
125,
126,
127,
128,
129,
130,
131,
132,
133,
134,
135,
136,
137,
138,
139,
140,
141,
142,
143,
144,
145,
146,
147,
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229,
230,
231,
232,
233,
234,
235,
236,
237,
238,
239,
240,
241,
242,
243,
244,
245,
246,
247,
248,
249,
250,
251,
252,
253,
254,
255,
256,
257,
258,
259,
260,
261,
262,
263,
264,
265,
266,
267,
268,
269,
270,
271,
272,
273,
274,
275,
276,
277,
278,
279,
280,
281,
282,
283,
284,
285,
286,
287,
288,
289,
290,
291,
292,
293,
294,
295,
296,
297,
298,
299,
300,
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320,
321,
322,
323,
324,
325,
326,
327,
328,
329,
330,
331,
332,
333,
334,
335,
336,
337,
338,
339,
340,
341,
342,
343,
344,
345,
346,
347,
348,
349,
350,
351,
352,
353,
354,
355,
356,
357,
358,
359,
360,
361,
362,
363,
364,
365,
366,
367,
368,
369,
370,
371,
372,
373,
374,
375,
376,
377,
378,
379,
380,
381,
382,
383,
384,
385,
386,
387,
388,
389,
390,
391,
392,
393,
394,
395,
396,
397,
398,
399,
400,
401,
402,
403,
404,
405,
406,
407,
408,
409,
410,
411,
412,
413,
414,
415,
416,
417,
418,
419,
420,
421,
422,
423,
424,
425,
426,
427,
428,
429,
430,
431,
432,
433,
434,
435,
436,
437,
438,
439,
440,
441,
442,
443,
444,
445,
446,
447,
448,
449,
450,
451,
452,
453,
454,
455,
456,
457,
458,
459,
460,
461,
462,
463,
464,
465,
466,
467,
468,
469,
470,
471,
472,
473,
474,
475,
476,
477,
478,
479,
480,
481,
482,
483,
484,
485,
486,
487,
488,
489,
490,
491,
492,
493,
494,
495,
496,
497,
498,
499,
500,
501,
502,
503,
504,
505,
506,
507,
508,
509,
510,
511,
512,
513,
514,
515,
516,
517,
518,
519,
520,
521,
522,
523,
524,
525,
526,
527,
528,
529,
530,
531,
532,
533,
534,
535,
536,
537,
538,
539,
540,
541,
542,
543,
544,
545,
546,
547,
548,
549,
550,
551,
552,
553,
554,
555,
556,
557,
558,
559,
560,
561,
562,
563,
564,
565,
566
],
"headContentSha256": "fc4037a1c0d49336354a5c8fd6ce851040df4dd77a048f62ebfa1f9f77830259"
},
{
"path": "apps/web/components/dashboard/dashboard-shell.tsx",
"hunks": [
{
"lines": [
{
"type": "context",
"content": " { label: \"Pull Requests\", href: \"/pull-requests\", enabled: true },",
"newLineNumber": 22,
"oldLineNumber": 22
},
{
"type": "context",
"content": " { label: \"Review Runs\", href: \"/review-runs\", enabled: true },",
"newLineNumber": 23,
"oldLineNumber": 23
},
{
"type": "context",
"content": " { label: \"Findings\", href: \"/findings\", enabled: true },",
"newLineNumber": 24,
"oldLineNumber": 24
},
{
"type": "deletion",
"content": " { label: \"CI Failures\", href: \"/ci-failures\", enabled: false },",
"newLineNumber": null,
"oldLineNumber": 25
},
{
"type": "addition",
"content": " { label: \"CI Failures\", href: \"/ci-failures\", enabled: true },",
"newLineNumber": 25,
"oldLineNumber": null
},
{
"type": "context",
"content": " { label: \"Rules / Policies\", href: \"/rules\", enabled: true },",
"newLineNumber": 26,
"oldLineNumber": 26
},
{
"type": "context",
"content": " { label: \"Settings\", href: \"/settings\", enabled: true },",
"newLineNumber": 27,
"oldLineNumber": 27
},
{
"type": "context",
"content": " { label: \"Billing\", href: \"/billing\", enabled: true }",
"newLineNumber": 28,
"oldLineNumber": 28
}
],
"newStart": 22,
"oldStart": 22,
"newLineCount": 7,
"oldLineCount": 7,
"sectionHeader": "const navItems = ["
}
],
"patch": "@@ -22,7 +22,7 @@ const navItems = [\n { label: \"Pull Requests\", href: \"/pull-requests\", enabled: true },\n { label: \"Review Runs\", href: \"/review-runs\", enabled: true },\n { label: \"Findings\", href: \"/findings\", enabled: true },\n- { label: \"CI Failures\", href: \"/ci-failures\", enabled: false },\n+ { label: \"CI Failures\", href: \"/ci-failures\", enabled: true },\n { label: \"Rules / Policies\", href: \"/rules\", enabled: true },\n { label: \"Settings\", href: \"/settings\", enabled: true },\n { label: \"Billing\", href: \"/billing\", enabled: true }",
"status": "modified",
"language": "typescript",
"additions": 1,
"deletions": 1,
"sizeBytes": 6624,
"previousPath": null,
"changedNewLines": [
25
],
"headContentSha256": "f6f441fcdb7d3da7d159f84fd6aaef88e3055c404623ae54cb20f4fc94f85ce1"
},
{
"path": "apps/web/lib/dashboard-data.ts",
"hunks": [
{
"lines": [
{
"type": "context",
"content": "import type {",
"newLineNumber": 1,
"oldLineNumber": 1
},
{
"type": "addition",
"content": " CiFailureDetailResponse,",
"newLineNumber": 2,
"oldLineNumber": null
},
{
"type": "addition",
"content": " CiFailureListFilters,",
"newLineNumber": 3,
"oldLineNumber": null
},
{
"type": "addition",
"content": " CiFailureListResponse,",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "context",
"content": " DashboardRepositoryListFilters,",
"newLineNumber": 5,
"oldLineNumber": 2
},
{
"type": "context",
"content": " FindingsListFilters,",
"newLineNumber": 6,
"oldLineNumber": 3
},
{
"type": "context",
"content": " FindingsListResponse,",
"newLineNumber": 7,
"oldLineNumber": 4
}
],
"newStart": 1,
"oldStart": 1,
"newLineCount": 7,
"oldLineCount": 4,
"sectionHeader": ""
},
{
"lines": [
{
"type": "context",
"content": " }",
"newLineNumber": 144,
"oldLineNumber": 141
},
{
"type": "context",
"content": "}",
"newLineNumber": 145,
"oldLineNumber": 142
},
{
"type": "context",
"content": "",
"newLineNumber": 146,
"oldLineNumber": 143
},
{
"type": "addition",
"content": "export async function loadCiFailuresState(searchParams: SearchParams): Promise<ViewState<CiFailureListResponse>> {",
"newLineNumber": 147,
"oldLineNumber": null
},
{
"type": "addition",
"content": " try {",
"newLineNumber": 148,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const filters = pickCiFailureFilters(searchParams);",
"newLineNumber": 149,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const data = await requestAuthenticatedJsonWithQuery<CiFailureListResponse>(\"/api/ci-failures\", filters);",
"newLineNumber": 150,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 151,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return data.ciFailures.length === 0 ? { status: \"empty\", data } : { status: \"populated\", data };",
"newLineNumber": 152,
"oldLineNumber": null
},
{
"type": "addition",
"content": " } catch (error) {",
"newLineNumber": 153,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return { status: \"error\", message: toErrorMessage(error) };",
"newLineNumber": 154,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 155,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 156,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 157,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export async function loadCiFailureDetailState(ciFailureId: string): Promise<ViewState<CiFailureDetailResponse>> {",
"newLineNumber": 158,
"oldLineNumber": null
},
{
"type": "addition",
"content": " try {",
"newLineNumber": 159,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const data = await requestAuthenticatedJson<CiFailureDetailResponse>(",
"newLineNumber": 160,
"oldLineNumber": null
},
{
"type": "addition",
"content": " `/api/ci-failures/${encodeURIComponent(decodeRouteSegment(ciFailureId))}`",
"newLineNumber": 161,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 162,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 163,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return { status: \"populated\", data };",
"newLineNumber": 164,
"oldLineNumber": null
},
{
"type": "addition",
"content": " } catch (error) {",
"newLineNumber": 165,
"oldLineNumber": null
},
{
"type": "addition",
"content": " if (error instanceof DashboardApiError && error.status === 404) {",
"newLineNumber": 166,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return { status: \"empty\" };",
"newLineNumber": 167,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 168,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 169,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return { status: \"error\", message: toErrorMessage(error) };",
"newLineNumber": 170,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 171,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 172,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 173,
"oldLineNumber": null
},
{
"type": "context",
"content": "export async function loadSettingsState(): Promise<ViewState<WorkspaceSettingsResponse>> {",
"newLineNumber": 174,
"oldLineNumber": 144
},
{
"type": "context",
"content": " try {",
"newLineNumber": 175,
"oldLineNumber": 145
},
{
"type": "context",
"content": " const data = await requestAuthenticatedJson<WorkspaceSettingsResponse>(\"/api/settings\");",
"newLineNumber": 176,
"oldLineNumber": 146
}
],
"newStart": 144,
"oldStart": 141,
"newLineCount": 33,
"oldLineCount": 6,
"sectionHeader": "export async function loadPullRequestDetailState(pullRequestId: string): Promise"
},
{
"lines": [
{
"type": "context",
"content": " });",
"newLineNumber": 300,
"oldLineNumber": 270
},
{
"type": "context",
"content": "}",
"newLineNumber": 301,
"oldLineNumber": 271
},
{
"type": "context",
"content": "",
"newLineNumber": 302,
"oldLineNumber": 272
},
{
"type": "addition",
"content": "function pickCiFailureFilters(searchParams: SearchParams): CiFailureListFilters {",
"newLineNumber": 303,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const limit = parsePositiveInteger(readSingleValue(searchParams.limit));",
"newLineNumber": 304,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 305,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return removeUndefinedValues({",
"newLineNumber": 306,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositoryId: readSingleValue(searchParams.repositoryId),",
"newLineNumber": 307,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repository: readSingleValue(searchParams.repository),",
"newLineNumber": 308,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status: readSingleValue(searchParams.status) as CiFailureListFilters[\"status\"],",
"newLineNumber": 309,
"oldLineNumber": null
},
{
"type": "addition",
"content": " flaky: parseBoolean(readSingleValue(searchParams.flaky)),",
"newLineNumber": 310,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateFrom: readSingleValue(searchParams.dateFrom),",
"newLineNumber": 311,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateTo: readSingleValue(searchParams.dateTo),",
"newLineNumber": 312,
"oldLineNumber": null
},
{
"type": "addition",
"content": " limit",
"newLineNumber": 313,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 314,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 315,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 316,
"oldLineNumber": null
},
{
"type": "context",
"content": "async function requestJson<T>(path: string, query: object): Promise<T> {",
"newLineNumber": 317,
"oldLineNumber": 273
},
{
"type": "context",
"content": " const url = new URL(path, getApiBaseUrl());",
"newLineNumber": 318,
"oldLineNumber": 274
},
{
"type": "context",
"content": "",
"newLineNumber": 319,
"oldLineNumber": 275
}
],
"newStart": 300,
"oldStart": 270,
"newLineCount": 20,
"oldLineCount": 6,
"sectionHeader": "function pickPullRequestFilters(searchParams: SearchParams): PullRequestListFilt"
},
{
"lines": [
{
"type": "context",
"content": " return value === \"\" ? undefined : value;",
"newLineNumber": 406,
"oldLineNumber": 362
},
{
"type": "context",
"content": "}",
"newLineNumber": 407,
"oldLineNumber": 363
},
{
"type": "context",
"content": "",
"newLineNumber": 408,
"oldLineNumber": 364
},
{
"type": "addition",
"content": "function decodeRouteSegment(value: string): string {",
"newLineNumber": 409,
"oldLineNumber": null
},
{
"type": "addition",
"content": " try {",
"newLineNumber": 410,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return decodeURIComponent(value);",
"newLineNumber": 411,
"oldLineNumber": null
},
{
"type": "addition",
"content": " } catch {",
"newLineNumber": 412,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return value;",
"newLineNumber": 413,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 414,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 415,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 416,
"oldLineNumber": null
},
{
"type": "context",
"content": "function removeUndefinedValues<T extends Record<string, unknown>>(value: T): T {",
"newLineNumber": 417,
"oldLineNumber": 365
},
{
"type": "context",
"content": " return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined)) as T;",
"newLineNumber": 418,
"oldLineNumber": 366
},
{
"type": "context",
"content": "}",
"newLineNumber": 419,
"oldLineNumber": 367
}
],
"newStart": 406,
"oldStart": 362,
"newLineCount": 14,
"oldLineCount": 6,
"sectionHeader": "function readSingleValue(value: string | string[] | undefined): string | undefin"
}
],
"patch": "@@ -1,4 +1,7 @@\n import type {\n+ CiFailureDetailResponse,\n+ CiFailureListFilters,\n+ CiFailureListResponse,\n DashboardRepositoryListFilters,\n FindingsListFilters,\n FindingsListResponse,\n@@ -141,6 +144,33 @@ export async function loadPullRequestDetailState(pullRequestId: string): Promise\n }\n }\n \n+export async function loadCiFailuresState(searchParams: SearchParams): Promise<ViewState<CiFailureListResponse>> {\n+ try {\n+ const filters = pickCiFailureFilters(searchParams);\n+ const data = await requestAuthenticatedJsonWithQuery<CiFailureListResponse>(\"/api/ci-failures\", filters);\n+\n+ return data.ciFailures.length === 0 ? { status: \"empty\", data } : { status: \"populated\", data };\n+ } catch (error) {\n+ return { status: \"error\", message: toErrorMessage(error) };\n+ }\n+}\n+\n+export async function loadCiFailureDetailState(ciFailureId: string): Promise<ViewState<CiFailureDetailResponse>> {\n+ try {\n+ const data = await requestAuthenticatedJson<CiFailureDetailResponse>(\n+ `/api/ci-failures/${encodeURIComponent(decodeRouteSegment(ciFailureId))}`\n+ );\n+\n+ return { status: \"populated\", data };\n+ } catch (error) {\n+ if (error instanceof DashboardApiError && error.status === 404) {\n+ return { status: \"empty\" };\n+ }\n+\n+ return { status: \"error\", message: toErrorMessage(error) };\n+ }\n+}\n+\n export async function loadSettingsState(): Promise<ViewState<WorkspaceSettingsResponse>> {\n try {\n const data = await requestAuthenticatedJson<WorkspaceSettingsResponse>(\"/api/settings\");\n@@ -270,6 +300,20 @@ function pickPullRequestFilters(searchParams: SearchParams): PullRequestListFilt\n });\n }\n \n+function pickCiFailureFilters(searchParams: SearchParams): CiFailureListFilters {\n+ const limit = parsePositiveInteger(readSingleValue(searchParams.limit));\n+\n+ return removeUndefinedValues({\n+ repositoryId: readSingleValue(searchParams.repositoryId),\n+ repository: readSingleValue(searchParams.repository),\n+ status: readSingleValue(searchParams.status) as CiFailureListFilters[\"status\"],\n+ flaky: parseBoolean(readSingleValue(searchParams.flaky)),\n+ dateFrom: readSingleValue(searchParams.dateFrom),\n+ dateTo: readSingleValue(searchParams.dateTo),\n+ limit\n+ });\n+}\n+\n async function requestJson<T>(path: string, query: object): Promise<T> {\n const url = new URL(path, getApiBaseUrl());\n \n@@ -362,6 +406,14 @@ function readSingleValue(value: string | string[] | undefined): string | undefin\n return value === \"\" ? undefined : value;\n }\n \n+function decodeRouteSegment(value: string): string {\n+ try {\n+ return decodeURIComponent(value);\n+ } catch {\n+ return value;\n+ }\n+}\n+\n function removeUndefinedValues<T extends Record<string, unknown>>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined)) as T;\n }",
"status": "modified",
"language": "typescript",
"additions": 52,
"deletions": 0,
"sizeBytes": 15082,
"previousPath": null,
"changedNewLines": [
2,
3,
4,
147,
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
409,
410,
411,
412,
413,
414,
415,
416
],
"headContentSha256": "ec99a21a4883fb9e750098c296adfcdaa8dfa602d19a4f0c80db52e6c0016833"
},
{
"path": "apps/web/lib/overview-data.ts",
"hunks": [
{
"lines": [
{
"type": "context",
"content": " kind: \"ci_failure\",",
"newLineNumber": 37,
"oldLineNumber": 37
},
{
"type": "context",
"content": " title: \"CI failure\",",
"newLineNumber": 38,
"oldLineNumber": 38
},
{
"type": "context",
"content": " detail: \"firmcode dashboard tests need a failure explanation review.\",",
"newLineNumber": 39,
"oldLineNumber": 39
},
{
"type": "deletion",
"content": " href: \"/findings?category=ci&status=open\",",
"newLineNumber": null,
"oldLineNumber": 40
},
{
"type": "addition",
"content": " href: \"/ci-failures\",",
"newLineNumber": 40,
"oldLineNumber": null
},
{
"type": "context",
"content": " severity: \"medium\",",
"newLineNumber": 41,
"oldLineNumber": 41
},
{
"type": "context",
"content": " updatedAt: \"2026-05-22T08:45:00.000Z\"",
"newLineNumber": 42,
"oldLineNumber": 42
},
{
"type": "context",
"content": " }",
"newLineNumber": 43,
"oldLineNumber": 43
}
],
"newStart": 37,
"oldStart": 37,
"newLineCount": 7,
"oldLineCount": 7,
"sectionHeader": "const DEFAULT_OVERVIEW_SUPPLEMENT: OverviewSupplementData = {"
}
],
"patch": "@@ -37,7 +37,7 @@ const DEFAULT_OVERVIEW_SUPPLEMENT: OverviewSupplementData = {\n kind: \"ci_failure\",\n title: \"CI failure\",\n detail: \"firmcode dashboard tests need a failure explanation review.\",\n- href: \"/findings?category=ci&status=open\",\n+ href: \"/ci-failures\",\n severity: \"medium\",\n updatedAt: \"2026-05-22T08:45:00.000Z\"\n }",
"status": "modified",
"language": "typescript",
"additions": 1,
"deletions": 1,
"sizeBytes": 9120,
"previousPath": null,
"changedNewLines": [
40
],
"headContentSha256": "96886b8c491207cfde02b55ed5ffb6c9f4a8e538802f6ed6f9e309a5319b5fc9"
},
{
"path": "apps/web/packages/shared/src/contracts/review.ts",
"hunks": [
{
"lines": [
{
"type": "context",
"content": " };",
"newLineNumber": 577,
"oldLineNumber": 577
},
{
"type": "context",
"content": "}",
"newLineNumber": 578,
"oldLineNumber": 578
},
{
"type": "context",
"content": "",
"newLineNumber": 579,
"oldLineNumber": 579
},
{
"type": "deletion",
"content": "export type ReviewRunArtifactType = \"diff\" | \"treesitter\" | \"semgrep\" | \"context_pack\" | \"llm_raw\" | \"ci_log\";",
"newLineNumber": null,
"oldLineNumber": 580
},
{
"type": "addition",
"content": "export type ReviewRunArtifactType =",
"newLineNumber": 580,
"oldLineNumber": null
},
{
"type": "addition",
"content": " | \"diff\"",
"newLineNumber": 581,
"oldLineNumber": null
},
{
"type": "addition",
"content": " | \"treesitter\"",
"newLineNumber": 582,
"oldLineNumber": null
},
{
"type": "addition",
"content": " | \"semgrep\"",
"newLineNumber": 583,
"oldLineNumber": null
},
{
"type": "addition",
"content": " | \"context_pack\"",
"newLineNumber": 584,
"oldLineNumber": null
},
{
"type": "addition",
"content": " | \"llm_raw\"",
"newLineNumber": 585,
"oldLineNumber": null
},
{
"type": "addition",
"content": " | \"ci_log\"",
"newLineNumber": 586,
"oldLineNumber": null
},
{
"type": "addition",
"content": " | \"ci_failure_explanation\";",
"newLineNumber": 587,
"oldLineNumber": null
},
{
"type": "context",
"content": "",
"newLineNumber": 588,
"oldLineNumber": 581
},
{
"type": "context",
"content": "export interface ReviewRunArtifact {",
"newLineNumber": 589,
"oldLineNumber": 582
},
{
"type": "context",
"content": " id: string;",
"newLineNumber": 590,
"oldLineNumber": 583
}
],
"newStart": 577,
"oldStart": 577,
"newLineCount": 14,
"oldLineCount": 7,
"sectionHeader": "export interface WorkspaceSettingsResponse {"
},
{
"lines": [
{
"type": "context",
"content": " };",
"newLineNumber": 655,
"oldLineNumber": 648
},
{
"type": "context",
"content": "}",
"newLineNumber": 656,
"oldLineNumber": 649
},
{
"type": "context",
"content": "",
"newLineNumber": 657,
"oldLineNumber": 650
},
{
"type": "addition",
"content": "export interface CiFailureListFilters {",
"newLineNumber": 658,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositoryId?: string;",
"newLineNumber": 659,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repository?: string;",
"newLineNumber": 660,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status?: ReviewRunStatus;",
"newLineNumber": 661,
"oldLineNumber": null
},
{
"type": "addition",
"content": " flaky?: boolean;",
"newLineNumber": 662,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateFrom?: string;",
"newLineNumber": 663,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateTo?: string;",
"newLineNumber": 664,
"oldLineNumber": null
},
{
"type": "addition",
"content": " limit?: number;",
"newLineNumber": 665,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 666,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 667,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export interface CiFailureFailedJob {",
"newLineNumber": 668,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: string;",
"newLineNumber": 669,
"oldLineNumber": null
},
{
"type": "addition",
"content": " workflowName: string | null;",
"newLineNumber": 670,
"oldLineNumber": null
},
{
"type": "addition",
"content": " jobName: string;",
"newLineNumber": 671,
"oldLineNumber": null
},
{
"type": "addition",
"content": " checkRunId: number;",
"newLineNumber": 672,
"oldLineNumber": null
},
{
"type": "addition",
"content": " conclusion: string;",
"newLineNumber": 673,
"oldLineNumber": null
},
{
"type": "addition",
"content": " stepName: string | null;",
"newLineNumber": 674,
"oldLineNumber": null
},
{
"type": "addition",
"content": " category: string;",
"newLineNumber": 675,
"oldLineNumber": null
},
{
"type": "addition",
"content": " detailsUrl: string | null;",
"newLineNumber": 676,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 677,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 678,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export interface CiFailureListItem {",
"newLineNumber": 679,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: string;",
"newLineNumber": 680,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositoryId: string;",
"newLineNumber": 681,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositoryFullName: string;",
"newLineNumber": 682,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pullRequestId: string;",
"newLineNumber": 683,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pullRequestNumber: number;",
"newLineNumber": 684,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pullRequestTitle: string;",
"newLineNumber": 685,
"oldLineNumber": null
},
{
"type": "addition",
"content": " reviewRunId: string;",
"newLineNumber": 686,
"oldLineNumber": null
},
{
"type": "addition",
"content": " failedJob: CiFailureFailedJob;",
"newLineNumber": 687,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rootCauseSummary: string;",
"newLineNumber": 688,
"oldLineNumber": null
},
{
"type": "addition",
"content": " flakySuspected: boolean;",
"newLineNumber": 689,
"oldLineNumber": null
},
{
"type": "addition",
"content": " suggestedFix: string | null;",
"newLineNumber": 690,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status: ReviewRunStatus;",
"newLineNumber": 691,
"oldLineNumber": null
},
{
"type": "addition",
"content": " createdAt: string;",
"newLineNumber": 692,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 693,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 694,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export interface CiFailureListResponse {",
"newLineNumber": 695,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ciFailures: CiFailureListItem[];",
"newLineNumber": 696,
"oldLineNumber": null
},
{
"type": "addition",
"content": " filters: CiFailureListFilters;",
"newLineNumber": 697,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pagination: {",
"newLineNumber": 698,
"oldLineNumber": null
},
{
"type": "addition",
"content": " limit: number;",
"newLineNumber": 699,
"oldLineNumber": null
},
{
"type": "addition",
"content": " returned: number;",
"newLineNumber": 700,
"oldLineNumber": null
},
{
"type": "addition",
"content": " };",
"newLineNumber": 701,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 702,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 703,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export interface CiFailureSuggestedFix {",
"newLineNumber": 704,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: string;",
"newLineNumber": 705,
"oldLineNumber": null
},
{
"type": "addition",
"content": " text: string;",
"newLineNumber": 706,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 707,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 708,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export interface CiFailureRelatedReviewRun {",
"newLineNumber": 709,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: string;",
"newLineNumber": 710,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status: ReviewRunStatus;",
"newLineNumber": 711,
"oldLineNumber": null
},
{
"type": "addition",
"content": " createdAt: string;",
"newLineNumber": 712,
"oldLineNumber": null
},
{
"type": "addition",
"content": " detailUrl: string;",
"newLineNumber": 713,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 714,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 715,
"oldLineNumber": null
},
{
"type": "addition",
"content": "export interface CiFailureDetailResponse extends CiFailureListItem {",
"newLineNumber": 716,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rootCause: string;",
"newLineNumber": 717,
"oldLineNumber": null
},
{
"type": "addition",
"content": " suggestedFixes: CiFailureSuggestedFix[];",
"newLineNumber": 718,
"oldLineNumber": null
},
{
"type": "addition",
"content": " failedJobs: CiFailureFailedJob[];",
"newLineNumber": 719,
"oldLineNumber": null
},
{
"type": "addition",
"content": " relatedReviewRun: CiFailureRelatedReviewRun;",
"newLineNumber": 720,
"oldLineNumber": null
},
{
"type": "addition",
"content": " relatedArtifacts: ReviewRunArtifact[];",
"newLineNumber": 721,
"oldLineNumber": null
},
{
"type": "addition",
"content": " logExcerpts: Array<ReviewRunLogExcerpt & { collapsed: true }>;",
"newLineNumber": 722,
"oldLineNumber": null
},
{
"type": "addition",
"content": " unavailableLogNotes: unknown[];",
"newLineNumber": 723,
"oldLineNumber": null
},
{
"type": "addition",
"content": "}",
"newLineNumber": 724,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 725,
"oldLineNumber": null
},
{
"type": "context",
"content": "export interface WorkspaceBillingResponse {",
"newLineNumber": 726,
"oldLineNumber": 651
},
{
"type": "context",
"content": " workspace: {",
"newLineNumber": 727,
"oldLineNumber": 652
},
{
"type": "context",
"content": " id: string;",
"newLineNumber": 728,
"oldLineNumber": 653
}
],
"newStart": 655,
"oldStart": 648,
"newLineCount": 74,
"oldLineCount": 6,
"sectionHeader": "export interface ReviewRunDetail extends ReviewRunSummary {"
}
],
"patch": "@@ -577,7 +577,14 @@ export interface WorkspaceSettingsResponse {\n };\n }\n \n-export type ReviewRunArtifactType = \"diff\" | \"treesitter\" | \"semgrep\" | \"context_pack\" | \"llm_raw\" | \"ci_log\";\n+export type ReviewRunArtifactType =\n+ | \"diff\"\n+ | \"treesitter\"\n+ | \"semgrep\"\n+ | \"context_pack\"\n+ | \"llm_raw\"\n+ | \"ci_log\"\n+ | \"ci_failure_explanation\";\n \n export interface ReviewRunArtifact {\n id: string;\n@@ -648,6 +655,74 @@ export interface ReviewRunDetail extends ReviewRunSummary {\n };\n }\n \n+export interface CiFailureListFilters {\n+ repositoryId?: string;\n+ repository?: string;\n+ status?: ReviewRunStatus;\n+ flaky?: boolean;\n+ dateFrom?: string;\n+ dateTo?: string;\n+ limit?: number;\n+}\n+\n+export interface CiFailureFailedJob {\n+ id: string;\n+ workflowName: string | null;\n+ jobName: string;\n+ checkRunId: number;\n+ conclusion: string;\n+ stepName: string | null;\n+ category: string;\n+ detailsUrl: string | null;\n+}\n+\n+export interface CiFailureListItem {\n+ id: string;\n+ repositoryId: string;\n+ repositoryFullName: string;\n+ pullRequestId: string;\n+ pullRequestNumber: number;\n+ pullRequestTitle: string;\n+ reviewRunId: string;\n+ failedJob: CiFailureFailedJob;\n+ rootCauseSummary: string;\n+ flakySuspected: boolean;\n+ suggestedFix: string | null;\n+ status: ReviewRunStatus;\n+ createdAt: string;\n+}\n+\n+export interface CiFailureListResponse {\n+ ciFailures: CiFailureListItem[];\n+ filters: CiFailureListFilters;\n+ pagination: {\n+ limit: number;\n+ returned: number;\n+ };\n+}\n+\n+export interface CiFailureSuggestedFix {\n+ id: string;\n+ text: string;\n+}\n+\n+export interface CiFailureRelatedReviewRun {\n+ id: string;\n+ status: ReviewRunStatus;\n+ createdAt: string;\n+ detailUrl: string;\n+}\n+\n+export interface CiFailureDetailResponse extends CiFailureListItem {\n+ rootCause: string;\n+ suggestedFixes: CiFailureSuggestedFix[];\n+ failedJobs: CiFailureFailedJob[];\n+ relatedReviewRun: CiFailureRelatedReviewRun;\n+ relatedArtifacts: ReviewRunArtifact[];\n+ logExcerpts: Array<ReviewRunLogExcerpt & { collapsed: true }>;\n+ unavailableLogNotes: unknown[];\n+}\n+\n export interface WorkspaceBillingResponse {\n workspace: {\n id: string;",
"status": "modified",
"language": "typescript",
"additions": 76,
"deletions": 1,
"sizeBytes": 21349,
"previousPath": null,
"changedNewLines": [
580,
581,
582,
583,
584,
585,
586,
587,
658,
659,
660,
661,
662,
663,
664,
665,
666,
667,
668,
669,
670,
671,
672,
673,
674,
675,
676,
677,
678,
679,
680,
681,
682,
683,
684,
685,
686,
687,
688,
689,
690,
691,
692,
693,
694,
695,
696,
697,
698,
699,
700,
701,
702,
703,
704,
705,
706,
707,
708,
709,
710,
711,
712,
713,
714,
715,
716,
717,
718,
719,
720,
721,
722,
723,
724,
725
],
"headContentSha256": "0d8c477a503a0d72d7cb8853e22487f2bd82bfbb423500ef5804657a711fac33"
},
{
"path": "apps/web/tests/ci-failures-view.spec.tsx",
"hunks": [
{
"lines": [
{
"type": "addition",
"content": "import React from \"react\";",
"newLineNumber": 1,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { renderToString } from \"react-dom/server\";",
"newLineNumber": 2,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import type { CiFailureDetailResponse, CiFailureListResponse } from \"@firmcode/shared\";",
"newLineNumber": 3,
"oldLineNumber": null
},
{
"type": "addition",
"content": "import { CiFailureDetailView, CiFailuresView } from \"../components/dashboard/ci-failures-view\";",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 5,
"oldLineNumber": null
},
{
"type": "addition",
"content": "describe(\"CiFailuresView\", () => {",
"newLineNumber": 6,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"renders loading, empty, and error states\", () => {",
"newLineNumber": 7,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(renderToString(<CiFailuresView state={{ status: \"loading\" }} />)).toContain(\"Loading CI failures\");",
"newLineNumber": 8,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(renderToString(<CiFailuresView state={{ status: \"empty\", data: emptyCiFailures }} />)).toContain(",
"newLineNumber": 9,
"oldLineNumber": null
},
{
"type": "addition",
"content": " \"No CI failures match these filters\"",
"newLineNumber": 10,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 11,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(renderToString(<CiFailuresView state={{ status: \"error\", message: \"API unavailable\" }} />)).toContain(",
"newLineNumber": 12,
"oldLineNumber": null
},
{
"type": "addition",
"content": " \"CI failures could not be loaded\"",
"newLineNumber": 13,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 14,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 15,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 16,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"renders selected filters for repository, run status, flaky status, and dates\", () => {",
"newLineNumber": 17,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(<CiFailuresView state={{ status: \"populated\", data: ciFailures }} />);",
"newLineNumber": 18,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 19,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('action=\"/ci-failures\"');",
"newLineNumber": 20,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('name=\"repository\"');",
"newLineNumber": 21,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('value=\"openclaw/firmcode\"');",
"newLineNumber": 22,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('name=\"status\"');",
"newLineNumber": 23,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('value=\"failed\" selected=\"\">Failed');",
"newLineNumber": 24,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('name=\"flaky\"');",
"newLineNumber": 25,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('value=\"true\" selected=\"\">Suspected');",
"newLineNumber": 26,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('name=\"dateFrom\"');",
"newLineNumber": 27,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('value=\"2026-05-20\"');",
"newLineNumber": 28,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('name=\"dateTo\"');",
"newLineNumber": 29,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('value=\"2026-05-24\"');",
"newLineNumber": 30,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 31,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 32,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"renders populated CI failure rows and responsive list surfaces\", () => {",
"newLineNumber": 33,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(<CiFailuresView state={{ status: \"populated\", data: ciFailures }} />);",
"newLineNumber": 34,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 35,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Broken checks queue\");",
"newLineNumber": 36,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"openclaw/firmcode\");",
"newLineNumber": 37,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Unit tests\");",
"newLineNumber": 38,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"npm test\");",
"newLineNumber": 39,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"AssertionError\");",
"newLineNumber": 40,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Update the API test expectation\");",
"newLineNumber": 41,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('href=\"/ci-failures/00000000-0000-4000-8000-000000000501%3Aunit-tests\"');",
"newLineNumber": 42,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"md:hidden\");",
"newLineNumber": 43,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"hidden overflow-x-auto md:block\");",
"newLineNumber": 44,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 45,
"oldLineNumber": null
},
{
"type": "addition",
"content": "});",
"newLineNumber": 46,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 47,
"oldLineNumber": null
},
{
"type": "addition",
"content": "describe(\"CiFailureDetailView\", () => {",
"newLineNumber": 48,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"renders loading, empty, and error states\", () => {",
"newLineNumber": 49,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(renderToString(<CiFailureDetailView state={{ status: \"loading\" }} />)).toContain(\"Loading CI failure detail\");",
"newLineNumber": 50,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(renderToString(<CiFailureDetailView state={{ status: \"empty\" }} />)).toContain(\"The CI failure could not be found.\");",
"newLineNumber": 51,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(renderToString(<CiFailureDetailView state={{ status: \"error\", message: \"API unavailable\" }} />)).toContain(",
"newLineNumber": 52,
"oldLineNumber": null
},
{
"type": "addition",
"content": " \"CI failures could not be loaded\"",
"newLineNumber": 53,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 54,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 55,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 56,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"renders failure summary, root cause, suggested fixes, failed jobs, and related links\", () => {",
"newLineNumber": 57,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: ciFailureDetail }} />);",
"newLineNumber": 58,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 59,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Failure Summary\");",
"newLineNumber": 60,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Likely root cause\");",
"newLineNumber": 61,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"The unit test expected HTTP 201 but the API returned 200.\");",
"newLineNumber": 62,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Suggested Fixes\");",
"newLineNumber": 63,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Update the API test expectation or restore created responses.\");",
"newLineNumber": 64,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Failed Jobs\");",
"newLineNumber": 65,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Related Links\");",
"newLineNumber": 66,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('href=\"/review-runs/00000000-0000-4000-8000-000000000401\"');",
"newLineNumber": 67,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('href=\"/pull-requests/pr-7\"');",
"newLineNumber": 68,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 69,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 70,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"renders redacted log excerpts collapsed by default\", () => {",
"newLineNumber": 71,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: ciFailureDetail }} />);",
"newLineNumber": 72,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 73,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Redacted Log Excerpts\");",
"newLineNumber": 74,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"collapsed excerpts available by default\");",
"newLineNumber": 75,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"redacted\");",
"newLineNumber": 76,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"[REDACTED_SECRET]\");",
"newLineNumber": 77,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"<details\");",
"newLineNumber": 78,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).not.toContain(\"<details open\");",
"newLineNumber": 79,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 80,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 81,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"disables unauthorized raw artifact controls without exposing storage keys\", () => {",
"newLineNumber": 82,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const viewerDetail: CiFailureDetailResponse = {",
"newLineNumber": 83,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ...ciFailureDetail,",
"newLineNumber": 84,
"oldLineNumber": null
},
{
"type": "addition",
"content": " relatedArtifacts: ciFailureDetail.relatedArtifacts.map((artifact) => ({",
"newLineNumber": 85,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ...artifact,",
"newLineNumber": 86,
"oldLineNumber": null
},
{
"type": "addition",
"content": " storageKey: null,",
"newLineNumber": 87,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessAllowed: false,",
"newLineNumber": 88,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessUrl: null",
"newLineNumber": 89,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }))",
"newLineNumber": 90,
"oldLineNumber": null
},
{
"type": "addition",
"content": " };",
"newLineNumber": 91,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: viewerDetail }} />);",
"newLineNumber": 92,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 93,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Raw artifact restricted\");",
"newLineNumber": 94,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"disabled=\\\"\\\"\");",
"newLineNumber": 95,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).not.toContain(\"artifacts/run-401/ci-log.json\");",
"newLineNumber": 96,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).not.toContain('href=\"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts');",
"newLineNumber": 97,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 98,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 99,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"shows raw artifact links only when the API marks access as allowed\", () => {",
"newLineNumber": 100,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: ciFailureDetail }} />);",
"newLineNumber": 101,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 102,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"Raw artifact\");",
"newLineNumber": 103,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(",
"newLineNumber": 104,
"oldLineNumber": null
},
{
"type": "addition",
"content": " 'href=\"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts/00000000-0000-4000-8000-000000000502/raw\"'",
"newLineNumber": 105,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 106,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).not.toContain(\"raw model output\");",
"newLineNumber": 107,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).not.toContain(\"raw Semgrep output\");",
"newLineNumber": 108,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 109,
"oldLineNumber": null
},
{
"type": "addition",
"content": "});",
"newLineNumber": 110,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 111,
"oldLineNumber": null
},
{
"type": "addition",
"content": "const CI_FAILURE_ID = \"00000000-0000-4000-8000-000000000501:unit-tests\";",
"newLineNumber": 112,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 113,
"oldLineNumber": null
},
{
"type": "addition",
"content": "const ciFailures: CiFailureListResponse = {",
"newLineNumber": 114,
"oldLineNumber": null
},
{
"type": "addition",
"content": " filters: {",
"newLineNumber": 115,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repository: \"openclaw/firmcode\",",
"newLineNumber": 116,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status: \"failed\",",
"newLineNumber": 117,
"oldLineNumber": null
},
{
"type": "addition",
"content": " flaky: true,",
"newLineNumber": 118,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateFrom: \"2026-05-20\",",
"newLineNumber": 119,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateTo: \"2026-05-24\"",
"newLineNumber": 120,
"oldLineNumber": null
},
{
"type": "addition",
"content": " },",
"newLineNumber": 121,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pagination: {",
"newLineNumber": 122,
"oldLineNumber": null
},
{
"type": "addition",
"content": " limit: 50,",
"newLineNumber": 123,
"oldLineNumber": null
},
{
"type": "addition",
"content": " returned: 1",
"newLineNumber": 124,
"oldLineNumber": null
},
{
"type": "addition",
"content": " },",
"newLineNumber": 125,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ciFailures: [",
"newLineNumber": 126,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {",
"newLineNumber": 127,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: CI_FAILURE_ID,",
"newLineNumber": 128,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositoryId: \"repo-1\",",
"newLineNumber": 129,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositoryFullName: \"openclaw/firmcode\",",
"newLineNumber": 130,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pullRequestId: \"pr-7\",",
"newLineNumber": 131,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pullRequestNumber: 7,",
"newLineNumber": 132,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pullRequestTitle: \"Add CI failure dashboard\",",
"newLineNumber": 133,
"oldLineNumber": null
},
{
"type": "addition",
"content": " reviewRunId: \"00000000-0000-4000-8000-000000000401\",",
"newLineNumber": 134,
"oldLineNumber": null
},
{
"type": "addition",
"content": " failedJob: {",
"newLineNumber": 135,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: \"unit-tests\",",
"newLineNumber": 136,
"oldLineNumber": null
},
{
"type": "addition",
"content": " workflowName: \"Unit tests\",",
"newLineNumber": 137,
"oldLineNumber": null
},
{
"type": "addition",
"content": " jobName: \"npm test\",",
"newLineNumber": 138,
"oldLineNumber": null
},
{
"type": "addition",
"content": " checkRunId: 12345,",
"newLineNumber": 139,
"oldLineNumber": null
},
{
"type": "addition",
"content": " conclusion: \"failure\",",
"newLineNumber": 140,
"oldLineNumber": null
},
{
"type": "addition",
"content": " stepName: \"Run npm test\",",
"newLineNumber": 141,
"oldLineNumber": null
},
{
"type": "addition",
"content": " category: \"test\",",
"newLineNumber": 142,
"oldLineNumber": null
},
{
"type": "addition",
"content": " detailsUrl: \"https://github.com/openclaw/firmcode/actions/runs/1/job/2\"",
"newLineNumber": 143,
"oldLineNumber": null
},
{
"type": "addition",
"content": " },",
"newLineNumber": 144,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rootCauseSummary: \"AssertionError: expected 201 to equal 200 in the API controller spec.\",",
"newLineNumber": 145,
"oldLineNumber": null
},
{
"type": "addition",
"content": " flakySuspected: true,",
"newLineNumber": 146,
"oldLineNumber": null
},
{
"type": "addition",
"content": " suggestedFix: \"Update the API test expectation or restore created responses.\",",
"newLineNumber": 147,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status: \"failed\",",
"newLineNumber": 148,
"oldLineNumber": null
},
{
"type": "addition",
"content": " createdAt: \"2026-05-23T10:06:00.000Z\"",
"newLineNumber": 149,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 150,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ]",
"newLineNumber": 151,
"oldLineNumber": null
},
{
"type": "addition",
"content": "};",
"newLineNumber": 152,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 153,
"oldLineNumber": null
},
{
"type": "addition",
"content": "const emptyCiFailures: CiFailureListResponse = {",
"newLineNumber": 154,
"oldLineNumber": null
},
{
"type": "addition",
"content": " filters: {},",
"newLineNumber": 155,
"oldLineNumber": null
},
{
"type": "addition",
"content": " pagination: {",
"newLineNumber": 156,
"oldLineNumber": null
},
{
"type": "addition",
"content": " limit: 50,",
"newLineNumber": 157,
"oldLineNumber": null
},
{
"type": "addition",
"content": " returned: 0",
"newLineNumber": 158,
"oldLineNumber": null
},
{
"type": "addition",
"content": " },",
"newLineNumber": 159,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ciFailures: []",
"newLineNumber": 160,
"oldLineNumber": null
},
{
"type": "addition",
"content": "};",
"newLineNumber": 161,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 162,
"oldLineNumber": null
},
{
"type": "addition",
"content": "const ciFailureDetail: CiFailureDetailResponse = {",
"newLineNumber": 163,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ...ciFailures.ciFailures[0],",
"newLineNumber": 164,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rootCause: \"The unit test expected HTTP 201 but the API returned 200.\",",
"newLineNumber": 165,
"oldLineNumber": null
},
{
"type": "addition",
"content": " suggestedFixes: [",
"newLineNumber": 166,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {",
"newLineNumber": 167,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: `${CI_FAILURE_ID}:fix:1`,",
"newLineNumber": 168,
"oldLineNumber": null
},
{
"type": "addition",
"content": " text: \"Update the API test expectation or restore created responses.\"",
"newLineNumber": 169,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 170,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ],",
"newLineNumber": 171,
"oldLineNumber": null
},
{
"type": "addition",
"content": " failedJobs: [",
"newLineNumber": 172,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ciFailures.ciFailures[0].failedJob,",
"newLineNumber": 173,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {",
"newLineNumber": 174,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: \"lint\",",
"newLineNumber": 175,
"oldLineNumber": null
},
{
"type": "addition",
"content": " workflowName: \"Unit tests\",",
"newLineNumber": 176,
"oldLineNumber": null
},
{
"type": "addition",
"content": " jobName: \"npm run lint\",",
"newLineNumber": 177,
"oldLineNumber": null
},
{
"type": "addition",
"content": " checkRunId: 12346,",
"newLineNumber": 178,
"oldLineNumber": null
},
{
"type": "addition",
"content": " conclusion: \"failure\",",
"newLineNumber": 179,
"oldLineNumber": null
},
{
"type": "addition",
"content": " stepName: \"TypeScript\",",
"newLineNumber": 180,
"oldLineNumber": null
},
{
"type": "addition",
"content": " category: \"typecheck\",",
"newLineNumber": 181,
"oldLineNumber": null
},
{
"type": "addition",
"content": " detailsUrl: null",
"newLineNumber": 182,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 183,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ],",
"newLineNumber": 184,
"oldLineNumber": null
},
{
"type": "addition",
"content": " relatedReviewRun: {",
"newLineNumber": 185,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: \"00000000-0000-4000-8000-000000000401\",",
"newLineNumber": 186,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status: \"failed\",",
"newLineNumber": 187,
"oldLineNumber": null
},
{
"type": "addition",
"content": " createdAt: \"2026-05-23T10:00:00.000Z\",",
"newLineNumber": 188,
"oldLineNumber": null
},
{
"type": "addition",
"content": " detailUrl: \"/api/review-runs/00000000-0000-4000-8000-000000000401\"",
"newLineNumber": 189,
"oldLineNumber": null
},
{
"type": "addition",
"content": " },",
"newLineNumber": 190,
"oldLineNumber": null
},
{
"type": "addition",
"content": " relatedArtifacts: [",
"newLineNumber": 191,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {",
"newLineNumber": 192,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: \"00000000-0000-4000-8000-000000000501\",",
"newLineNumber": 193,
"oldLineNumber": null
},
{
"type": "addition",
"content": " artifactType: \"ci_failure_explanation\",",
"newLineNumber": 194,
"oldLineNumber": null
},
{
"type": "addition",
"content": " storageKey: \"artifacts/run-401/ci-failure-explanation.json\",",
"newLineNumber": 195,
"oldLineNumber": null
},
{
"type": "addition",
"content": " metadata: { schemaVersion: \"ci-failure-explanation/v1\" },",
"newLineNumber": 196,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessAllowed: true,",
"newLineNumber": 197,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessRequiredRole: \"developer\",",
"newLineNumber": 198,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessUrl:",
"newLineNumber": 199,
"oldLineNumber": null
},
{
"type": "addition",
"content": " \"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts/00000000-0000-4000-8000-000000000501/raw\",",
"newLineNumber": 200,
"oldLineNumber": null
},
{
"type": "addition",
"content": " createdAt: \"2026-05-23T10:06:00.000Z\"",
"newLineNumber": 201,
"oldLineNumber": null
},
{
"type": "addition",
"content": " },",
"newLineNumber": 202,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {",
"newLineNumber": 203,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: \"00000000-0000-4000-8000-000000000502\",",
"newLineNumber": 204,
"oldLineNumber": null
},
{
"type": "addition",
"content": " artifactType: \"ci_log\",",
"newLineNumber": 205,
"oldLineNumber": null
},
{
"type": "addition",
"content": " storageKey: \"artifacts/run-401/ci-log.json\",",
"newLineNumber": 206,
"oldLineNumber": null
},
{
"type": "addition",
"content": " metadata: { schemaVersion: \"ci-log-artifact/v1\", redacted: true, logsCount: 1 },",
"newLineNumber": 207,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessAllowed: true,",
"newLineNumber": 208,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessRequiredRole: \"developer\",",
"newLineNumber": 209,
"oldLineNumber": null
},
{
"type": "addition",
"content": " rawAccessUrl:",
"newLineNumber": 210,
"oldLineNumber": null
},
{
"type": "addition",
"content": " \"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts/00000000-0000-4000-8000-000000000502/raw\",",
"newLineNumber": 211,
"oldLineNumber": null
},
{
"type": "addition",
"content": " createdAt: \"2026-05-23T10:05:00.000Z\"",
"newLineNumber": 212,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 213,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ],",
"newLineNumber": 214,
"oldLineNumber": null
},
{
"type": "addition",
"content": " logExcerpts: [",
"newLineNumber": 215,
"oldLineNumber": null
},
{
"type": "addition",
"content": " {",
"newLineNumber": 216,
"oldLineNumber": null
},
{
"type": "addition",
"content": " id: `${CI_FAILURE_ID}:excerpt:1`,",
"newLineNumber": 217,
"oldLineNumber": null
},
{
"type": "addition",
"content": " source: \"ci_log\",",
"newLineNumber": 218,
"oldLineNumber": null
},
{
"type": "addition",
"content": " title: \"Run npm test\",",
"newLineNumber": 219,
"oldLineNumber": null
},
{
"type": "addition",
"content": " excerpt: \"AssertionError: expected 201 to equal 200\\nTOKEN=[REDACTED_SECRET]\",",
"newLineNumber": 220,
"oldLineNumber": null
},
{
"type": "addition",
"content": " artifactId: \"00000000-0000-4000-8000-000000000502\",",
"newLineNumber": 221,
"oldLineNumber": null
},
{
"type": "addition",
"content": " storageKey: null,",
"newLineNumber": 222,
"oldLineNumber": null
},
{
"type": "addition",
"content": " redacted: true,",
"newLineNumber": 223,
"oldLineNumber": null
},
{
"type": "addition",
"content": " truncated: false,",
"newLineNumber": 224,
"oldLineNumber": null
},
{
"type": "addition",
"content": " collapsed: true,",
"newLineNumber": 225,
"oldLineNumber": null
},
{
"type": "addition",
"content": " createdAt: \"2026-05-23T10:06:00.000Z\"",
"newLineNumber": 226,
"oldLineNumber": null
},
{
"type": "addition",
"content": " }",
"newLineNumber": 227,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ],",
"newLineNumber": 228,
"oldLineNumber": null
},
{
"type": "addition",
"content": " unavailableLogNotes: []",
"newLineNumber": 229,
"oldLineNumber": null
},
{
"type": "addition",
"content": "};",
"newLineNumber": 230,
"oldLineNumber": null
}
],
"newStart": 1,
"oldStart": 0,
"newLineCount": 230,
"oldLineCount": 0,
"sectionHeader": ""
}
],
"patch": "@@ -0,0 +1,230 @@\n+import React from \"react\";\n+import { renderToString } from \"react-dom/server\";\n+import type { CiFailureDetailResponse, CiFailureListResponse } from \"@firmcode/shared\";\n+import { CiFailureDetailView, CiFailuresView } from \"../components/dashboard/ci-failures-view\";\n+\n+describe(\"CiFailuresView\", () => {\n+ it(\"renders loading, empty, and error states\", () => {\n+ expect(renderToString(<CiFailuresView state={{ status: \"loading\" }} />)).toContain(\"Loading CI failures\");\n+ expect(renderToString(<CiFailuresView state={{ status: \"empty\", data: emptyCiFailures }} />)).toContain(\n+ \"No CI failures match these filters\"\n+ );\n+ expect(renderToString(<CiFailuresView state={{ status: \"error\", message: \"API unavailable\" }} />)).toContain(\n+ \"CI failures could not be loaded\"\n+ );\n+ });\n+\n+ it(\"renders selected filters for repository, run status, flaky status, and dates\", () => {\n+ const html = renderToString(<CiFailuresView state={{ status: \"populated\", data: ciFailures }} />);\n+\n+ expect(html).toContain('action=\"/ci-failures\"');\n+ expect(html).toContain('name=\"repository\"');\n+ expect(html).toContain('value=\"openclaw/firmcode\"');\n+ expect(html).toContain('name=\"status\"');\n+ expect(html).toContain('value=\"failed\" selected=\"\">Failed');\n+ expect(html).toContain('name=\"flaky\"');\n+ expect(html).toContain('value=\"true\" selected=\"\">Suspected');\n+ expect(html).toContain('name=\"dateFrom\"');\n+ expect(html).toContain('value=\"2026-05-20\"');\n+ expect(html).toContain('name=\"dateTo\"');\n+ expect(html).toContain('value=\"2026-05-24\"');\n+ });\n+\n+ it(\"renders populated CI failure rows and responsive list surfaces\", () => {\n+ const html = renderToString(<CiFailuresView state={{ status: \"populated\", data: ciFailures }} />);\n+\n+ expect(html).toContain(\"Broken checks queue\");\n+ expect(html).toContain(\"openclaw/firmcode\");\n+ expect(html).toContain(\"Unit tests\");\n+ expect(html).toContain(\"npm test\");\n+ expect(html).toContain(\"AssertionError\");\n+ expect(html).toContain(\"Update the API test expectation\");\n+ expect(html).toContain('href=\"/ci-failures/00000000-0000-4000-8000-000000000501%3Aunit-tests\"');\n+ expect(html).toContain(\"md:hidden\");\n+ expect(html).toContain(\"hidden overflow-x-auto md:block\");\n+ });\n+});\n+\n+describe(\"CiFailureDetailView\", () => {\n+ it(\"renders loading, empty, and error states\", () => {\n+ expect(renderToString(<CiFailureDetailView state={{ status: \"loading\" }} />)).toContain(\"Loading CI failure detail\");\n+ expect(renderToString(<CiFailureDetailView state={{ status: \"empty\" }} />)).toContain(\"The CI failure could not be found.\");\n+ expect(renderToString(<CiFailureDetailView state={{ status: \"error\", message: \"API unavailable\" }} />)).toContain(\n+ \"CI failures could not be loaded\"\n+ );\n+ });\n+\n+ it(\"renders failure summary, root cause, suggested fixes, failed jobs, and related links\", () => {\n+ const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: ciFailureDetail }} />);\n+\n+ expect(html).toContain(\"Failure Summary\");\n+ expect(html).toContain(\"Likely root cause\");\n+ expect(html).toContain(\"The unit test expected HTTP 201 but the API returned 200.\");\n+ expect(html).toContain(\"Suggested Fixes\");\n+ expect(html).toContain(\"Update the API test expectation or restore created responses.\");\n+ expect(html).toContain(\"Failed Jobs\");\n+ expect(html).toContain(\"Related Links\");\n+ expect(html).toContain('href=\"/review-runs/00000000-0000-4000-8000-000000000401\"');\n+ expect(html).toContain('href=\"/pull-requests/pr-7\"');\n+ });\n+\n+ it(\"renders redacted log excerpts collapsed by default\", () => {\n+ const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: ciFailureDetail }} />);\n+\n+ expect(html).toContain(\"Redacted Log Excerpts\");\n+ expect(html).toContain(\"collapsed excerpts available by default\");\n+ expect(html).toContain(\"redacted\");\n+ expect(html).toContain(\"[REDACTED_SECRET]\");\n+ expect(html).toContain(\"<details\");\n+ expect(html).not.toContain(\"<details open\");\n+ });\n+\n+ it(\"disables unauthorized raw artifact controls without exposing storage keys\", () => {\n+ const viewerDetail: CiFailureDetailResponse = {\n+ ...ciFailureDetail,\n+ relatedArtifacts: ciFailureDetail.relatedArtifacts.map((artifact) => ({\n+ ...artifact,\n+ storageKey: null,\n+ rawAccessAllowed: false,\n+ rawAccessUrl: null\n+ }))\n+ };\n+ const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: viewerDetail }} />);\n+\n+ expect(html).toContain(\"Raw artifact restricted\");\n+ expect(html).toContain(\"disabled=\\\"\\\"\");\n+ expect(html).not.toContain(\"artifacts/run-401/ci-log.json\");\n+ expect(html).not.toContain('href=\"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts');\n+ });\n+\n+ it(\"shows raw artifact links only when the API marks access as allowed\", () => {\n+ const html = renderToString(<CiFailureDetailView state={{ status: \"populated\", data: ciFailureDetail }} />);\n+\n+ expect(html).toContain(\"Raw artifact\");\n+ expect(html).toContain(\n+ 'href=\"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts/00000000-0000-4000-8000-000000000502/raw\"'\n+ );\n+ expect(html).not.toContain(\"raw model output\");\n+ expect(html).not.toContain(\"raw Semgrep output\");\n+ });\n+});\n+\n+const CI_FAILURE_ID = \"00000000-0000-4000-8000-000000000501:unit-tests\";\n+\n+const ciFailures: CiFailureListResponse = {\n+ filters: {\n+ repository: \"openclaw/firmcode\",\n+ status: \"failed\",\n+ flaky: true,\n+ dateFrom: \"2026-05-20\",\n+ dateTo: \"2026-05-24\"\n+ },\n+ pagination: {\n+ limit: 50,\n+ returned: 1\n+ },\n+ ciFailures: [\n+ {\n+ id: CI_FAILURE_ID,\n+ repositoryId: \"repo-1\",\n+ repositoryFullName: \"openclaw/firmcode\",\n+ pullRequestId: \"pr-7\",\n+ pullRequestNumber: 7,\n+ pullRequestTitle: \"Add CI failure dashboard\",\n+ reviewRunId: \"00000000-0000-4000-8000-000000000401\",\n+ failedJob: {\n+ id: \"unit-tests\",\n+ workflowName: \"Unit tests\",\n+ jobName: \"npm test\",\n+ checkRunId: 12345,\n+ conclusion: \"failure\",\n+ stepName: \"Run npm test\",\n+ category: \"test\",\n+ detailsUrl: \"https://github.com/openclaw/firmcode/actions/runs/1/job/2\"\n+ },\n+ rootCauseSummary: \"AssertionError: expected 201 to equal 200 in the API controller spec.\",\n+ flakySuspected: true,\n+ suggestedFix: \"Update the API test expectation or restore created responses.\",\n+ status: \"failed\",\n+ createdAt: \"2026-05-23T10:06:00.000Z\"\n+ }\n+ ]\n+};\n+\n+const emptyCiFailures: CiFailureListResponse = {\n+ filters: {},\n+ pagination: {\n+ limit: 50,\n+ returned: 0\n+ },\n+ ciFailures: []\n+};\n+\n+const ciFailureDetail: CiFailureDetailResponse = {\n+ ...ciFailures.ciFailures[0],\n+ rootCause: \"The unit test expected HTTP 201 but the API returned 200.\",\n+ suggestedFixes: [\n+ {\n+ id: `${CI_FAILURE_ID}:fix:1`,\n+ text: \"Update the API test expectation or restore created responses.\"\n+ }\n+ ],\n+ failedJobs: [\n+ ciFailures.ciFailures[0].failedJob,\n+ {\n+ id: \"lint\",\n+ workflowName: \"Unit tests\",\n+ jobName: \"npm run lint\",\n+ checkRunId: 12346,\n+ conclusion: \"failure\",\n+ stepName: \"TypeScript\",\n+ category: \"typecheck\",\n+ detailsUrl: null\n+ }\n+ ],\n+ relatedReviewRun: {\n+ id: \"00000000-0000-4000-8000-000000000401\",\n+ status: \"failed\",\n+ createdAt: \"2026-05-23T10:00:00.000Z\",\n+ detailUrl: \"/api/review-runs/00000000-0000-4000-8000-000000000401\"\n+ },\n+ relatedArtifacts: [\n+ {\n+ id: \"00000000-0000-4000-8000-000000000501\",\n+ artifactType: \"ci_failure_explanation\",\n+ storageKey: \"artifacts/run-401/ci-failure-explanation.json\",\n+ metadata: { schemaVersion: \"ci-failure-explanation/v1\" },\n+ rawAccessAllowed: true,\n+ rawAccessRequiredRole: \"developer\",\n+ rawAccessUrl:\n+ \"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts/00000000-0000-4000-8000-000000000501/raw\",\n+ createdAt: \"2026-05-23T10:06:00.000Z\"\n+ },\n+ {\n+ id: \"00000000-0000-4000-8000-000000000502\",\n+ artifactType: \"ci_log\",\n+ storageKey: \"artifacts/run-401/ci-log.json\",\n+ metadata: { schemaVersion: \"ci-log-artifact/v1\", redacted: true, logsCount: 1 },\n+ rawAccessAllowed: true,\n+ rawAccessRequiredRole: \"developer\",\n+ rawAccessUrl:\n+ \"/api/review-runs/00000000-0000-4000-8000-000000000401/artifacts/00000000-0000-4000-8000-000000000502/raw\",\n+ createdAt: \"2026-05-23T10:05:00.000Z\"\n+ }\n+ ],\n+ logExcerpts: [\n+ {\n+ id: `${CI_FAILURE_ID}:excerpt:1`,\n+ source: \"ci_log\",\n+ title: \"Run npm test\",\n+ excerpt: \"AssertionError: expected 201 to equal 200\\nTOKEN=[REDACTED_SECRET]\",\n+ artifactId: \"00000000-0000-4000-8000-000000000502\",\n+ storageKey: null,\n+ redacted: true,\n+ truncated: false,\n+ collapsed: true,\n+ createdAt: \"2026-05-23T10:06:00.000Z\"\n+ }\n+ ],\n+ unavailableLogNotes: []\n+};",
"status": "added",
"language": "typescript",
"additions": 230,
"deletions": 0,
"sizeBytes": 8921,
"previousPath": null,
"changedNewLines": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
124,
125,
126,
127,
128,
129,
130,
131,
132,
133,
134,
135,
136,
137,
138,
139,
140,
141,
142,
143,
144,
145,
146,
147,
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229,
230
],
"headContentSha256": "3eec478c376d4633021b5f14b0d89a2443ad4691409fde0ae86546a01f29f234"
},
{
"path": "apps/web/tests/dashboard-data.spec.ts",
"hunks": [
{
"lines": [
{
"type": "context",
"content": "import type { FindingsListResponse } from \"@firmcode/shared\";",
"newLineNumber": 1,
"oldLineNumber": 1
},
{
"type": "context",
"content": "import {",
"newLineNumber": 2,
"oldLineNumber": 2
},
{
"type": "context",
"content": " loadBillingState,",
"newLineNumber": 3,
"oldLineNumber": 3
},
{
"type": "addition",
"content": " loadCiFailureDetailState,",
"newLineNumber": 4,
"oldLineNumber": null
},
{
"type": "addition",
"content": " loadCiFailuresState,",
"newLineNumber": 5,
"oldLineNumber": null
},
{
"type": "context",
"content": " loadFindingsState,",
"newLineNumber": 6,
"oldLineNumber": 4
},
{
"type": "context",
"content": " loadGitHubInstallationsState,",
"newLineNumber": 7,
"oldLineNumber": 5
},
{
"type": "context",
"content": " loadGitHubRepositoryControlsState,",
"newLineNumber": 8,
"oldLineNumber": 6
}
],
"newStart": 1,
"oldStart": 1,
"newLineCount": 8,
"oldLineCount": 6,
"sectionHeader": ""
},
{
"lines": [
{
"type": "context",
"content": " expect(headers.get(\"x-firmcode-user-id\")).toBe(\"user-1\");",
"newLineNumber": 200,
"oldLineNumber": 198
},
{
"type": "context",
"content": " });",
"newLineNumber": 201,
"oldLineNumber": 199
},
{
"type": "context",
"content": "",
"newLineNumber": 202,
"oldLineNumber": 200
},
{
"type": "addition",
"content": " it(\"maps CI failure filters into the authenticated API query string\", async () => {",
"newLineNumber": 203,
"oldLineNumber": null
},
{
"type": "addition",
"content": " process.env.NEXT_PUBLIC_API_URL = \"http://dashboard-api.test\";",
"newLineNumber": 204,
"oldLineNumber": null
},
{
"type": "addition",
"content": " process.env.FIRMCODE_DASHBOARD_WORKSPACE_ID = \"workspace-1\";",
"newLineNumber": 205,
"oldLineNumber": null
},
{
"type": "addition",
"content": " process.env.FIRMCODE_DASHBOARD_CLERK_USER_ID = \"user-1\";",
"newLineNumber": 206,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const fetcher = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) =>",
"newLineNumber": 207,
"oldLineNumber": null
},
{
"type": "addition",
"content": " jsonResponse({ ciFailures: [{ id: \"failure-1\" }], filters: {}, pagination: { limit: 25, returned: 1 } })",
"newLineNumber": 208,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 209,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 210,
"oldLineNumber": null
},
{
"type": "addition",
"content": " vi.stubGlobal(\"fetch\", fetcher);",
"newLineNumber": 211,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 212,
"oldLineNumber": null
},
{
"type": "addition",
"content": " await expect(",
"newLineNumber": 213,
"oldLineNumber": null
},
{
"type": "addition",
"content": " loadCiFailuresState({",
"newLineNumber": 214,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repository: \"openclaw/firmcode\",",
"newLineNumber": 215,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositoryId: \"repo-1\",",
"newLineNumber": 216,
"oldLineNumber": null
},
{
"type": "addition",
"content": " status: \"failed\",",
"newLineNumber": 217,
"oldLineNumber": null
},
{
"type": "addition",
"content": " flaky: \"false\",",
"newLineNumber": 218,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateFrom: \"2026-05-20\",",
"newLineNumber": 219,
"oldLineNumber": null
},
{
"type": "addition",
"content": " dateTo: \"2026-05-24\",",
"newLineNumber": 220,
"oldLineNumber": null
},
{
"type": "addition",
"content": " limit: \"25\"",
"newLineNumber": 221,
"oldLineNumber": null
},
{
"type": "addition",
"content": " })",
"newLineNumber": 222,
"oldLineNumber": null
},
{
"type": "addition",
"content": " ).resolves.toMatchObject({ status: \"populated\" });",
"newLineNumber": 223,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 224,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const url = new URL(String(fetcher.mock.calls[0]?.[0]));",
"newLineNumber": 225,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const init = fetcher.mock.calls[0]?.[1] as RequestInit;",
"newLineNumber": 226,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const headers = new Headers(init.headers);",
"newLineNumber": 227,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 228,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.pathname).toBe(\"/api/ci-failures\");",
"newLineNumber": 229,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.searchParams.get(\"repository\")).toBe(\"openclaw/firmcode\");",
"newLineNumber": 230,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.searchParams.get(\"repositoryId\")).toBe(\"repo-1\");",
"newLineNumber": 231,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.searchParams.get(\"status\")).toBe(\"failed\");",
"newLineNumber": 232,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.searchParams.get(\"flaky\")).toBe(\"false\");",
"newLineNumber": 233,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.searchParams.get(\"dateFrom\")).toBe(\"2026-05-20\");",
"newLineNumber": 234,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.searchParams.get(\"dateTo\")).toBe(\"2026-05-24\");",
"newLineNumber": 235,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.searchParams.get(\"limit\")).toBe(\"25\");",
"newLineNumber": 236,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(headers.get(\"x-firmcode-workspace-id\")).toBe(\"workspace-1\");",
"newLineNumber": 237,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(headers.get(\"x-firmcode-user-id\")).toBe(\"user-1\");",
"newLineNumber": 238,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 239,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 240,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"fetches CI failure detail with dashboard auth headers and maps 404 to empty\", async () => {",
"newLineNumber": 241,
"oldLineNumber": null
},
{
"type": "addition",
"content": " process.env.NEXT_PUBLIC_API_URL = \"http://dashboard-api.test\";",
"newLineNumber": 242,
"oldLineNumber": null
},
{
"type": "addition",
"content": " process.env.FIRMCODE_DASHBOARD_WORKSPACE_ID = \"workspace-1\";",
"newLineNumber": 243,
"oldLineNumber": null
},
{
"type": "addition",
"content": " process.env.FIRMCODE_DASHBOARD_CLERK_USER_ID = \"user-1\";",
"newLineNumber": 244,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const fetcher = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {",
"newLineNumber": 245,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const pathname = new URL(String(input)).pathname;",
"newLineNumber": 246,
"oldLineNumber": null
},
{
"type": "addition",
"content": " return pathname.endsWith(\"/missing\") ? jsonResponse({ message: \"CI failure not found\" }, 404) : jsonResponse({ id: \"failure-1\" });",
"newLineNumber": 247,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 248,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 249,
"oldLineNumber": null
},
{
"type": "addition",
"content": " vi.stubGlobal(\"fetch\", fetcher);",
"newLineNumber": 250,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 251,
"oldLineNumber": null
},
{
"type": "addition",
"content": " await expect(loadCiFailureDetailState(\"failure-1%3Aunit-tests\")).resolves.toMatchObject({ status: \"populated\" });",
"newLineNumber": 252,
"oldLineNumber": null
},
{
"type": "addition",
"content": " await expect(loadCiFailureDetailState(\"missing\")).resolves.toEqual({ status: \"empty\" });",
"newLineNumber": 253,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 254,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const url = new URL(String(fetcher.mock.calls[0]?.[0]));",
"newLineNumber": 255,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const init = fetcher.mock.calls[0]?.[1] as RequestInit;",
"newLineNumber": 256,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const headers = new Headers(init.headers);",
"newLineNumber": 257,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 258,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(url.pathname).toBe(\"/api/ci-failures/failure-1%3Aunit-tests\");",
"newLineNumber": 259,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(headers.get(\"x-firmcode-workspace-id\")).toBe(\"workspace-1\");",
"newLineNumber": 260,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(headers.get(\"x-firmcode-user-id\")).toBe(\"user-1\");",
"newLineNumber": 261,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 262,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 263,
"oldLineNumber": null
},
{
"type": "context",
"content": " it(\"fetches repository detail with dashboard auth headers and maps 404 to empty\", async () => {",
"newLineNumber": 264,
"oldLineNumber": 201
},
{
"type": "context",
"content": " process.env.NEXT_PUBLIC_API_URL = \"http://dashboard-api.test\";",
"newLineNumber": 265,
"oldLineNumber": 202
},
{
"type": "context",
"content": " process.env.FIRMCODE_DASHBOARD_WORKSPACE_ID = \"workspace-1\";",
"newLineNumber": 266,
"oldLineNumber": 203
}
],
"newStart": 200,
"oldStart": 198,
"newLineCount": 67,
"oldLineCount": 6,
"sectionHeader": "describe(\"dashboard findings data loader\", () => {"
}
],
"patch": "@@ -1,6 +1,8 @@\n import type { FindingsListResponse } from \"@firmcode/shared\";\n import {\n loadBillingState,\n+ loadCiFailureDetailState,\n+ loadCiFailuresState,\n loadFindingsState,\n loadGitHubInstallationsState,\n loadGitHubRepositoryControlsState,\n@@ -198,6 +200,67 @@ describe(\"dashboard findings data loader\", () => {\n expect(headers.get(\"x-firmcode-user-id\")).toBe(\"user-1\");\n });\n \n+ it(\"maps CI failure filters into the authenticated API query string\", async () => {\n+ process.env.NEXT_PUBLIC_API_URL = \"http://dashboard-api.test\";\n+ process.env.FIRMCODE_DASHBOARD_WORKSPACE_ID = \"workspace-1\";\n+ process.env.FIRMCODE_DASHBOARD_CLERK_USER_ID = \"user-1\";\n+ const fetcher = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) =>\n+ jsonResponse({ ciFailures: [{ id: \"failure-1\" }], filters: {}, pagination: { limit: 25, returned: 1 } })\n+ );\n+\n+ vi.stubGlobal(\"fetch\", fetcher);\n+\n+ await expect(\n+ loadCiFailuresState({\n+ repository: \"openclaw/firmcode\",\n+ repositoryId: \"repo-1\",\n+ status: \"failed\",\n+ flaky: \"false\",\n+ dateFrom: \"2026-05-20\",\n+ dateTo: \"2026-05-24\",\n+ limit: \"25\"\n+ })\n+ ).resolves.toMatchObject({ status: \"populated\" });\n+\n+ const url = new URL(String(fetcher.mock.calls[0]?.[0]));\n+ const init = fetcher.mock.calls[0]?.[1] as RequestInit;\n+ const headers = new Headers(init.headers);\n+\n+ expect(url.pathname).toBe(\"/api/ci-failures\");\n+ expect(url.searchParams.get(\"repository\")).toBe(\"openclaw/firmcode\");\n+ expect(url.searchParams.get(\"repositoryId\")).toBe(\"repo-1\");\n+ expect(url.searchParams.get(\"status\")).toBe(\"failed\");\n+ expect(url.searchParams.get(\"flaky\")).toBe(\"false\");\n+ expect(url.searchParams.get(\"dateFrom\")).toBe(\"2026-05-20\");\n+ expect(url.searchParams.get(\"dateTo\")).toBe(\"2026-05-24\");\n+ expect(url.searchParams.get(\"limit\")).toBe(\"25\");\n+ expect(headers.get(\"x-firmcode-workspace-id\")).toBe(\"workspace-1\");\n+ expect(headers.get(\"x-firmcode-user-id\")).toBe(\"user-1\");\n+ });\n+\n+ it(\"fetches CI failure detail with dashboard auth headers and maps 404 to empty\", async () => {\n+ process.env.NEXT_PUBLIC_API_URL = \"http://dashboard-api.test\";\n+ process.env.FIRMCODE_DASHBOARD_WORKSPACE_ID = \"workspace-1\";\n+ process.env.FIRMCODE_DASHBOARD_CLERK_USER_ID = \"user-1\";\n+ const fetcher = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {\n+ const pathname = new URL(String(input)).pathname;\n+ return pathname.endsWith(\"/missing\") ? jsonResponse({ message: \"CI failure not found\" }, 404) : jsonResponse({ id: \"failure-1\" });\n+ });\n+\n+ vi.stubGlobal(\"fetch\", fetcher);\n+\n+ await expect(loadCiFailureDetailState(\"failure-1%3Aunit-tests\")).resolves.toMatchObject({ status: \"populated\" });\n+ await expect(loadCiFailureDetailState(\"missing\")).resolves.toEqual({ status: \"empty\" });\n+\n+ const url = new URL(String(fetcher.mock.calls[0]?.[0]));\n+ const init = fetcher.mock.calls[0]?.[1] as RequestInit;\n+ const headers = new Headers(init.headers);\n+\n+ expect(url.pathname).toBe(\"/api/ci-failures/failure-1%3Aunit-tests\");\n+ expect(headers.get(\"x-firmcode-workspace-id\")).toBe(\"workspace-1\");\n+ expect(headers.get(\"x-firmcode-user-id\")).toBe(\"user-1\");\n+ });\n+\n it(\"fetches repository detail with dashboard auth headers and maps 404 to empty\", async () => {\n process.env.NEXT_PUBLIC_API_URL = \"http://dashboard-api.test\";\n process.env.FIRMCODE_DASHBOARD_WORKSPACE_ID = \"workspace-1\";",
"status": "modified",
"language": "typescript",
"additions": 63,
"deletions": 0,
"sizeBytes": 19935,
"previousPath": null,
"changedNewLines": [
4,
5,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229,
230,
231,
232,
233,
234,
235,
236,
237,
238,
239,
240,
241,
242,
243,
244,
245,
246,
247,
248,
249,
250,
251,
252,
253,
254,
255,
256,
257,
258,
259,
260,
261,
262,
263
],
"headContentSha256": "387f6ae446803f7492cf760b61c989933e4bd9c7159237f44be88bf681e24e0b"
},
{
"path": "apps/web/tests/dashboard-navigation.spec.tsx",
"hunks": [
{
"lines": [
{
"type": "context",
"content": " expect(html).not.toContain(\"Pull Requests</span>\");",
"newLineNumber": 61,
"oldLineNumber": 61
},
{
"type": "context",
"content": " });",
"newLineNumber": 62,
"oldLineNumber": 62
},
{
"type": "context",
"content": "",
"newLineNumber": 63,
"oldLineNumber": 63
},
{
"type": "addition",
"content": " it(\"enables CI Failures sidebar navigation once the route exists\", () => {",
"newLineNumber": 64,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(",
"newLineNumber": 65,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <DashboardShell activeItem=\"CI Failures\">",
"newLineNumber": 66,
"oldLineNumber": null
},
{
"type": "addition",
"content": " <main>CI failures body</main>",
"newLineNumber": 67,
"oldLineNumber": null
},
{
"type": "addition",
"content": " </DashboardShell>",
"newLineNumber": 68,
"oldLineNumber": null
},
{
"type": "addition",
"content": " );",
"newLineNumber": 69,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 70,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('href=\"/ci-failures\"');",
"newLineNumber": 71,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\">CI Failures</a>\");",
"newLineNumber": 72,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('aria-current=\"page\"');",
"newLineNumber": 73,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).not.toContain(\"CI Failures</span>\");",
"newLineNumber": 74,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 75,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 76,
"oldLineNumber": null
},
{
"type": "context",
"content": " it(\"routes overview pull request links to the implemented queue\", () => {",
"newLineNumber": 77,
"oldLineNumber": 64
},
{
"type": "context",
"content": " const data = buildOverviewDashboardData({",
"newLineNumber": 78,
"oldLineNumber": 65
},
{
"type": "context",
"content": " repositories: repositoryList.repositories,",
"newLineNumber": 79,
"oldLineNumber": 66
}
],
"newStart": 61,
"oldStart": 61,
"newLineCount": 19,
"oldLineCount": 6,
"sectionHeader": "describe(\"dashboard navigation\", () => {"
},
{
"lines": [
{
"type": "context",
"content": " expect(html).toContain('href=\"/pull-requests\"');",
"newLineNumber": 85,
"oldLineNumber": 72
},
{
"type": "context",
"content": " expect(html).toContain(\">Pull requests</a>\");",
"newLineNumber": 86,
"oldLineNumber": 73
},
{
"type": "context",
"content": " });",
"newLineNumber": 87,
"oldLineNumber": 74
},
{
"type": "addition",
"content": "",
"newLineNumber": 88,
"oldLineNumber": null
},
{
"type": "addition",
"content": " it(\"routes overview CI failure needs-attention links to the implemented queue\", () => {",
"newLineNumber": 89,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const data = buildOverviewDashboardData({",
"newLineNumber": 90,
"oldLineNumber": null
},
{
"type": "addition",
"content": " repositories: repositoryList.repositories,",
"newLineNumber": 91,
"oldLineNumber": null
},
{
"type": "addition",
"content": " reviewRuns: [],",
"newLineNumber": 92,
"oldLineNumber": null
},
{
"type": "addition",
"content": " now: new Date(\"2026-05-24T12:00:00.000Z\")",
"newLineNumber": 93,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 94,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const html = renderToString(<OverviewView state={{ status: \"populated\", data }} />);",
"newLineNumber": 95,
"oldLineNumber": null
},
{
"type": "addition",
"content": "",
"newLineNumber": 96,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain('href=\"/ci-failures\"');",
"newLineNumber": 97,
"oldLineNumber": null
},
{
"type": "addition",
"content": " expect(html).toContain(\"CI failure\");",
"newLineNumber": 98,
"oldLineNumber": null
},
{
"type": "addition",
"content": " });",
"newLineNumber": 99,
"oldLineNumber": null
},
{
"type": "context",
"content": "});",
"newLineNumber": 100,
"oldLineNumber": 75
},
{
"type": "context",
"content": "",
"newLineNumber": 101,
"oldLineNumber": 76
},
{
"type": "context",
"content": "const settings = {",
"newLineNumber": 102,
"oldLineNumber": 77
}
],
"newStart": 85,
"oldStart": 72,
"newLineCount": 18,
"oldLineCount": 6,
"sectionHeader": "describe(\"dashboard navigation\", () => {"
}
],
"patch": "@@ -61,6 +61,19 @@ describe(\"dashboard navigation\", () => {\n expect(html).not.toContain(\"Pull Requests</span>\");\n });\n \n+ it(\"enables CI Failures sidebar navigation once the route exists\", () => {\n+ const html = renderToString(\n+ <DashboardShell activeItem=\"CI Failures\">\n+ <main>CI failures body</main>\n+ </DashboardShell>\n+ );\n+\n+ expect(html).toContain('href=\"/ci-failures\"');\n+ expect(html).toContain(\">CI Failures</a>\");\n+ expect(html).toContain('aria-current=\"page\"');\n+ expect(html).not.toContain(\"CI Failures</span>\");\n+ });\n+\n it(\"routes overview pull request links to the implemented queue\", () => {\n const data = buildOverviewDashboardData({\n repositories: repositoryList.repositories,\n@@ -72,6 +85,18 @@ describe(\"dashboard navigation\", () => {\n expect(html).toContain('href=\"/pull-requests\"');\n expect(html).toContain(\">Pull requests</a>\");\n });\n+\n+ it(\"routes overview CI failure needs-attention links to the implemented queue\", () => {\n+ const data = buildOverviewDashboardData({\n+ repositories: repositoryList.repositories,\n+ reviewRuns: [],\n+ now: new Date(\"2026-05-24T12:00:00.000Z\")\n+ });\n+ const html = renderToString(<OverviewView state={{ status: \"populated\", data }} />);\n+\n+ expect(html).toContain('href=\"/ci-failures\"');\n+ expect(html).toContain(\"CI failure\");\n+ });\n });\n \n const settings = {",
"status": "modified",
"language": "typescript",
"additions": 25,
"deletions": 0,
"sizeBytes": 5328,
"previousPath": null,
"changedNewLines": [
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99
],
"headContentSha256": "4322c7ee4e6ea4289f6d07d82960fb4bdca3304462ca85586bd3bfc4574e2184"
},
{
"path": "apps/web/tests/github-sync-routes.spec.ts",
"hunks": [
{
"lines": [
{
"type": "context",
"content": " vi.stubGlobal(\"fetch\", fetcher);",
"newLineNumber": 141,
"oldLineNumber": 141
},
{
"type": "context",
"content": "",
"newLineNumber": 142,
"oldLineNumber": 142
},
{
"type": "context",
"content": " await listCiFailures(new Request(\"http://localhost/api/ci-failures?repository=openclaw%2Ffirmcode\"));",
"newLineNumber": 143,
"oldLineNumber": 143
},
{
"type": "deletion",
"content": " await readCiFailure(new Request(\"http://localhost/api/ci-failures/failure-1\"), {",
"newLineNumber": null,
"oldLineNumber": 144
},
{
"type": "deletion",
"content": " params: { id: \"failure-1\" }",
"newLineNumber": null,
"oldLineNumber": 145
},
{
"type": "addition",
"content": " await readCiFailure(new Request(\"http://localhost/api/ci-failures/00000000-0000-4000-8000-000000000501%3Aunit-tests\"), {",
"newLineNumber": 144,
"oldLineNumber": null
},
{
"type": "addition",
"content": " params: { id: \"00000000-0000-4000-8000-000000000501%3Aunit-tests\" }",
"newLineNumber": 145,
"oldLineNumber": null
},
{
"type": "context",
"content": " });",
"newLineNumber": 146,
"oldLineNumber": 146
},
{
"type": "context",
"content": "",
"newLineNumber": 147,
"oldLineNumber": 147
},
{
"type": "deletion",
"content": " const listUrl = new URL(String(fetcher.mock.calls[0]?.[0]));",
"newLineNumber": null,
"oldLineNumber": 148
},
{
"type": "deletion",
"content": " const detailUrl = new URL(String(fetcher.mock.calls[1]?.[0]));",
"newLineNumber": null,
"oldLineNumber": 149
},
{
"type": "deletion",
"content": " const listHeaders = new Headers((fetcher.mock.calls[0]?.[1] as RequestInit | undefined)?.headers);",
"newLineNumber": null,
"oldLineNumber": 150
},
{
"type": "addition",
"content": " const calls = fetcher.mock.calls as unknown as Array<[RequestInfo | URL, RequestInit | undefined]>;",
"newLineNumber": 148,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const listUrl = new URL(String(calls[0]?.[0]));",
"newLineNumber": 149,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const detailUrl = new URL(String(calls[1]?.[0]));",
"newLineNumber": 150,
"oldLineNumber": null
},
{
"type": "addition",
"content": " const listHeaders = new Headers(calls[0]?.[1]?.headers);",
"newLineNumber": 151,
"oldLineNumber": null
},
{
"type": "context",
"content": "",
"newLineNumber": 152,
"oldLineNumber": 151
},
{
"type": "context",
"content": " expect(listUrl.pathname).toBe(\"/api/ci-failures\");",
"newLineNumber": 153,
"oldLineNumber": 152
},
{
"type": "context",
"content": " expect(listUrl.searchParams.get(\"repository\")).toBe(\"openclaw/firmcode\");",
"newLineNumber": 154,
"oldLineNumber": 153
},
{
"type": "deletion",
"content": " expect(detailUrl.pathname).toBe(\"/api/ci-failures/failure-1\");",
"newLineNumber": null,
"oldLineNumber": 154
},
{
"type": "addition",
"content": " expect(detailUrl.pathname).toBe(\"/api/ci-failures/00000000-0000-4000-8000-000000000501%3Aunit-tests\");",
"newLineNumber": 155,
"oldLineNumber": null
},
{
"type": "context",
"content": " expect(listHeaders.get(\"x-firmcode-workspace-id\")).toBe(\"workspace-1\");",
"newLineNumber": 156,
"oldLineNumber": 155
},
{
"type": "context",
"content": " expect(listHeaders.get(\"x-firmcode-user-id\")).toBe(\"user-1\");",
"newLineNumber": 157,
"oldLineNumber": 156
},
{
"type": "context",
"content": " });",
"newLineNumber": 158,
"oldLineNumber": 157
}
],
"newStart": 141,
"oldStart": 141,
"newLineCount": 18,
"oldLineCount": 17,
"sectionHeader": "describe(\"GitHub sync routes\", () => {"
}
],
"patch": "@@ -141,17 +141,18 @@ describe(\"GitHub sync routes\", () => {\n vi.stubGlobal(\"fetch\", fetcher);\n \n await listCiFailures(new Request(\"http://localhost/api/ci-failures?repository=openclaw%2Ffirmcode\"));\n- await readCiFailure(new Request(\"http://localhost/api/ci-failures/failure-1\"), {\n- params: { id: \"failure-1\" }\n+ await readCiFailure(new Request(\"http://localhost/api/ci-failures/00000000-0000-4000-8000-000000000501%3Aunit-tests\"), {\n+ params: { id: \"00000000-0000-4000-8000-000000000501%3Aunit-tests\" }\n });\n \n- const listUrl = new URL(String(fetcher.mock.calls[0]?.[0]));\n- const detailUrl = new URL(String(fetcher.mock.calls[1]?.[0]));\n- const listHeaders = new Headers((fetcher.mock.calls[0]?.[1] as RequestInit | undefined)?.headers);\n+ const calls = fetcher.mock.calls as unknown as Array<[RequestInfo | URL, RequestInit | undefined]>;\n+ const listUrl = new URL(String(calls[0]?.[0]));\n+ const detailUrl = new URL(String(calls[1]?.[0]));\n+ const listHeaders = new Headers(calls[0]?.[1]?.headers);\n \n expect(listUrl.pathname).toBe(\"/api/ci-failures\");\n expect(listUrl.searchParams.get(\"repository\")).toBe(\"openclaw/firmcode\");\n- expect(detailUrl.pathname).toBe(\"/api/ci-failures/failure-1\");\n+ expect(detailUrl.pathname).toBe(\"/api/ci-failures/00000000-0000-4000-8000-000000000501%3Aunit-tests\");\n expect(listHeaders.get(\"x-firmcode-workspace-id\")).toBe(\"workspace-1\");\n expect(listHeaders.get(\"x-firmcode-user-id\")).toBe(\"user-1\");\n });",
"status": "modified",
"language": "typescript",
"additions": 7,
"deletions": 6,
"sizeBytes": 8064,
"previousPath": null,
"changedNewLines": [
144,
145,
148,
149,
150,
151,
155
],
"headContentSha256": "d9d953b61671747235ebb6d255e2cbd3ca61c7341082db723f53931d9eaee80c"
}
],
"baseSha": "ddfbd596724edd032ae406961705db60308eaed4",
"headSha": "319e7fcfc5bea1b5bd3f6282a2ded458f987aeed",
"reviewRunId": "edca9e0f-16da-4cfa-8760-d10cea42ea1c",
"skippedFiles": [],
"schemaVersion": "diff-artifact/v1",
"pullRequestNumber": 83,
"repositoryFullName": "firmcloudapps/firmcode-tester"
}
}