---
name: contract-sidecar
description: Author or consume a screen's contract sidecar — the machine-readable JSON twin of a prototype screen (route, entities, CTAs, states, role gates, list params) emitted AT DESIGN TIME so downstream agents validate the contract instead of re-deriving it from rendered output. Use when authoring a prototype screen (design-builder, or the Global preamble's output contract for the console lane), when reconciling a spec/UIMAP (spec-architect), when reconstructing a screen (screen-reconstructor), or when classifying drift as contract-affecting (design-sync).
allowed-tools: Read, Write, Edit, Grep, Glob, Bash
---

> **Specialization:** generic by design — bind every `<…>` and "per `CLAUDE.md`" reference from the project `CLAUDE.md` (the prototype location + file pattern, the sidecar convention, the naming grammar, module codes). See `SPECIALIZE.md`. If a needed value is missing, STOP-and-ask.

## Why the sidecar exists — pay for the contract once

A screen's data-flow contract (fields, CTAs + effects, states, role gates, list params) is cheapest to capture **at the moment of design**, when the generating model already holds it in working context. Without a sidecar it is re-derived at least twice downstream — by `screen-reconstructor` (reading the rendered prototype) and by `spec-architect` (writing the UIMAP) — and a third time whenever `design-sync` must judge whether a drift is contract-affecting. The sidecar converts each of those *derivations* into a cheap *validation*, and makes contract drift a JSON diff instead of rendered-output archaeology.

**Law 1 is untouched.** The rendered prototype remains the visual & UX contract. The sidecar is the **data-flow contract's first draft with provenance** — where sidecar and render disagree, the render wins and the disagreement is a defect in the sidecar to fix (or a STOP-and-surface if the divergence is substantive).

## File convention

One sidecar per prototype screen file, a **sibling** with the suffix `.uimap.json`:
```
<prototype dir>/<screen-file>            e.g.  proto-screen-alpha.jsx
<prototype dir>/<screen-file>.uimap.json e.g.  proto-screen-alpha.jsx.uimap.json
```
(If `CLAUDE.md` pins a different sidecar location/suffix, that binding wins.)

## Schema (all keys lower-camel; omit empty arrays rather than emitting `[]` noise)

```json
{
  "sidecarVersion": 1,
  "route": "/module-slug/screen-slug",
  "screen": "screen-id",
  "module": "<MODULE_CODE per CLAUDE.md>",
  "scaffold": "<scaffold name per CLAUDE.md §page-scaffolds>",
  "formMode": "create|edit|view|none",
  "subject": { "entity": "<the entity this screen presents>", "inputKey": "<route param / context key>", "suppliedBy": "<upstream screen/CTA>" },
  "entities": [ { "name": "EntityName", "fields": [ { "name": "FieldName", "type": "string|number|date|enum|bool|ref", "required": true, "enumValues": ["…"], "inferred": true } ] } ],
  "ctas": [ { "label": "Verb-led label", "kind": "primary|secondary|row-action|drawer|link", "effect": "mutate|persist|navigate|open-overlay|export|stub|notify-sim", "destination": "/route or overlay id or null", "guard": "condition → disabled-with-reason, or null", "event": "EVT-<X>-Name or null", "audit": true, "identityConfirm": true } ],
  "states": ["default", "loading", "empty", "error", "…screen-specific…"],
  "roleGates": ["PERM-<X>-Entity.Action"],
  "journeys": ["J-…"],
  "listParams": { "sort": "…", "filters": ["…"], "paged": true },
  "liveElements": [ { "what": "…", "channel": "<realtime channel/group>" } ],
  "provenance": { "born": "in-repo|console", "briefId": "DB-…", "sourceHash": "<hash of the screen file>", "date": "YYYY-MM-DD" }
}
```

Field notes: `journeys` records journey membership so continuity audits build their ledgers from sidecars instead of re-tracing; `inferred: true` flags a type guess (a string might be an enum/date/coded value — same caution as UIMAP mock-lifting); `effect: "stub"` marks a briefed Marked-stub (disabled-with-reason), never a silent toast; `effect: "notify-sim"` declares an ACCEPTED fire-and-forget toast (honest simulation of an out-of-scope side effect, per the dead-edge-audit exceptions ledger) — any other toast-only handler is a defect; `guard` text must match the rendered inline reason verbatim.

