From 0600b2ab595ab341d74b83a7250c461f06b653a7 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 28 Feb 2026 13:58:40 -0500 Subject: [PATCH] Extract shared run data, add list view for workflow runs tab Shared data module at data/runs.ts powers both the kanban board (/runs) and the vertical list view (/workflows/:name/runs). List view shows status dot, repo, title, diff stats, CI badge, comments, and elapsed time in a compact row format. Co-Authored-By: Claude Opus 4.6 --- apps/arc-web/app/data/runs.ts | 180 ++++++++++++++++++++++ apps/arc-web/app/layouts/app-shell.tsx | 15 +- apps/arc-web/app/routes/pipelines.tsx | 177 ++------------------- apps/arc-web/app/routes/workflow-runs.tsx | 72 ++++++++- 4 files changed, 270 insertions(+), 174 deletions(-) create mode 100644 apps/arc-web/app/data/runs.ts diff --git a/apps/arc-web/app/data/runs.ts b/apps/arc-web/app/data/runs.ts new file mode 100644 index 000000000..0348a812f --- /dev/null +++ b/apps/arc-web/app/data/runs.ts @@ -0,0 +1,180 @@ +export type CiStatus = "passing" | "failing" | "pending"; + +export interface RunItem { + repo: string; + title: string; + number?: number; + additions?: number; + deletions?: number; + ci?: CiStatus; + elapsed?: string; + elapsedWarning?: boolean; + resources?: string; + actionDisabled?: boolean; + comments?: number; +} + +export type ColumnStatus = "working" | "pending" | "review" | "merge"; + +export interface RunWithStatus extends RunItem { + status: ColumnStatus; + statusLabel: string; +} + +export const columns: { + id: ColumnStatus; + name: string; + accent: string; + iconColor: string; + iconType: "branch" | "pr"; + actions: string[]; + items: RunItem[]; +}[] = [ + { + id: "working", + name: "Working", + accent: "bg-teal-500", + iconColor: "text-teal-500", + iconType: "branch", + actions: ["Watch", "Steer"], + items: [ + { + repo: "api-server", + title: "Add rate limiting to auth endpoints", + resources: "4 CPU / 8 GB", + elapsed: "7m", + }, + { + repo: "web-dashboard", + title: "Migrate to React Router v7", + resources: "8 CPU / 16 GB", + elapsed: "2h 15m", + }, + { + repo: "cli-tools", + title: "Fix config parsing for nested values", + resources: "2 CPU / 4 GB", + elapsed: "45m", + }, + ], + }, + { + id: "pending", + name: "Pending", + accent: "bg-amber", + iconColor: "text-amber", + iconType: "branch", + actions: ["Answer Question"], + items: [ + { + repo: "api-server", + title: "Update OpenAPI spec for v3", + additions: 567, + deletions: 234, + elapsed: "1h 12m", + }, + { + repo: "shared-types", + title: "Add pipeline event types", + additions: 145, + deletions: 23, + elapsed: "28m", + }, + ], + }, + { + id: "review", + name: "Verify", + accent: "bg-mint", + iconColor: "text-mint", + iconType: "pr", + actions: ["Resolve"], + items: [ + { + repo: "web-dashboard", + title: "Add dark mode toggle", + number: 889, + additions: 234, + deletions: 67, + ci: "failing", + elapsed: "35m", + comments: 4, + }, + { + repo: "infrastructure", + title: "Terraform module for Redis cluster", + number: 156, + additions: 412, + deletions: 0, + ci: "pending", + elapsed: "12m", + actionDisabled: true, + comments: 1, + }, + ], + }, + { + id: "merge", + name: "Merge", + accent: "bg-teal-300", + iconColor: "text-teal-300", + iconType: "pr", + actions: ["Merge"], + items: [ + { + repo: "api-server", + title: "Implement webhook retry logic", + number: 1249, + additions: 189, + deletions: 45, + ci: "passing", + elapsed: "3d", + elapsedWarning: true, + comments: 7, + }, + { + repo: "cli-tools", + title: "Add --verbose flag to run command", + number: 430, + additions: 56, + deletions: 12, + ci: "passing", + elapsed: "1h 5m", + comments: 2, + }, + { + repo: "shared-types", + title: "Export utility type helpers", + number: 76, + additions: 34, + deletions: 8, + ci: "passing", + elapsed: "48m", + comments: 0, + }, + ], + }, +]; + +export function allRunsFlat(): RunWithStatus[] { + return columns.flatMap((col) => + col.items.map((item) => ({ + ...item, + status: col.id, + statusLabel: col.name, + })), + ); +} + +export const statusColors: Record = { + working: { dot: "bg-teal-500", text: "text-teal-500" }, + pending: { dot: "bg-amber", text: "text-amber" }, + review: { dot: "bg-mint", text: "text-mint" }, + merge: { dot: "bg-teal-300", text: "text-teal-300" }, +}; + +export const ciConfig: Record = { + passing: { label: "Passing", dot: "bg-mint", text: "text-mint" }, + failing: { label: "Changes needed", dot: "bg-coral", text: "text-coral" }, + pending: { label: "Pending", dot: "bg-amber", text: "text-amber" }, +}; diff --git a/apps/arc-web/app/layouts/app-shell.tsx b/apps/arc-web/app/layouts/app-shell.tsx index e71e86e5a..b6a1c6e0b 100644 --- a/apps/arc-web/app/layouts/app-shell.tsx +++ b/apps/arc-web/app/layouts/app-shell.tsx @@ -45,8 +45,9 @@ export default function AppShell() { const currentNav = navigation.find((item) => pathname.startsWith(item.href)); const title = currentNav?.name ?? ""; const lastMatch = matches[matches.length - 1]; - const headerExtra = (lastMatch?.handle as { headerExtra?: React.ReactNode }) - ?.headerExtra; + const handle = lastMatch?.handle as { headerExtra?: React.ReactNode; wide?: boolean } | undefined; + const headerExtra = handle?.headerExtra; + const maxWidth = handle?.wide ? "max-w-7xl" : "max-w-5xl"; return (
@@ -187,13 +188,15 @@ export default function AppShell() {
-
-

{title}

- {headerExtra &&
{headerExtra}
} +
+
+

{title}

+ {headerExtra &&
{headerExtra}
} +
-
+
diff --git a/apps/arc-web/app/routes/pipelines.tsx b/apps/arc-web/app/routes/pipelines.tsx index fc58670aa..fee1d4828 100644 --- a/apps/arc-web/app/routes/pipelines.tsx +++ b/apps/arc-web/app/routes/pipelines.tsx @@ -1,3 +1,5 @@ +import { columns, ciConfig } from "../data/runs"; +import type { CiStatus, RunItem } from "../data/runs"; import type { Route } from "./+types/pipelines"; export function meta({}: Route.MetaArgs) { @@ -31,18 +33,10 @@ function GitPullRequestIcon({ className }: { className?: string }) { ); } -type CiStatus = "passing" | "failing" | "pending"; - -const ciConfig: Record = - { - passing: { label: "Passing", dot: "bg-mint", text: "text-mint" }, - failing: { - label: "Changes needed", - dot: "bg-coral", - text: "text-coral", - }, - pending: { label: "Pending", dot: "bg-amber", text: "text-amber" }, - }; +const iconMap = { + branch: GitBranchIcon, + pr: GitPullRequestIcon, +}; function CiBadge({ status }: { status: CiStatus }) { const config = ciConfig[status]; @@ -54,157 +48,6 @@ function CiBadge({ status }: { status: CiStatus }) { ); } -interface PullRequest { - repo: string; - title: string; - number?: number; - additions?: number; - deletions?: number; - ci?: CiStatus; - elapsed?: string; - elapsedWarning?: boolean; - resources?: string; - actionDisabled?: boolean; - comments?: number; -} - -interface Column { - id: string; - name: string; - accent: string; - iconColor: string; - icon: React.ComponentType<{ className?: string }>; - actions?: string[]; - items: PullRequest[]; -} - -const columns: Column[] = [ - { - id: "working", - name: "Working", - accent: "bg-teal-500", - iconColor: "text-teal-500", - icon: GitBranchIcon, - actions: ["Watch", "Steer"], - items: [ - { - repo: "api-server", - title: "Add rate limiting to auth endpoints", - resources: "4 CPU / 8 GB", - elapsed: "7m", - }, - { - repo: "web-dashboard", - title: "Migrate to React Router v7", - resources: "8 CPU / 16 GB", - elapsed: "2h 15m", - }, - { - repo: "cli-tools", - title: "Fix config parsing for nested values", - resources: "2 CPU / 4 GB", - elapsed: "45m", - }, - ], - }, - { - id: "pending", - name: "Pending", - accent: "bg-amber", - iconColor: "text-amber", - icon: GitBranchIcon, - actions: ["Answer Question"], - items: [ - { - repo: "api-server", - title: "Update OpenAPI spec for v3", - additions: 567, - deletions: 234, - elapsed: "1h 12m", - }, - { - repo: "shared-types", - title: "Add pipeline event types", - additions: 145, - deletions: 23, - elapsed: "28m", - }, - ], - }, - { - id: "review", - name: "Verify", - accent: "bg-mint", - iconColor: "text-mint", - icon: GitPullRequestIcon, - actions: ["Resolve"], - items: [ - { - repo: "web-dashboard", - title: "Add dark mode toggle", - number: 889, - additions: 234, - deletions: 67, - ci: "failing", - elapsed: "35m", - comments: 4, - }, - { - repo: "infrastructure", - title: "Terraform module for Redis cluster", - number: 156, - additions: 412, - deletions: 0, - ci: "pending", - elapsed: "12m", - actionDisabled: true, - comments: 1, - }, - ], - }, - { - id: "merge", - name: "Merge", - accent: "bg-teal-300", - iconColor: "text-teal-300", - icon: GitPullRequestIcon, - actions: ["Merge"], - items: [ - { - repo: "api-server", - title: "Implement webhook retry logic", - number: 1249, - additions: 189, - deletions: 45, - ci: "passing", - elapsed: "3d", - elapsedWarning: true, - comments: 7, - }, - { - repo: "cli-tools", - title: "Add --verbose flag to run command", - number: 430, - additions: 56, - deletions: 12, - ci: "passing", - elapsed: "1h 5m", - comments: 2, - }, - { - repo: "shared-types", - title: "Export utility type helpers", - number: 76, - additions: 34, - deletions: 8, - ci: "passing", - elapsed: "48m", - comments: 0, - }, - ], - }, -]; - const totalCards = columns.reduce((sum, col) => sum + col.items.length, 0); const totalPrs = columns.reduce( (sum, col) => sum + col.items.filter((item) => item.number != null).length, @@ -212,6 +55,7 @@ const totalPrs = columns.reduce( ); export const handle = { + wide: true, headerExtra: (
@@ -230,7 +74,7 @@ function PrCard({ iconColor, actions, }: { - pr: PullRequest; + pr: RunItem; icon: React.ComponentType<{ className?: string }>; iconColor: string; actions?: string[]; @@ -332,7 +176,8 @@ function PrCard({ ); } -function BoardColumn({ column }: { column: Column }) { +function BoardColumn({ column }: { column: (typeof columns)[number] }) { + const Icon = iconMap[column.iconType]; return (
@@ -350,7 +195,7 @@ function BoardColumn({ column }: { column: Column }) { diff --git a/apps/arc-web/app/routes/workflow-runs.tsx b/apps/arc-web/app/routes/workflow-runs.tsx index d6b500876..bca224957 100644 --- a/apps/arc-web/app/routes/workflow-runs.tsx +++ b/apps/arc-web/app/routes/workflow-runs.tsx @@ -1,3 +1,71 @@ -export default function WorkflowRuns() { - return

Recent runs will appear here.

; +import { allRunsFlat, ciConfig, statusColors } from "../data/runs"; +import type { CiStatus, RunWithStatus } from "../data/runs"; + +const runs = allRunsFlat(); + +function CiBadge({ status }: { status: CiStatus }) { + const config = ciConfig[status]; + return ( + + + {config.label} + + ); +} + +function RunRow({ run }: { run: RunWithStatus }) { + const colors = statusColors[run.status]; + return ( +
+ + + {run.statusLabel} + + + + {run.repo} + {run.number != null && ( + #{run.number} + )} + + + {run.title} + + {run.additions != null && run.deletions != null && ( + + +{run.additions.toLocaleString()} + -{run.deletions.toLocaleString()} + + )} + + {run.ci != null && ( + + + + )} + + {run.comments != null && run.comments > 0 && ( + + + {run.comments} + + )} + + + {run.elapsed} + +
+ ); +} + +export default function WorkflowRuns() { + return ( +
+ {runs.map((run) => ( + + ))} +
+ ); }