# Status-color contract

Status color is semantic across every Layers operator surface:

- Green means healthy, ok, live, working, complete, or otherwise operating normally.
- Amber means warning, stale, degraded, or needs attention.
- Red means error, destructive, blocked, or failed only.

A healthy state rendered in red is a defect, even when red was an intentional visual motif.
Information blue is a separate, non-status semantic exposed as `--info`; it does not replace any
of the three status meanings.

## Tokens

CSS status UI uses these pairs:

| Semantic | Surface | Foreground |
| --- | --- | --- |
| ok | `--status-ok` | `--status-ok-foreground` |
| warning | `--status-warn` | `--status-warn-foreground` |
| error | `--status-error` | `--status-error-foreground` |

Tailwind exposes the same values as `--color-status-ok`, `--color-status-warn`, and
`--color-status-error`, including each `-foreground` counterpart. React Native consumers import
`STATUS_COLORS` from `@layers/ui/tokens/status-colors`; they do not hand-copy hex values.

### Warn splits ink from surface (founder ruling 2026-07-31)

`--status-warn` aliases **`--warning-background`** (`#ff9f66`, constant across themes), not
`--warning`. Warn was the only semantic without its own fill: ok already aliased
`--success-background` and error `--destructive-background`, while warn pointed at `--warning` — a
value tuned to be legible **as text on white** (L44%). Using a text color as a badge fill and
setting near-black on top is what made every "Needs attention" chip read muddy.

- **`--warning`** is the INK: `text-warning`, `bg-warning/10`, stale-age labels, inline notices.
  Still tuned per theme, because text has to hold against two different page colors.
- **`--warning-background`** is the FILL: badges, the rail notification dot, anything with
  `--ink` on top. One value for both themes, exactly like `--success-background`.

The fill sits at L70% against ok's L63% so the three status chips read as one family, and it is
deliberately **not** the literal brand peach `#ffc0a2` — that is `--chart-series-4`, and status
colors are never series colors.

The status pairs alias the existing success, warning, and destructive theme families. The shared
ok surface uses `--ink` as its foreground because white does not provide sufficient contrast on
the bright `#59e67e` ok green.

### Error finished the same split (ruling 2026-08-01)

Warn's split fixed warn and left error half-done. `--status-error` aliased `--destructive-background`
and `--status-error-foreground` aliased `--destructive-foreground` — and those two tokens held the
**identical value**, so the documented pair was red on red at 1:1. Nothing rendered it (every
consumer used `--status-error` as ink, never as a fill with its foreground on top), so nothing was
broken. It was a loaded gun in the token layer: the first person to follow the table above would
have shipped an invisible chip.

- **`--destructive`** is the INK: `text-destructive`, `bg-destructive/10`, `border-destructive/30`.
- **`--destructive-foreground`** is the ink that sits ON the fill, and it is now genuinely that.

Unlike ok and warn, this foreground is **not `--ink`** and **not one value across themes**. Their
fills are bright pastels; `--destructive` is a deep red in light (white reads 4.88:1 there, near-black
only 4.30:1) and a light salmon in dark (`--ink` reads 7.26:1, white only 2.89:1). The fill flips
lightness between themes, so its ink has to flip with it.

Six call sites were using `text-`/`bg-destructive-foreground` as destructive ink or tint — they were
migrated to `--destructive` first, because redefining the token under them would have turned red
error text white on a white card.

## Annotation and enforcement

Every element whose color communicates status declares `data-status="ok"`,
`data-status="warn"`, or `data-status="error"`. The shared Badge uses
`data-status="warning"` for its public warning variant; the guard treats that spelling as the
`warn` semantic.

CSS selectors that apply status color include the attribute selector. Inside that selector:

- use only the matching `--status-*` or `--color-status-*` family for the status signal;
- do not use raw hex, `rgb(...)`, raw palette utilities, or fallback colors;
- do not use legacy `--success-*`, `--warning*`, `--destructive*`, or `--info*` as a status color;
- do not map one status to another status token.

The blocking Stylelint rule is `layers/status-color-contract`. It validates explicit status
selectors and follows file-local aliases such as the console's live motif back to their underlying
status semantic — including alias chains that resolve through the legacy families
(`--success*` → ok, `--warning*` → warn, `--destructive*` → error, `--info*` → info), so the
original console defect (`--cora-explore-live` aliasing the destructive family under a live
surface) is caught if reintroduced. Neutral colors used only to compose a tint remain allowed;
the matching status token still carries the meaning. Unit fixtures and the top-level negative
control cover raw green, a dangling hardcoded fallback, an ok-to-error semantic mismatch, and a
file-local alias resolving to the destructive family.

## Shipped inventory

The implementation inventory is code-grounded against the 2026-07-19 worktree. Every defect row
below is fixed in the shared contract or its consuming surface.

