refactor(web): rename board column pending → initializing

Clarifies that Submitted/Starting runs are initializing, not just
pending. Also refactors run-detail to display the actual run status
via runStatusDisplay instead of mapping to board columns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-15 09:22:17 -04:00
parent e79baeab8e
commit a90038f7a7
No known key found for this signature in database
6 changed files with 50 additions and 26 deletions

View file

@ -29,11 +29,11 @@ export interface RunItem {
sandboxId?: string;
}
export type ColumnStatus = "working" | "pending" | "review" | "merge" | "running" | "waiting" | "succeeded" | "failed";
export type ColumnStatus = "working" | "initializing" | "review" | "merge" | "running" | "waiting" | "succeeded" | "failed";
export const columnNames: Record<ColumnStatus, string> = {
working: "Working",
pending: "Pending",
initializing: "Initializing",
review: "Verify",
merge: "Merge",
running: "Running",
@ -108,7 +108,7 @@ export function deriveCiStatus(checks: CheckRun[]): CiStatus {
export const statusColors: Record<ColumnStatus, { dot: string; text: string }> = {
working: { dot: "bg-teal-500", text: "text-teal-500" },
pending: { dot: "bg-amber", text: "text-amber" },
initializing: { dot: "bg-amber", text: "text-amber" },
review: { dot: "bg-mint", text: "text-mint" },
merge: { dot: "bg-teal-300", text: "text-teal-300" },
running: { dot: "bg-teal-500", text: "text-teal-500" },
@ -117,6 +117,33 @@ export const statusColors: Record<ColumnStatus, { dot: string; text: string }> =
failed: { dot: "bg-coral", text: "text-coral" },
};
export type RunStatus =
| "submitted"
| "starting"
| "running"
| "paused"
| "removing"
| "succeeded"
| "failed"
| "dead";
export const runStatusDisplay: Record<RunStatus, { label: string; dot: string; text: string }> = {
submitted: { label: "Submitted", dot: "bg-fg-muted", text: "text-fg-muted" },
starting: { label: "Starting", dot: "bg-amber", text: "text-amber" },
running: { label: "Running", dot: "bg-teal-500", text: "text-teal-500" },
paused: { label: "Paused", dot: "bg-amber", text: "text-amber" },
removing: { label: "Removing", dot: "bg-fg-muted", text: "text-fg-muted" },
succeeded: { label: "Succeeded", dot: "bg-mint", text: "text-mint" },
failed: { label: "Failed", dot: "bg-coral", text: "text-coral" },
dead: { label: "Dead", dot: "bg-coral", text: "text-coral" },
};
const knownRunStatuses = new Set<string>(Object.keys(runStatusDisplay));
export function isRunStatus(s: string): s is RunStatus {
return knownRunStatuses.has(s);
}
export const ciConfig: Record<CiStatus, { label: string; dot: string; text: string }> = {
passing: { label: "Passing", dot: "bg-mint", text: "text-mint" },
failing: { label: "Changes needed", dot: "bg-coral", text: "text-coral" },

View file

@ -2,8 +2,8 @@ import { useEffect } from "react";
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/20/solid";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
import { Link, Outlet, useFetcher, useLocation } from "react-router";
import { columnNames, mapRunSummaryToRunItem, statusColors } from "../data/runs";
import type { ColumnStatus, RunSummaryResponse } from "../data/runs";
import { mapRunSummaryToRunItem, runStatusDisplay, isRunStatus } from "../data/runs";
import type { RunSummaryResponse } from "../data/runs";
import { apiJson } from "../api";
import { useDemoMode } from "../lib/demo-mode";
import type { PreviewUrlResponse } from "@qltysh/fabro-api-client";
@ -25,17 +25,16 @@ export async function loader({ request, params }: any) {
if (!response.ok) return { run: null };
const summary: RunSummaryResponse = await response.json();
const item = mapRunSummaryToRunItem(summary);
const statusMap: Record<string, ColumnStatus> = {
running: "working",
paused: "pending",
completed: "merge",
};
const status = statusMap[summary.status ?? ""] ?? "working";
const rawStatus = summary.status ?? "submitted";
const display = isRunStatus(rawStatus)
? runStatusDisplay[rawStatus]
: { label: rawStatus, dot: "bg-fg-muted", text: "text-fg-muted" };
return {
run: {
...item,
status,
statusLabel: columnNames[status] ?? summary.status ?? "Unknown",
statusLabel: display.label,
statusDot: display.dot,
statusText: display.text,
},
};
}
@ -78,8 +77,6 @@ export default function RunDetail({ loaderData, params }: any) {
return <p className="py-8 text-center text-sm text-fg-muted">Run not found.</p>;
}
const colors = statusColors[run.status];
return (
<div>
<nav className="mb-4 flex items-center gap-1 text-sm text-fg-muted">
@ -97,8 +94,8 @@ export default function RunDetail({ loaderData, params }: any) {
<h2 className="text-xl font-semibold text-fg">{run.title}</h2>
<div className="mt-2 flex items-center gap-3 text-sm">
<span className="flex items-center gap-1.5">
<span className={`size-2 rounded-full ${colors.dot}`} />
<span className={`font-medium ${colors.text}`}>{run.statusLabel}</span>
<span className={`size-2 rounded-full ${run.statusDot}`} />
<span className={`font-medium ${run.statusText}`}>{run.statusLabel}</span>
</span>
<span className="font-mono text-xs text-fg-muted">{run.repo}</span>
{run.elapsed && (

View file

@ -36,7 +36,7 @@ interface ColumnStyle {
const columnStyles: Record<string, ColumnStyle> = {
working: { accent: "bg-teal-500", iconColor: "text-teal-500", iconType: "branch", actions: ["Watch", "Steer"] },
pending: { accent: "bg-amber", iconColor: "text-amber", iconType: "branch", actions: [] },
initializing: { accent: "bg-amber", iconColor: "text-amber", iconType: "branch", actions: [] },
review: { accent: "bg-mint", iconColor: "text-mint", iconType: "pr", actions: [] },
merge: { accent: "bg-teal-300", iconColor: "text-teal-300", iconType: "pr", actions: ["Merge"] },
running: { accent: "bg-teal-500", iconColor: "text-teal-500", iconType: "branch", actions: ["Watch", "Steer"] },

View file

@ -3109,7 +3109,7 @@ components:
type: string
enum:
- working
- pending
- initializing
- review
- merge

View file

@ -2428,7 +2428,7 @@ fn test_secret_store_path() -> PathBuf {
fn board_column(status: WorkflowRunStatus) -> Option<&'static str> {
match status {
WorkflowRunStatus::Submitted | WorkflowRunStatus::Starting => Some("pending"),
WorkflowRunStatus::Submitted | WorkflowRunStatus::Starting => Some("initializing"),
WorkflowRunStatus::Running => Some("running"),
WorkflowRunStatus::Paused => Some("waiting"),
WorkflowRunStatus::Succeeded => Some("succeeded"),
@ -2439,7 +2439,7 @@ fn board_column(status: WorkflowRunStatus) -> Option<&'static str> {
fn board_columns() -> serde_json::Value {
serde_json::json!([
{"id": "pending", "name": "Pending"},
{"id": "initializing", "name": "Initializing"},
{"id": "running", "name": "Running"},
{"id": "waiting", "name": "Waiting"},
{"id": "succeeded", "name": "Succeeded"},
@ -8520,8 +8520,8 @@ level = "debug"
let body = body_json(response.into_body()).await;
assert_eq!(body["pending_control"].as_str(), Some("pause"));
// Verify the run appears on the board (store has Submitted status → "pending"
// column)
// Verify the run appears on the board (store has Submitted status →
// "initializing" column)
let req = Request::builder()
.method("GET")
.uri(api("/boards/runs"))
@ -8535,7 +8535,7 @@ level = "debug"
.iter()
.find(|item| item["id"].as_str() == Some(run_id_str.as_str()))
.expect("board item should exist");
assert_eq!(item["status"].as_str(), Some("pending"));
assert_eq!(item["status"].as_str(), Some("initializing"));
}
#[tokio::test]
@ -9005,7 +9005,7 @@ timeout = "30s"
// Status should be a board column, not a lifecycle status
let status = item["status"].as_str().unwrap();
assert!(
["working", "pending", "review", "merge"].contains(&status),
["working", "initializing", "review", "merge"].contains(&status),
"status should be a board column, got: {status}"
);
assert!(item["created_at"].is_string());

View file

@ -20,7 +20,7 @@
export const BoardColumn = {
WORKING: 'working',
PENDING: 'pending',
INITIALIZING: 'initializing',
REVIEW: 'review',
MERGE: 'merge'
} as const;