# How a prototype works

A prototype is a disposable artefact that settles screens, states and decisions
**before** anyone writes Next.js. It is judged by what it settles, not by how
finished it looks. Copy this folder, rename it, fill it in, review it, lock it.

```bash
cp -r TEMPLATE P2-offer-thread
```

Then edit `prototypes.json` and the list in the root `index.html` to add the new
entry. Both are hand-maintained; keep them in step.

---

## 1. Naming and shape

One folder per prototype ticket, named `P<n>-<slug>/`:

```
P1-market-journey/
  index.html          # the entry screen and the state switcher
  screens.md          # screen inventory + the decisions this prototype must settle
  findings.md         # the outcome per decision, grouped by write-back destination
  screenshots/        # evidence, committed
```

`<n>` is a running integer, never reused. `<slug>` is short, English, kebab-case
and names the journey, not the component (`market-journey`, not `filter-panel`).

Sub-screens are extra HTML files in the same folder — `profile.html`,
`offer.html` — linked from `index.html`. No nesting, no `src/`, no shared folder
of your own: everything common lives in `/shared`.

---

## 2. States

Every screen declares its named states. In the HTML, one region carries
`data-states` and each direct child carries `data-state="<name>"`:

```html
<section data-states>
  <div data-state="default">…</div>
  <div data-state="loading">…</div>
  <div data-state="empty">…</div>
</section>
```

`shared/proto.js` builds the state `<select>` in the header from those names,
shows exactly one, and keeps the choice in the URL: `index.html?state=empty`.
The first child is the default. An unknown `?state=` falls back to it rather
than showing a blank page.

The vocabulary — use these names, add others only when the screen really has
another condition:

| State | It means |
| --- | --- |
| `default` | The happy path with representative data. |
| `loading` | Work in flight; skeletons, never a spinner on a blank page. |
| `empty` | The query succeeded and matched nothing. |
| `error` | The request failed. Say what failed and what to do. |
| `partial` | Some input was dropped or some data is missing, and the page says so. |
| `no-permission` | The user is signed in but not entitled to this screen. |

Locale (`ar`/`en`), theme (`light`/`dark`) and viewport (mobile/desktop) are
**not** states in the switcher — they are the toggles in the header and the
browser window. They multiply the screenshots, not the markup.

Screenshots are named:

```
<screen>--<state>--<locale>.png
market--empty--ar.png
market--default--en.png
```

Add `--dark` or `--mobile` only for the shots where that dimension is the point:
`market--default--ar--mobile.png`.

---

## 3. Decisions

A prototype exists to settle a named list of decisions, written **up front** in
`screens.md` before any HTML. Each gets an id `D1`, `D2`, … scoped to that
prototype. A decision is a question with at least two defensible answers that
the screen can distinguish — "does the availability filter live in the panel or
as a preset row above the results?" — not a task and not a taste poll.

If a prototype settles nothing, do not build it.

---

## 4. Write-back contract

A locked prototype's findings are worthless until they land somewhere a builder
will read. There are exactly three destinations, and `findings.md` has one
section per destination so the write-back is copy-paste, not translation.

**(a) A new user story on the owning epic** in the planning manifest
`gh-project/manifest.json` of the sibling project. Append it to the end of the
epic's `## Stories` block, in exactly this form — two lines, the second indented
by two spaces:

```
- **As a <actor>**, I <do>, so that <outcome>.
  - Accept: <criteria>.
```

Stories are **append-only**. Never reorder, renumber or reword an existing
story: other records point at stories by position.

**(b) A note on an existing story**, when the finding qualifies a story rather
than adding one. Add a `story_notes` entry on that epic, keyed by the story's
1-based index within its `## Stories` block:

```json
"story_notes": { "3": "partial: the preset row is above the results, not inside the panel." }
```

**(c) A rule for the code repo**, when the finding is a standing constraint on
how the UI is built rather than something a user asks for. It goes to
`.ai/rules/` in the code repo as a few lines under the matching glob, recorded
with the `record-rule` tool — never pasted into `CLAUDE.md`.

Two hard rules for everything written back:

- Requirement ids from any parked requirement document must never appear — not
  in a story, not in a note, not in a rule, not in a commit message. Restate the
  requirement in the project's own words or leave it out.
- A finding with no evidence is not written back. Cite the screenshot filename.

---

## 5. Review

Each prototype names one **reviewer** and one **timebox in days**, both recorded
in `prototypes.json` and at the top of `screens.md`. The timebox is the budget
for building *and* reviewing; when it runs out, the prototype is reviewed in
whatever state it is in. A half-answered decision is a finding.

Status moves in one direction only:

```
draft  →  in-review  →  locked  →  superseded
```

- **draft** — being built. The author edits freely.
- **in-review** — handed to the reviewer. No new screens; only fixes the review asks for.
- **locked** — decisions settled, `findings.md` complete, findings written back.
  **A locked prototype is never edited again**, not even to fix a typo. It is the
  record of what was decided on that date.
- **superseded** — a later `P<n>` answers the same question differently. Set the
  old entry to `superseded` in `prototypes.json` and name the successor in its
  `findings.md`. The folder stays in the repo; history is the point.

---

## 6. Screenshots

Taken by hand, in a real browser, by the person who built or reviewed the
prototype. **Do not automate this in the repo** — no Playwright, no npm, no
headless script committed here. The zero-build promise is worth more than the
convenience.

For each state that a decision rests on:

1. Size the window to **1280 × 800** (desktop) and **390 × 844** (mobile).
2. Take Arabic (`?locale=ar`) and English (`?locale=en`).
3. Take dark only where the decision is about colour, contrast or status
   legibility. Otherwise light is enough.
4. Save into `screenshots/` with the name from section 2 and commit it.

The URL carries the full state, so a screenshot is always reproducible: paste
`index.html?locale=ar&theme=dark&state=empty` and you are looking at exactly
what the reviewer saw.
