<!-- >>> cmp:feature harness -->
# __APP_NAME__ — AI delivery contract

Generated by [create-cmp](https://github.com/kvdm-co-pilot/create-cmp) with a verification
harness. Every AI session in this repo works under this contract.

**Principles** — derived, never claimed · prove the instrument before you read it · the layer you
changed cannot certify itself · proof costs what the change costs · never wait on nothing · a
signature binds content, a decision is closed · one record, read first. The full form with each
episode is create-cmp's `docs/PRINCIPLES.md` (upstream, not shipped); when a rule below and a
principle disagree, the principle wins and the rule is the bug.

## Definition of done

Done means `node qa/verify.mjs` reports PASS and the receipt it writes
(`qa/evidence/latest.json`) attests this tree — the hook checks the working tree by hash; commit
it with your change so CI and reviewers hold the same proof. Claiming completion without a PASS receipt is
a failure. SKIPped steps are recorded in the receipt, and a step that SKIPped for an environmental reason (no
device, no bootable AVD) makes the receipt unusable as done-evidence — `qa/receipt-check.mjs` refuses
it. Never present green-with-gaps as fully verified.

**Verify in two tiers — the full lane is a checkpoint, not an inner loop.** It builds,
tests, and gates the whole tree to produce the receipt, so it is slow by design; running it
after every edit wastes the minutes it takes. Iterate on the fast tier, and run the lane
once — when you believe the change is done.

- **Inner loop — run continuously (seconds):** the preview loop (below) for UI;
  `node qa/watch.mjs` for verification — a resident watcher that re-runs the fast tier
  (`node qa/verify.mjs --fast`) on every save and re-prints the step table, so the
  did-I-break-anything signal is free the way an IDE's errors-on-save are free; and
  `./gradlew :composeApp:desktopTest` for the unit tests your change touches. This is where
  you catch your own mistakes.
- **Checkpoint — run once, at done:** `node qa/verify.mjs`. It writes the receipt (a `--fast` run writes
  `qa/evidence/latest-fast.json` instead, so the watcher can keep running); commit
  the receipt with your change. The Stop hook (`qa/receipt-check.mjs`) then confirms — with a
  cheap hash check, not another lane run — that a valid receipt attests this tree, and CI
  re-runs the full lane on push. After a green checkpoint, do not re-run the lane unless you
  change the tree again.

Humans get the same gate at push time: run `node qa/setup-hooks.mjs` once (after `git init`)
to enable the shipped pre-push hook. It blocks a push whose receipt doesn't attest
the tree — the same cheap check, before code leaves the machine (`git push --no-verify` bypasses
it; CI still enforces it).

## Specifications — behavior starts here

New behavior begins as a spec clause in `specs/<feature>.spec.md`: Given/When/Then with a
stable id (see [`specs/README.md`](./specs/README.md)). Propose the clause, get it confirmed,
then implement. Durable tests cite their clause (`// SPEC: HOME-02`).

**A clause about device behavior must say so.** A citation proves a test *exists*; it cannot
prove that test could ever *observe* the promise. Add `[tier: device]` (or `[tier: e2e]`)
after the id when the claim is about OS facts a host JVM cannot see — lifecycle, alarms,
notifications, permissions, real navigation. `specCoverage` then requires a citation from
`androidInstrumentedTest` or `qa/e2e` for `device`, from `qa/e2e` alone for `e2e`, and FAILS without one, rather than accepting a
desktop test that is structurally blind to the claim.
[`specs/app-base.spec.md`](./specs/app-base.spec.md) states the architecture and shell
invariants the conformance gates enforce.

## Architecture

[`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) is the doc of record. Every normative
sentence in it carries a tier tag: `[enforced: CLAUSE-ID]`, `[governed]`, or `[advisory]`.
Its `cmp:generated` sections (expect/actual table, layer inventory, ADR index, glossary) are
derived from a tree walk, never hand-maintained — `node qa/arch-doc.mjs` regenerates them,
and `node qa/arch-doc.mjs --check`, a verify-lane step, fails the lane when they drift from
the tree. The governed `architecture` artifact (below) hashes the document alongside
`specs/app-base.spec.md`, so approving it is consent to this document. The summary:

- **Layers:** `presentation` → `domain` ← `data`. `domain` imports nothing app-internal;
  `presentation` never imports `data`. Koin wires implementations in `di/`.
- **Screens compose the registry vocabulary** (`presentation/components/*.kt` —
  `ScreenColumn`, `AppHeader`, `ContentStateContainer`, `ListItemCard`, …). Do not hand-roll
  a header, loading state, or list row; the exemplar's `HomeScreen.kt` shows the pattern.
  `ScreenColumn(screenTag = "<feature>")` tags the root, and ARCH-04 accepts that wiring as
  tag provenance — a literal `testTag` is only needed for content the registry does not
  already tag (per-row ids, for example). Every screen is a `*Screen` composable with a
  tested ViewModel.
- **Errors are typed, never thrown across layers.** Repositories return `AppResult`
  (`Failure` carries a `DomainError` kind). The data layer's `suspendRunCatching` is the
  only catch point and always rethrows `CancellationException`. ViewModels contain no
  `try`/`catch`; they fold results into a sealed UiState, and presentation maps error kinds
  to user copy.
- **Design values** (colors, spacing, typography, radii) come from the token catalog in
  `presentation/theme/`. No hardcoded literals in screens.
- **One feature is the exemplar** — the full pattern through every layer, tests included.
  It ships as `home`; `qa/approvals.json`'s `exemplarFeature` key retargets it to your own
  first feature once shaped (see "Configurable exemplar"). New features mirror the exemplar
  exactly: Screen → ViewModel (+ test) → UseCase (+ test) → Repository interface in `domain`
  + impl in `data` (+ test) → DI entry → navigation route.

## Testing

- **Unit** (`composeApp/src/commonTest`, run via `./gradlew :composeApp:desktopTest`):
  kotlin-test + coroutines-test + Turbine. Hand-written fakes in `testing/fakes/`; no
  mocking frameworks. Every new ViewModel/UseCase/Repository gets a test in the exemplar's
  style: Arrange-Act-Assert, behavior-named backtick tests, one behavior per test.
- **Conformance + screen tests** (`composeApp/src/desktopTest`): source-scanning
  architecture gates enforcing `specs/app-base.spec.md`'s ARCH clauses, Compose UI Tests
  (spec-cited, testTag selectors), and the golden-tree baseline (`qa/golden/`). Unintended
  golden drift means fix your change; intended drift is regenerated explicitly
  (`UPDATE_GOLDEN=1`) and declared.
- **E2E** (`qa/e2e/*.yaml`): Maestro flows; smoke covers boot + bottom nav. Select by
  testTag, never by display text.
- Never delete, weaken, or `@Ignore` a failing test to reach green. Fix the behavior — or,
  if the test itself is wrong, say so in your summary and justify the change.

**Platform behavior tests live in `composeApp/src/androidInstrumentedTest`** — when a
feature touches alarms, notifications, lock-screen intents, audio routing, **or app/process
lifecycle** (cold start vs warm resume, "once per process start", process death and
restore, `ON_STOP`/`ON_START`), its behavior test goes there, because no desktop tier can
see those OS facts. A desktop Compose test has no process lifecycle *at all*, so a claim
about one is unobservable there by construction — and `ProcessControl` below is the organ
that puts the device into the state such a claim is about. **Declare it on the clause**:
`- **MOTION-13** [tier: device] — Given a cold start, …`. The lane's `specCoverage` then
FAILS unless a test from a tier that can actually see it cites the clause, instead of
accepting a citation from a tier that cannot. Assertion helpers:
`NotificationAsserts`, `AlarmAsserts`, `SystemState`. **Runtime state control** — put the
device into the state your claim is about, instead of waiting for it: `TimeWarp` (clock,
timezone), `DozeControl` (forced idle), `PermissionControl`, `ProcessControl`,
`NetworkControl`, `ConfigControl` (dark mode, font scale, per-app locale). They compose —
the exemplar proves an `allowWhileIdle` alarm delivers from inside forced deep idle by
nesting a clock warp in a Doze bracket. Exemplars: `PlatformBehaviorSeamTest`,
`RuntimeStateSeamTest`. Each organ's header states what it does NOT reproduce; read it
before claiming more than it proves. The lane's `androidChecks` step runs them — the lane provisions its own device: an attached one, or a
headless AVD it boots itself. `CMP_DEVICE=none` opts out, and that SKIP is environmental, so the receipt is
then not done-evidence. See `docs/TESTING.md`.

## Evidence

`node qa/verify.mjs` writes `qa/evidence/latest.json` (schema: `qa/evidence/schema.json`).
Each PASS receipt names its **evidence rung** (L0 scaffold / L1 desktop / L2 device /
L3 release), derived from which steps actually ran and passed — never declared, and a
SKIPped step never upgrades it (see `docs/TESTING.md` §"The evidence ladder").
Commit it with your change; git history is the audit ledger. Binary artifacts under
`qa-artifacts/` are hashed into the receipt, never committed. The studio console's Evidence
page reconstructs the full audit trail from the git log of `latest.json` — every commit is
one verified, attributed state — so committing each receipt is what builds the record.

## The lane is not yours to edit

Every `.mjs` file directly under `qa/` and `qa/lib/`, every `.mjs` under
`qa/lib/profiles/**` (this app's stack profile — its steps, tiers, ladder and device
glue), and the declarations the lane reads (`qa/harness-manifest.json`,
`qa/verified-surface.json`) are **machine-owned**: harness code that is byte-identical in
every create-cmp app and carries no app content at all. It
belongs to `prooflane-harness` (the name in your `qa/harness.lock.json`), versioned independently of the engine that stamped this
app's shape, and `qa/harness.lock.json` records a sha256 of every one of those files.

`node qa/verify.mjs` checks that lock first, on every run. Editing lane code fails the
`harnessIntegrity` step and names the file — because a lane that has been modified cannot
honestly vouch for itself. Without that check the receipt was unfalsifiable in one
specific way: force every step to PASS in `qa/verify.mjs` and the receipt still validated,
since the edited file was simply part of the hashed input surface.

**So: do not edit `qa/*.mjs`, `qa/lib/*.mjs`, `qa/lib/profiles/cmp/**`, `qa/verified-surface.json`, or
`qa/harness-manifest.json`.** (A profile you wrote yourself under `qa/lib/profiles/<id>/` is yours; after
editing it, `npx create-cmp-cli harness relock` re-takes the lock.) If the lane is wrong, the fix is
upstream in the engine, not here. If you genuinely must fork it, know that
`npx create-cmp-cli upgrade --harness` will replace the region and preserve your edits as
`qa/harness-local.patch` for you to re-apply or upstream — nothing is lost, but the fork
stops being invisible.

Everything else under `qa/` **is** yours: `approvals.json`, `comments.json`, `golden/`,
`evidence/`, and `e2e/*.yaml` (seeded once at stamp time, app-owned forever after). So is
`specs/`, and so is every line under `composeApp/src/`.

Upgrading the lane is safe to do unattended — it touches no app content and no signed
artifact:

```bash
npx create-cmp-cli upgrade --harness --yes   # without --yes it dry-runs and exits 0 — a silent no-op from a tool
```

## Approvals — governed artifacts need a human's sign-off

Some artifacts are **governed**: a human approves them, and the approval is bound to the
artifact's content by hash (`qa/approvals.json`) — the evidence-receipt idea, applied to a
human decision. The ordered walk is a **definition order**, not just an approval order:
each artifact is the vocabulary the next is written in, so on a fresh app each step is a
conversation that ends in an approval — the genesis walk:

The genesis order — intent → first feature brief → architecture + structure → exemplar spec →
exemplar feature → design system → components → per-feature specs — is driven by the `cmp-new`
interview and is over before this file is your working contract; `node qa/approve.mjs --status`
prints every artifact and its state. Two disciplines survive it: **behavior is spec-first** (clauses
confirmed before the slice is built) and **visuals are UI-first** (the design system and component
vocabulary are distilled from real screens, so they lock after the exemplar — a provisional palette
carries the build until then; if the lock changes the exemplar's look, reopen → re-approve it — that
loop is the design, not a failure). Once approved, the component registry is law: adding or changing
a common component invalidates the approval until a human re-approves.

### After genesis — every change is the same loop

Genesis governs the app's birth. Everything after runs the SAME loop — decide →
contract → build → prove → sign — over a subset (`docs/CHANGE-FLOW-DESIGN.md` in the
create-cmp repo is the doc of record). Two lanes, one triage rule — and **the triage is
always visible**: your FIRST reply to any change request (new feature, edit, bug fix, copy
tweak, redesign — every entry point) states in one or two plain sentences what you
understood the change to be, which lane it takes, and why, before any tool runs. The human
can overrule the lane in a word; a silent route is a routing error even when the lane was
right.

**Grill before the brief** (the `grill-me` plugin skill carries the procedure; the rule holds without the plugin):
on the brief lane, after the triage restatement and before a word of the brief, settle
the load-bearing questions — a signed brief or spec is a CLOSED decision: cite it, never re-ask it;
ask the unsettled ones as a numbered list with a recommended answer each, and WAIT. Answers land in the brief's **Decisions** and
**Open decisions**; the signature closes them. The direct lane is not grilled.

### Configurable exemplar — the DNA features are cloned from

`qa/approvals.json` carries a top-level `"exemplarFeature"` key (absent means `"home"`, so
older ledgers keep meaning what they meant). It names the feature whose 11-file set is the
governed **exemplar-feature** artifact and the clone source `qa/scaffold-feature.mjs`
stamps new features from. The genesis walk's endgame is pointing it at *your* first real
feature: stamp it (`add-feature`), shape it, then set `exemplarFeature` — from then on the
stamper clones your pattern in your domain language, and `home` demotes to an ordinary
feature spec. If the configured exemplar has grown files beyond the canonical 11-file
shape, the stamper clones the canonical set and warns, listing exactly what it skipped.


## The walk — the user always knows where we are and whose turn it is

Every governed change is a **walk** through six stages, spoken ONLY in this vocabulary
wherever the human reads (chat, cards, commit prose): **Decide · Design · Contract ·
Build · Prove · Sign-off**. The mapping is mechanical — Decide=the brief, Design=the
rendered screens, Contract=the spec, Build=code+citing tests, Prove=the lane's receipt,
Sign-off=acceptance — and spec clauses are spoken as **promises** ("Contract: 7
promises agreed" · "Build: keeping promise 5 of 7" · "Prove: all promises kept,
evidence attached"). `node qa/walk-status.mjs` derives the live position; a
UserPromptSubmit hook injects it every prompt. **Render the injected state — never
your memory of it.**

**At kickoff** (with the triage restatement): print the itinerary — and DECLARE it as
the live chain, so the studio's Drive strip and the statusline's readers see the same
steps you just printed:

    Navigation redesign — the journey (brief lane)
    Decide → Design → Contract → Build → Prove → Sign-off
    Stops for you: 3 (Decide — now · Contract · Sign-off). Build and Prove never stop for you.
    First stop is now: 2 open decisions below.

```bash
node qa/plan.mjs --set "sign the brief | draft screens | agree the promises | build | full check | your sign-off" --title "navigation redesign"
```

**The chain is an offer, not an announcement** (drive-narration N6): show the declared
steps in your first reply and invite the reshape in one breath — "say the word and I'll
reorder" — then start work immediately; the chain gates nothing, so the offer never
blocks. If the human redirects, re-declare (`--set` again) without ceremony: their
reshape IS the new chain.

**The chain stays current** — this is part of the contract, not a nicety: advance it
with `node qa/plan.mjs --step N` as each step lands and `--done` when the request
lands (closing writes the request's line into the local trail the studio's Recent
requests fold shows). The current request itself is recorded mechanically (the
per-prompt hook), the steps are yours to declare, and every surface shows the
declaration's age — a stale chain reads as stale to the human watching the studio,
which is worse than no chain. While the full check runs, the chain's observed line
narrates the lane's own position (step, elapsed, usual cost) — quote THAT, never an
estimate. The chain gates nothing; the walk stays the truth for doneness.

**While a walk is open, the per-prompt inject carries the running protocol** — the `[studio: …]`
line (restore it if DOWN: the cmp-inspector `preview { projectDir }` tool, or tell the human once),
the `[chat header]` to open every reply with, verbatim, and the `▲ ARRIVED, UNPLANNED` line for work
that belongs to no open walk (default: after the current walk lands; one walk at a time). Render the
injected lines, never your memory of them; with no walk open it delivers nothing and you write none
of it. After the header: one line per stage transition, nothing per-file.

**At every human gate — loud:** a full stop card, never a bare question — and the
easiest act leads:

    ■ YOUR TURN — <feature> · stage 3 of 6: Contract — agreeing what it promises
    <what it is, in plain words — two lines maximum>
    → Easiest: the studio console at <url from the injected card> — the row carries the button.
    → CLI fallback: <the command>   (or "reply approve" when no console is up)
    After this: <the remaining stages, and which ones stop for the human>

## Comments — review feedback flows back through the agent

Approvals are binding (they gate the verify lane); **comments are advisory** — a human's
running commentary, with a defined path back into your plan, spec, and code.
`qa/comments.json` is the ledger; `qa/lib/comments.mjs` is the library, mirroring
`qa/lib/approvals.mjs`'s shape: state, validation, transitions, nothing fabricated.

**The loop of record:**

1. A human adds a comment from the preview console — on a screen, a spec clause, a
   design-system token or component, or an architecture tree node.
2. You observe it — `review_comments { waitForComment: true }` (plugin) blocks until a new
   one lands; without the plugin, `node qa/comment.mjs --list --open`.
3. You act on it — update the plan, the spec clause, or the code it points at.
4. You resolve it **after** acting, with a note saying what you did —
   `resolve_comment { id, note }` (plugin) or `node qa/comment.mjs --resolve <id> --note
   "..."` (CLI, records author `agent-cli`). The console then shows `resolved` plus your
   note. The console never edits code: humans comment, agents resolve.

| Command | What |
|---|---|
| `node qa/comment.mjs --list` | Every comment, open and resolved, with resolution notes |
| `node qa/comment.mjs --list --open` | Only open comments |
| `node qa/comment.mjs --resolve <id> --note "..."` | Resolve a comment, recording what changed |

A comment targets one of: a **screen**, an **element** (screen + testTag), a **spec-line**
(file + clause id), a **design-system** token, an **architecture** path, or **general**.
`addComment` refuses empty text and a target missing the fields its type requires — the
same refusal-over-fabrication stance as approvals. A ledger that exists but cannot be
parsed is never treated as empty (that would hide real feedback); reads and writes surface
the honest error instead.

## UI feedback loop — see what you build, without a device

<!-- >>> cmp:feature inspector -->
While building or changing any screen, use the preview loop instead of an emulator. It
renders this app's real screens (real DI, real theme, seeded data) headlessly in seconds
and tells you what your edit changed.

**With the create-cmp plugin (cmp-inspector MCP tools):**

1. `preview { projectDir }` — once per session. Returns a live gallery URL for the human
   (it re-renders on every save) and per-screen structural summaries for you. Sources are
   watched; you never run Gradle by hand.
2. After each edit: `preview_status { waitForRender: true }` blocks until the outcome.
   `changedLastRender` names the screens your edit touched (empty = the edit reached no
   screen); `lastErrorSource: "compile"` means the edit did not build — the compiler's `e:`
   lines are in `lastError`.
3. `preview_diff { screen }` proves the change in one call: `proven-clean` /
   `changed-with-regressions` / `no-change`. No snapshot bookkeeping.

**If the tools are missing:** capability absence is a fault to diagnose and report — never
a silent fallback. If ToolSearch finds no `cmp-inspector` tools, STOP and tell the human
which it is: the plugin is disabled (`enabledPlugins` in `~/.claude/settings.json` or the
project settings); the session predates the plugin's enablement (MCP servers attach at
session start — restart the session; no in-session retry will surface them); or the plugin
copy is stale/broken (run cmp-doctor's inspector-MCP check group). Report before degrading.

**Degraded path** — for environments where the plugin is genuinely unavailable (CI, other
agents), and only after the fault is reported: `./gradlew :composeApp:renderScreens` renders
every screen to `composeApp/build/previews/<id>/{screen.png, tree.json}` (`-Pscreen=<id>`
for one); `node qa/preview-gallery.mjs` builds a self-contained gallery page from the
output. What this loses: on-save re-render, changed-screen attribution, compile errors
in-band, and the `preview_diff` change proof — structured feedback replaced by pixels.

**Live tier — the human's live device view (standing step).** Whenever `connect_live`
succeeds, OFFER the `remoteUrl` it returns (`http://127.0.0.1:9500/inspect/remote`) to the
human — every time, not as a maybe. It is a self-contained browser page that mirrors the
running app (~700ms refresh) with click-to-tap driving the real device: they watch and drive
the actual app while you assert on the tree (`navigate_and_inspect` — its before/after delta
is the change proof live — and `inspect_tree`). It is also the right way for a human to
*watch* an e2e run.

Asserting persisted state: `db_query` reads bounded rows from the running app's database;
use it when a flow's proof is a row existing (or not) after an action, instead of shelling
into sqlite or trusting the UI.

When the app crashes or misbehaves on device: `runtime_crashes` returns persisted crashes
with cause attribution and `runtime_logs` bounded structured logcat for the app's pid; use
these before hand-grepping `adb logcat`.

Screens come from `inspector/PreviewRegistry.kt` (desktopMain). The `add-feature` and
`add-screen` stampers auto-register stamped screens at the `// cmp:anchor preview-registry`
marker; when you add a screen by hand, register it there — a forced-state variant is just
another entry (`"home@empty"`). Every common component also carries a story entry
(`"component.<kebab-name>"` in `inspector/ComponentStories.kt`); when you add a component,
add its story — the lane's `componentStories` step fails naming the missing id otherwise.
Assert on `tree.json` structure; never read PNG bytes. Pixels are for humans.
<!-- <<< cmp:feature inspector -->
<!-- >>> cmp:feature dev-client -->
For one interactive window instead of stills of every screen:
`./gradlew :composeApp:hotRunDesktop --auto` (Compose Hot Reload dev-client).
<!-- <<< cmp:feature dev-client -->

## Docs

[`README.md`](./README.md) (front page) · [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md)
(layers + how to add a feature) · [`docs/TESTING.md`](./docs/TESTING.md) (pyramid +
conventions) · [`CONTRIBUTING.md`](./CONTRIBUTING.md) (workflow, Conventional Commits) ·
[`docs/adr/`](./docs/adr/) (decision records — significant decisions get one). Keep
[`CHANGELOG.md`](./CHANGELOG.md) current under `[Unreleased]` as part of your change.

## Commands

| Command | What |
|---|---|
| `node qa/verify.mjs` | The verify lane (profile `local`) — the done checkpoint, run once |
| `node qa/verify.mjs --fast` | **Inner loop — NOT the done-gate**: skips the device/release tier (`releaseBuild`, `tokenDrift`, `e2eSmoke`, `androidChecks`, `releaseSmoke`), reuses unchanged pure-Node step results (`CACHED`, content-hashed inputs), and scopes unit tests to the working-tree change (broad-impact changes — build files, DI, theme, shared components, `qa/` — run the full suite). Its receipt records `"mode": "fast"`, earns no evidence rung, and the Stop hook refuses it — run the full lane once at done |
| `node qa/watch.mjs` | **Resident inner loop — never a gate**: watches `composeApp/src`, `specs/`, `qa/` and re-runs `node qa/verify.mjs --fast` on save (debounced — a save storm is one run; defers while a verify lane or a preview render holds the project). `--once` for a single pass, `--json` for line-per-run output. The done-gate stays one deliberate full `node qa/verify.mjs` run |
| `./gradlew :composeApp:desktopTest` | Unit tests only (fast inner loop) |
| `node qa/setup-hooks.mjs` | Enable the pre-push receipt gate (one-time, after `git init`) |
| `./gradlew :composeApp:assembleDebug` | Android debug build |
| `./gradlew :composeApp:assembleRelease` | Android release build — R8 + `lintVital`, the variant the lane's `releaseBuild` step proves. Produces an **unsigned** APK; signing needs a keystore, which is yours to create and keep out of the repo. |
| `./gradlew :composeApp:connectedDebugAndroidTest` | Instrumented behavior tests on the attached device (the lane's `androidChecks` step) |
| `node qa/framework-check.mjs` | GATE-RULES Rule 0: proves the lane returns both ways — PASS on the clean tree, FAIL by name on planted violations — in seconds, restoring every byte. Run it first in a repo whose harness is new or freshly upgraded. |
| `node qa/verify.mjs --profile smoke` | The smallest end-to-end lane: every pure-Node gate, no Gradle, no device — seconds. Proves the framework *returns*, never the change (its receipt is refused as done-evidence). Run it first in any repo whose harness is new or freshly upgraded |
| `node qa/verify.mjs --profile nightly` | Scheduled stage: everything `ci` proves with the determinism probe forced on. Proves the harness, never a change — its receipt (`stage: "nightly"`) is refused as done-evidence, exactly like `--fast`. Schedule it; never wait on it |
| `node qa/verify.mjs --profile release` | Ship-time lane: everything `ci` proves plus the audit-cadence report (`auditCadence` — which androidMain subsystems changed since their last recorded `cmp-audit`; a nudge, never a gate) and the release-APK Maestro smoke (`releaseSmoke`) |
| `node qa/verify.mjs --determinism` | Timezone determinism probe, alone: runs the JVM test tier twice under UTC-12 and UTC+14 and FAILs naming any test whose outcome differs — the dynamic net behind ARCH-13's static one. Opt-in inside a lane via `--profile ci --determinism`; never with `--fast`; writes no receipt on its own |
| `node qa/record-audit.mjs <subsystem>` | Record that a `cmp-audit` of an androidMain subsystem happened (appends subsystem + HEAD sha + timestamp to `qa/audits.jsonl`; refuses dirty/unknown targets). `--list` shows every derived subsystem and its audit status |
| `node qa/retrospective.mjs` | How this project actually uses its harness, from `qa/flight-recorder.jsonl` (appended by every lane run): fast vs full ratio, verbatim SKIP reasons grouped, whether the device tier is ever reached, longest stretch with no full lane. States only what the journal recorded |
<!-- >>> cmp:feature dev-client -->
| `./gradlew :composeApp:hotRunDesktop --auto` | Desktop dev-client with hot reload |
<!-- <<< cmp:feature dev-client -->
<!-- <<< cmp:feature harness -->
<!-- >>> cmp:feature !harness -->
# __APP_NAME__ — working guide

Generated by [create-cmp](https://github.com/kvdm-co-pilot/create-cmp) as a **minimal
scaffold**: the full app architecture and its tests, without the verification harness.

## Build & test

| Command | What |
|---|---|
| `./gradlew :composeApp:desktopTest` | Unit + conformance + golden-tree tests (JVM, seconds) |
| `./gradlew :composeApp:assembleDebug` | Android debug build |
| `./gradlew :composeApp:installDebug` | Install on the attached device/emulator |

Run `desktopTest` after every change — it carries the architecture gates that keep this
codebase coherent. Never delete or weaken a failing test to reach green.

## Architecture

[`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) is the working guide: `presentation` →
`domain` ← `data`; typed errors (`AppResult`, no cross-layer throws); design tokens from
`presentation/theme/` (no hardcoded values); every screen a `*Screen` composable with a
tested ViewModel, mirroring the `home` exemplar through every layer.
<!-- >>> cmp:feature inspector -->

## UI feedback loop — see what you build, without a device

`./gradlew :composeApp:renderScreens` renders every screen in
`inspector/PreviewRegistry.kt` headlessly (real DI, real theme) to
`composeApp/build/previews/<id>/{screen.png, tree.json}`; `node qa/preview-gallery.mjs`
builds one self-contained gallery page. Assert on `tree.json` structure — pixels are for
humans. Register new screens in the PreviewRegistry.

With a debug build running: `adb forward tcp:9500 tcp:9500`, then
`http://127.0.0.1:9500/inspect/remote` mirrors the live app with click-to-tap.
<!-- <<< cmp:feature inspector -->

## What full mode adds

The verification harness: a verify lane (`qa/verify.mjs`) with evidence receipts,
behavior specs, approval gates, feature generators, and a Stop hook that makes "done"
machine-checked instead of honor-system. One idempotent command installs it all:

```bash
npx create-cmp-cli harden
```
<!-- <<< cmp:feature !harness -->
