fix(review-gates): green success badges, null-safe preview rows, optional section items

- Extract shared statusBadgeClass() helper (src/viewers/statusBadge.ts) so
  SqlViewer, CteResultViewer and PhaseSummaryViewer can't drift: ok/success/
  passed/promoted render green (--success), warn renders amber (--warning),
  error/failed stay destructive red, everything else stays neutral outline.
  Previously CteResultViewer/PhaseSummaryViewer mapped "ok"/"promoted" to the
  default badge variant, which is bg-primary (GSD red) — success states
  rendered red.
- enrich.js: buildCteResultV2 now falls back preview.rows to [] instead of
  null when last_test.preview_rows is missing (pre-upgrade ok records), and
  CteResultViewer reads result.preview?.rows?.length with a null-safe
  fallback so it degrades to the "No preview rows" empty state instead of
  crashing.
- PhaseSummaryViewer: section.items is optional (model-authored sections can
  be prose-only); render (section.items ?? []) instead of crashing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 01:37:35 +02:00
co-authored by Claude Fable 5
parent b089482b8d
commit b59b57c4e6
9 changed files with 65 additions and 26 deletions
+3 -8
View File
@@ -1,16 +1,11 @@
import { Badge } from "../components/ui/badge";
import { SqlViewer, type SqlBlock } from "./SqlViewer";
import { PreviewGrid } from "./PreviewGrid";
import { statusBadgeClass } from "./statusBadge";
import type { CteResultV2 } from "./artifactV2";
const CHIP = "rounded-md bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground/90";
function statusBadgeVariant(status: string): "default" | "destructive" | "outline" {
if (status === "ok") return "default";
if (status === "error") return "destructive";
return "outline";
}
function statusLabel(status: string): string {
if (status === "ok") return "success";
if (status === "error") return "error";
@@ -24,7 +19,7 @@ function testStatus(status: string): SqlBlock["testStatus"] {
}
export function CteResultViewer({ result }: { result: CteResultV2 }) {
const hasPreviewRows = !!result.preview && result.preview.rows.length > 0;
const hasPreviewRows = (result.preview?.rows?.length ?? 0) > 0;
const descriptions = (result.columns ?? []).map((c) => c.description ?? "");
const showColumns = descriptions.some((d) => d.trim() !== "");
const dependsOn = result.depends_on ?? [];
@@ -42,7 +37,7 @@ export function CteResultViewer({ result }: { result: CteResultV2 }) {
CTE {result.index}/{result.total}
</Badge>
<span className="font-mono text-sm font-semibold">{result.name}</span>
<Badge variant={statusBadgeVariant(result.status)}>{statusLabel(result.status)}</Badge>
<Badge variant="outline" className={statusBadgeClass(result.status)}>{statusLabel(result.status)}</Badge>
{result.execution_ms !== undefined && (
<span className="text-xs text-muted-foreground">{result.execution_ms} ms</span>
)}