Pattern gallery

Job title composite

A job's header row: the run identity, a one-click copy, and a status that updates as the run moves.

What this solves

Information and interaction. A job header carries three facts a reader needs without opening a drawer, which run this is, its id (copyable in one click instead of select-and-copy), and whether it is moving, done, or stuck.
Use it when
  • A job run's own detail page header.Speedway's job detail route (job.tsx:203-330), the closest shipped header today.
  • Any header where a background process's state must read at a glance.A module run driven by the pipeline (parttype, scrape, normalize, content) instead of a job.

Rendered

Fixture data; check both themes.

Brake pads Q3

run 4
running
3 files · Scrape · AC Delco Q3 taxonomy
patterns/job-title/example.tsxCopy this into a fresh route and it renders as above.
"use client";
import { useState } from "react";
import { Button, CopyButton, PageTitle, StatusPill, Tooltip } from "@versable-git/ui";
// Designed fresh (ruling D6): the job name carries a quieter mono suffix for
// the run identity, a copy affordance for the id, and a dot-joined meta line.
// The kit only styles string titles, so the JSX title self-styles its h1.
// One state axis: click a stage button and the pill, its pulse, and the
// meta line update together, the way a real run actually moves.
type Stage = "queued" | "running" | "done" | "failed";
const STAGES: { key: Stage; label: string }[] = [
{ key: "queued", label: "Queued" },
{ key: "running", label: "Running" },
{ key: "done", label: "Done" },
{ key: "failed", label: "Failed" },
];
const STAGE_META: Record<Stage, { kind: "neutral" | "info" | "ok" | "err"; pillLabel: string; meta: string }> = {
queued: { kind: "neutral", pillLabel: "queued", meta: "3 files · Scrape · waiting for run 3 to finish" },
running: { kind: "info", pillLabel: "running", meta: "3 files · Scrape · AC Delco Q3 taxonomy" },
done: { kind: "ok", pillLabel: "done", meta: "3 files · Scrape · finished in 4m 12s" },
failed: { kind: "err", pillLabel: "failed", meta: "3 files · Scrape · stopped on file 2 of 3" },
};
export function JobTitle() {
const [stage, setStage] = useState<Stage>("running");
const meta = STAGE_META[stage];
return (
<div className="flex flex-col gap-3">
<div className="flex gap-2">
{STAGES.map((s) => (
<Button
key={s.key}
size="xs"
variant={s.key === stage ? "solid" : "outline"}
content={s.label}
onClick={() => setStage(s.key)}
/>
))}
</div>
<PageTitle
Icon="Refresh"
title={
<span className="flex min-w-0 items-baseline gap-2">
<h1 className="truncate text-2xl font-bold">Brake pads Q3</h1>
<Tooltip content="The 4th run of this job">
<span className="text-base-content/50 shrink-0 font-mono text-lg font-medium">run 4</span>
</Tooltip>
<CopyButton value="job_h83k2m" size="xs" copiedLabel="Job id copied" />
</span>
}
subtitle={meta.meta}
titleExtra={
<StatusPill kind={meta.kind} size="sm" dot dotPulse={stage === "running"}>
{meta.pillLabel}
</StatusPill>
}
/>
</div>
);
}

Where it ships

  • speedway/app/routes/workspaces/jobs/job.tsx:203-330the closest shipped analog: JSX title self-styling its h1 (InlineEdit inside), dot-joined subtitle, actions on the row

App-specific: The suffix and the title-side copy action ship nowhere yet; ruling D6 asked for them designed fresh here rather than waiting for an app need. Real headers swap the static name for InlineEdit and derive the meta line from the loader. The run identity is mono because it is an identifier.

@versable-git/ui · composites proven in the apps