| Surface | Semantic | Previous rendering | Resolution | Status |
| --- | --- | --- | --- | --- |
| Shared Badge `success` | ok | raw Tailwind greens | `--color-status-ok`; annotated `ok` | Fixed |
| Shared Badge `warning` | warning | no semantic variant | `--color-status-warn`; annotated `warning` | Fixed |
| Shared OutcomeCard `success` | ok | raw Tailwind green text | `--color-status-ok`; annotated `ok` | Fixed |
| Shared and web-v1 stat-card positive delta | ok | hardcoded green fallback | `--status-ok`; annotated `ok` | Fixed |
| Shared and web-v1 stat-card negative delta | error | hardcoded red fallback | `--status-error`; annotated `error` | Fixed |
| web-v1 Home connected/event/live dots | ok | success token or dangling info fallback | status dots use `--status-ok`; info uses defined `--info` | Fixed |
| web-v1 account connection, authorization, SDK, and eligibility indicators | ok / warning | raw green and amber Tailwind palettes | matching status utilities and annotations | Fixed |
| web-v1 social account availability dot | ok | undefined success token with hardcoded green fallback | `--status-ok`; annotated `ok` | Fixed |
| Console explore live motif, glyphs, borders, and backgrounds | ok | destructive-red live alias | live alias resolves to `--status-ok`; live DOM is annotated `ok` | Fixed |
| Console build-job heartbeat | ok / warning when stale | selector-local success override | healthy uses `--status-ok`; stale uses `--status-warn` | Fixed |
| Console consensus outcome and connector health lines | ok / warning / error | neutral, generic warning, or destructive utilities | matching status utilities and annotations | Fixed |
| Console status badges | ok / warning / error | shared variants without a status contract | inherited semantic Badge variants and annotations | Fixed |
| Mobile chip tone map | ok / warning / error / info | legacy tone names backed by app-local palette values | shared `STATUS_COLORS` via semantic tones | Fixed |
| web-v2 Badge and stat-card consumers | ok / warning / error | inherited shared defects | inherit the shared component fixes | Fixed |

The mobile code/typecheck is part of the automated vehicle. Color perception on an Expo device or
simulator remains an operator visual check, as does the final light/dark founder walk.

## Cross-surface follow-up (post-#2973)

A later cross-surface audit under the same initiative (48d6a97a) swept the Cora-owned surfaces the
original research index missed. Both rows below sit **outside the Stylelint rule's reach** — it only
inspects `.css` files carrying a `[data-status]` selector, so React/RN component variants and Astro
theme tokens are structurally invisible to it (see `tools/stylelint/status-color-rule.cjs:102`).

| Surface | Indicator | Semantic | Rendered | Disposition |
| --- | --- | --- | --- | --- |
| `apps/cora-marketing/src/pages/live.astro` | `.st-run` "RUNNING", `.nb-name` "IN PROGRESS · N RUNNING", the `.tok-live` live-burn counter, and the `.now-*` frame | live / running (a working state) | `--t-red` (red) across all register themes | **Open — founder decision** (see below) |
| `apps/cora-console` and `apps/cora-mobile` `INBOX_KIND.delivery_attention` | "delivery" inbox badge | blocking delivery disposition | `destructive` / `error` (red) | **Kept red.** A delivery awaiting disposition is a blocking item, not mere attention, and red is permitted for blocking. Consistent across console and mobile; #2973 deliberately left it destructive when it reclassified the stale-squawk and dropped-hunch badges. |

### `live.astro` — why this is a founder call, not a mechanical swap

`live.astro` is a deliberate iterm/ANSI register: nine palette tokens per theme across ~9 themes,
with a documented editorial metaphor (`live.astro:476`) — **red = the live burn, green = shipped,
yellow = comparisons, magenta = cora.** "Running" is red because red = tokens burning *now*; green
already means *shipped / done*.

The literal ruling ("a live/working state styled red is a defect, even as a deliberate motif") is
the same shape as the console explore "on-air" motif this initiative migrated. Three things still
make the marketing page a distinct call rather than an implementer swap:

1. It is a **public marketing microsite**, not an operator surface — an outward-facing change.
2. Red is a **woven identity color** ("the burn") across nine register themes, not a status token;
   migrating it is a palette redesign, not a token remap.
3. "Running" and "shipped" are the page's **central visual contrast** (burning-now vs. done).
   Making both green collapses that distinction and the page's editorial concept.

Recommendation: route to the founder for a ruling on whether the sweep governs the public marketing
aesthetic; do not recolor unilaterally. No inverse defects (an error/failed state styled green) were
found on any surface.

## Research-index process finding

`apps/cora-console`, `apps/cora-mobile`, and `apps/cora-marketing` were absent from the research
index used by earlier planning passes — cora-marketing surfaced only in the later cross-surface
audit above. This implementation resolved the first two surfaces by direct working-tree inspection.
The codebase-knowledge-graph initiative should rebuild coverage for all three apps before a future
cross-surface inventory relies on that index alone.