**`formMode` — the create-vs-edit initial-state contract (a screen's true initial data state, not its dressed-up render).** Every form/entry screen declares one:
- **`create`** — the form opens **empty**: placeholders + *system-proposed* defaults only (a proposed id, today's date, a defaulted locale/status dropdown). It is **never** bound to a sample or existing record. A create form rendered pre-filled with record-identifying sample values (a real-looking name, email, address, phone, DOB, member id) is a **defect, not fidelity** — the prototype's "show mock/seed data at full fidelity" convention is **exempted for a create form's field values** (its true initial state is blank). This defect is the *inverse* of context-loss: context present that must be absent.
- **`edit`** — the form opens **bound to the subject entity** carried in via `subject.inputKey` (pre-populated is correct here). An edit form that opens blank/reset is the defect.
- **`view`** — read-only presentation of the subject. **`none`** — not a form screen (board/report/dashboard).

`formMode` is the single field the create-empty checks (edge-auditor / journey-auditor tells, the reconstruction + reviewer gate, and the preflight `*Form`/`*Model`-default detector) key off — without it there is nothing to check "opens empty" against.

## Producer duties (who writes one, and the one law of writing)

**The sidecar describes exactly what the screen file renders — nothing more, nothing less.** Every CTA/field/state in the sidecar exists in the render; every CTA/field/state in the render exists in the sidecar. Verify bidirectionally against the DOM before reporting Done.

- **`design-builder`** (in-repo lane): emits the sidecar as a mandatory deliverable, verified in its render gate.
- **Console lane** (Claude Design): the design-prompts board's **Global preamble** carries the sidecar requirement in its output contract, so the design tool delivers `screen-file + sidecar` together. If a console delivery arrives without one, `/promote-design` flags it and the orchestrator backfills it (author it from the delivered file + brief, mark `"born": "console"`), so no screen enters the registry sidecar-less going forward.
- **Backfill for legacy screens** is on-demand, not a sweep: when a sweep touches a pre-sidecar screen, `spec-architect` writes the sidecar as a by-product of the UIMAP it was going to write anyway.

## Consumer duties (validate, don't re-derive)

- **`spec-architect`**: when a sidecar exists, validate it against the rendered screen (spot-check the bidirectional rule), then **lift it as the first draft of the UIMAP** — the UIMAP adds what the sidecar doesn't carry (validation semantics, cross-module contract routing, absent-state gap list); it does not re-enumerate what the sidecar already proves. Hash-stamp the UIMAP with the sidecar's `sourceHash`.
- **`screen-reconstructor`**: use the sidecar as the completeness checklist for the fidelity gate (every sidecar CTA/field/state present in the reconstruction) — the *visual* contract still comes from rendering the prototype. **Honor `formMode`:** for a `create` screen, reproduce layout/placeholders/list rows 1:1 but initialize the form model **empty** (do NOT copy the prototype's sample field values — only system-proposed defaults carry over); for `edit`, bind to the carried subject.
- **`edge-auditor` / `journey-auditor`**: for a `formMode: create` screen, flag any input rendered with a non-placeholder, record-identifying value (the phantom-prefill tell); for `formMode: edit`, flag a form that opens blank (context-loss).
- **`design-sync`**: a MODIFIED screen's contract-affecting classification is first a **sidecar diff** (regenerate/compare the contract surface); only an ambiguous diff needs rendered-output comparison.

## Done when (producer gate)
- The sidecar parses as JSON, carries `sidecarVersion` + full `provenance`, and every enum above is respected.
- Bidirectional render check passes (sidecar↔DOM, both directions).
- Every `guard` string matches its rendered inline reason; every `effect: "stub"` is visibly a Marked-stub in the render.
- `formMode` is set for every form/entry screen; a `create` screen renders **empty** (placeholders + system-proposed defaults only, no record-identifying sample values); an `edit` screen renders **bound** to its subject.
- Zero invented vocabulary: `roleGates`/`event` names follow the naming grammar per `CLAUDE.md`; `inferred` flags are honest.
