Moderne LST — service blueprint

From registered repo to finished recipe run. Phases 1–4 are per-repo (long-lived); 5–7 fire on every run. Row statuses in phases 5–6 reflect the post-PR #1518 world where Sync and State are independent (see callout below).

Lifecycle by layer

Rows are layers, columns are phases. The Platform UI status row splits today | proposed. Click any cell or chip for detail; toggle rows to expand CLI status and Engine & Storage.

PR #1518 · Sync and State are now independent
Sync is no longer a prerequisite for State. Previously safe assumption “State advances only after Sync=SYNCED” is now false. Any code that reads syncStatus to decide whether to render / enable the State column must be relaxed.
Event ordering can interleave. RepoSynced and RepoRunFinished arrive in either order for the same repo. Row-derivation logic that assumes sync-then-run must handle run-then-sync too — check repository-results.rows.ts and changeset-status.helpers.ts.
resultsCount gating. isRepositoryNotYetRun(row.state) currently returns - for pre-run states. Confirm SYNCING is still treated as “not yet run” for legacy rows, and that a row in RUNNING with Sync=PENDING doesn't render a stale count.
Error messages are capped server-side. The worker truncates RepoRunFinished.errorMessage at PIPE_BUF. Nothing to change in the UI, but tooltip / popover copy-full-message is now working on a shorter payload.
State filter valueOptions omits SYNCING. Correct for new runs. If the backend can return SYNCING on legacy rows, those rows are unfilterable — decide whether to add SYNCING back as a legacy-only option.
Cancel-file consolidated. RecipeRunService.cancel now writes one cancel file, not two. The Sync=CANCELED status still exists on repos.
Run-level SYNCING is @deprecated. New runs jump straight to RUNNING. Consumers of OrganizationRecipeRunSyncingstate-status-cell (run header), results-progress-bar, changeset-status.helpers, use-chat-recipe-run-status, recent-activity-neo.rows — should keep the SYNCING branch for legacy / in-flight only.
Gaps the proposed model doesn't resolve on its own
No build / refresh triggerrequestRepositoryLstDetails has no client caller; the UI is a viewer.
No build diagnostics — clone / parse / build / publish logs live only in CI; the UI can't say why a repo failed.
Staleness is unreliableRepository.changeset is the last-observed SCM HEAD, not a live one, so "N commits behind" would frequently lie. The proposed model drops "Stale" for this reason.

Proposed status colour model

Aligned with the Moderne design system Status foundation — eight intents, semantic tokens --status-{intent}-{light|default|dark}.

Success
Finished · Synced · Available · Merged · Approved
Queued · clock icon
Queued · Pending · Awaiting work
Active · spinner (open arc)
Running · Processing · Syncing · In progress
Info
Open (PR) · Feature labels · Metadata
Warning · gold (was amber)
No LST · LST out of date · Partially parsed · Needs approval
Error
Error · Failed · Timeout · Blocked
Neutral · cool grey
Unknown · Draft · No changes · Not required · Closed
Canceled · warm grey · own namespace
Canceled (distinct from Neutral so “draft” vs “canceled” read differently)
Sync collapses into Run. One status column. Queued covers fetch + slot-wait; Failed → Error, Skipped → No LST, Canceled stays Canceled.
Indeterminate progress. Running uses the Active intent (violet, spinning open-arc) — no percentage, we can't compute reliable per-repo progress. Terminal outcome replaces the spinner.
Queued and Active share the violet ramp. Same intent family — Queued (clock) → Active (spinner) reads as a single motion story. Canceled uses a separate warm-grey ramp so it does not blend into Neutral / Draft rows.

Badge spec

Chips in this prototype render at the DS Badge medium size (Figma "Badge" node 4091:17230). The Figma file defines only one size; small / large scale proportionally for cases layered on top.

Size Height Padding Font Sample
small20 px1 px 8 px10 px / 500Success
medium · Figma spec24 px2 px 10 px12 px / 500Success
large28 px3 px 12 px14 px / 500Success
Radius · radius-full (pill)
Border · 1 px solid, --status-{intent}-transparent (60% alpha — softer than the solid default)
Text + icon colour · --status-{intent}-dark
Background · --status-{intent}-light
Line-height · 1 (text-box-trim: cap → alphabetic centres label between pill caps)
Gap · 4 px between icon / dot and label
Leading icon slot · 14 px box, stroke 1.5, currentColor
Dot (icon-less variant) · 6 px, filled --status-{intent}-default

Icon per intent

Lucide outline names — Queued (clock) and Active (loader-circle) are mandatory because they share the violet ramp; nothing else disambiguates them. Active is the one outline-only intent — the open-arc loader cannot fill — and spins at 4s linear infinite honouring prefers-reduced-motion. For standalone status icons outside a badge: minimum 16 px, filled silhouette only at 16–17 px, outline at 18 px+.

Success · circle-check Queued · clock (mandatory) Active · loader-circle (mandatory · spin) Info · info Warning · triangle-alert Error · octagon-alert Neutral · circle-minus Canceled · circle-x

User flow