From 75ffbdf9e4d1ac37a975f8da7e176579842d31ef Mon Sep 17 00:00:00 2001 From: Tom Smith <142233216+tomsmith8@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:36:46 +0100 Subject: [PATCH] Rail: aligned grid columns, icons over words Production feedback: rows had no column structure (variable-width labels pushed timestamps out of line) and status words were noise. - Rows are a shared CSS grid (label | status | time | trailing group) so labels, statuses and timestamps align across every row - The word "analysis" is gone: run-only rows carry the recursion-loop icon with the stage in the tooltip - "running"/"completed" words are gone: the spinner and colored dot carry the state, with the word in the tooltip. Failure states keep their word - a red dot alone under-sells a failed run - Report link and score sit in a right-aligned trailing group beside the Stakwork link --- .../components/RecursionActivityRail.test.tsx | 32 +++++++-- .../legal/RecursionActivityRail.tsx | 71 ++++++++++++------- 2 files changed, 73 insertions(+), 30 deletions(-) diff --git a/src/__tests__/unit/components/RecursionActivityRail.test.tsx b/src/__tests__/unit/components/RecursionActivityRail.test.tsx index 4253fbcf62..bf0e7b606c 100644 --- a/src/__tests__/unit/components/RecursionActivityRail.test.tsx +++ b/src/__tests__/unit/components/RecursionActivityRail.test.tsx @@ -46,13 +46,17 @@ describe("RecursionActivityRail", () => { expect(screen.getByTestId("activity-rail-empty")).toBeTruthy(); }); - it("renders label, status, score and relative time per row", () => { + it("renders label, status icon, score and relative time per row", () => { render(); const row = screen.getByTestId("rail-row-trigger-1"); expect(row.textContent).toContain("base"); - expect(row.textContent).toContain("completed"); expect(row.textContent).toContain("50/74"); expect(row.textContent).toContain("ago"); + // Terminal success is a dot with a tooltip — no word cluttering the rail + const status = screen.getByTestId("rail-status-trigger-1"); + expect(status.getAttribute("data-status")).toBe("COMPLETED"); + expect(status.getAttribute("title")).toBe("completed"); + expect(status.textContent).not.toContain("completed"); }); it("renders a graph-only row with an em dash for status", () => { @@ -65,24 +69,40 @@ describe("RecursionActivityRail", () => { expect(screen.getByTestId("rail-status-trigger-1").textContent).toBe("—"); }); - it("shows a spinner-style running state for in-flight rows", () => { + it("in-flight rows spin without the word 'running'", () => { render( , ); - expect(screen.getByTestId("rail-status-trigger-1").textContent).toContain("running"); + const status = screen.getByTestId("rail-status-trigger-1"); + expect(status.getAttribute("data-status")).toBe("IN_PROGRESS"); + expect(status.textContent).not.toContain("running"); + expect(status.querySelector(".animate-spin")).not.toBeNull(); }); - it("labels a run-only recursion row by pipeline name", () => { + it("failure states keep their word — a red dot alone under-sells them", () => { + render( + , + ); + expect(screen.getByTestId("rail-status-trigger-1").textContent).toContain("failed"); + }); + + it("marks a run-only row with the loop icon, stage in the tooltip — no words", () => { render( , ); - expect(screen.getByTestId("rail-row-rec-1").textContent).toContain("recursion"); + const icon = screen.getByTestId("rail-pipeline-rec-1"); + expect(icon.getAttribute("title")).toMatch(/recursion loop/i); + expect(screen.getByTestId("rail-row-rec-1").textContent).not.toContain("analysis"); + expect(screen.getByTestId("rail-row-rec-1").textContent).not.toContain("recursion"); }); it("links to the report when the bundle exists", () => { diff --git a/src/components/legal/RecursionActivityRail.tsx b/src/components/legal/RecursionActivityRail.tsx index 10005ab440..29e12c7e43 100644 --- a/src/components/legal/RecursionActivityRail.tsx +++ b/src/components/legal/RecursionActivityRail.tsx @@ -1,7 +1,7 @@ "use client"; import { formatDistanceToNow } from "date-fns"; -import { FileText, Loader2 } from "lucide-react"; +import { FileText, Loader2, Repeat } from "lucide-react"; import { StakworkRunLink } from "@/components/legal/StakworkRunLink"; import { useWorkspace } from "@/hooks/useWorkspace"; import type { AttemptRailRow } from "@/hooks/useEvalRunHistory"; @@ -19,19 +19,29 @@ interface RecursionActivityRailProps { * exists in Jarvis but no StakworkRun matched) and renders as an em dash * rather than pretending to know. */ -const STATUS_STYLES: Record = { +const STATUS_STYLES: Record = { PENDING: { dot: "bg-gray-400", text: "text-muted-foreground", label: "pending" }, IN_PROGRESS: { dot: "bg-blue-500", text: "text-blue-700 dark:text-blue-300", label: "running" }, COMPLETED: { dot: "bg-green-600", text: "text-green-700 dark:text-green-400", label: "completed" }, - ERROR: { dot: "bg-red-500", text: "text-red-700 dark:text-red-400", label: "error" }, - FAILED: { dot: "bg-red-500", text: "text-red-700 dark:text-red-400", label: "failed" }, + // Exceptional states keep their word — a red dot alone under-sells a failure. + ERROR: { dot: "bg-red-500", text: "text-red-700 dark:text-red-400", label: "error", wordy: true }, + FAILED: { dot: "bg-red-500", text: "text-red-700 dark:text-red-400", label: "failed", wordy: true }, HALTED: { dot: "bg-gray-400", text: "text-muted-foreground", label: "halted" }, }; +/** + * Icons carry the state: spinner = running, colored dot = terminal, em dash = + * graph-only. The word rides in the tooltip; only failure states spell it out. + */ function RowStatus({ row }: { row: AttemptRailRow }) { if (!row.status) { return ( - + ); @@ -44,31 +54,40 @@ function RowStatus({ row }: { row: AttemptRailRow }) { return ( {row.inFlight ? ( - + ) : ( - + )} - {style.label} + {style.wordy && style.label} ); } -/** Row identity chip: the chart label when charted, else the pipeline name. */ +/** + * Row identity: the chart label when charted; the recursion-loop icon for + * run-only rows (in-flight pipeline work with no dot yet) — the stage name + * rides in the tooltip rather than cluttering the rail. + */ function RowLabel({ row }: { row: AttemptRailRow }) { if (row.label) { return ( - {row.label} + {row.label} ); } - // Pipeline names, not internal jargon: LEGAL_BENCHMARK_RECURSION drives the - // loop ("recursion"); LEGAL_BENCHMARK_EVAL is failure analysis — its webhook - // writes cause annotations, never a score ("analysis"). + const stage = + row.runType === "recursion" + ? "recursion loop — fix proposal" + : row.runType === "eval" + ? "recursion loop — failure analysis" + : "run"; return ( - - {row.runType === "recursion" ? "recursion" : row.runType === "eval" ? "analysis" : "run"} + + ); } @@ -126,30 +145,34 @@ export function RecursionActivityRail({ rows, partial }: RecursionActivityRailPr return (
+ {/* One shared grid template so every column lines up across rows: + label | status icon | relative time | trailing links + score. */} {rows.map((row) => (
{row.timestamp ? formatDistanceToNow(new Date(row.timestamp), { addSuffix: true }) : "—"} - - {row.score && ( - - {row.score.passed}/{row.score.total} - - )} - + + + {row.score && ( + + {row.score.passed}/{row.score.total} + + )} + +
))}