import { type JSX, Show } from "solid-js"; import { CONFIG } from "../config"; import { fmtAge, fmtDuration, shortDelivery, shortText } from "../format"; import { runCancel, runTrigger } from "../state"; import type { WorkItem } from "../work-items"; import { IssueLink, PrLink } from "./IssueLink"; import { LifecycleStepper } from "./LifecycleStepper"; import { Pill } from "./Pill"; export interface IssueCardProps { item: WorkItem; } // Verbatim cancel-confirm copy retained from the previous running table so the // operator-facing wording does not drift. const CANCEL_CONFIRM = "Kill this running task? The omp subprocess dies and the row lands in 'failed'."; // One issue = one card. Full shape when the item has an issue ref (the // common case); compact shape for orphan deliveries with no bound issue. // State tint is carried by `data-bucket` + CSS — no side-stripe, no shadow. export function IssueCard(props: IssueCardProps): JSX.Element { const item = (): WorkItem => props.item; const ref = () => item().ref; const full = (): boolean => ref() !== null; return (
}>
{item().classification ?? "issue"}
{shortText(item().error, 200)}
); } function CompactHead(props: { item: WorkItem }): JSX.Element { return (
{shortDelivery(props.item.deliveryId)}
); } function ActivityPill(props: { item: WorkItem }): JSX.Element { const state = (): string | undefined => props.item.latestEvent?.state ?? (props.item.bucket === "running" ? "running" : undefined); const label = (): string => props.item.latestEvent?.state ?? (props.item.bucket === "running" ? (props.item.inflightOnly ? "inflight" : "running") : "—"); return ( {label()} ); } function MetaRow(props: { item: WorkItem }): JSX.Element { const item = (): WorkItem => props.item; const live = () => item().live; // Only the applicable fragments render; the CSS layer draws a · separator // before every non-first child, so we never emit leading/trailing dots. return (
{live()!.model} last action {live()!.last_tool}{" "} {fmtAge(live()!.last_tool_ts)} elapsed {liveElapsed(live()!.started_at)} attempt #{live()!.attempts} held by pool {item().branch} {item().latestEvent!.event_type} · attempt #{item().latestEvent!.attempts} ·{" "} {fmtAge(item().latestEvent!.received_at)}
); } async function cancel(deliveryId: string): Promise { if (!window.confirm(CANCEL_CONFIRM)) return; await runCancel(deliveryId); } // Elapsed for a live run. Guards against invalid/unparseable timestamps so we // never render a NaN-duration — falls back to "—". function liveElapsed(startedAt: string | null): string { if (!startedAt) return "—"; const t = Date.parse(startedAt); if (Number.isNaN(t)) return "—"; return fmtDuration((Date.now() - t) / 1000); }