The family app's Shopping screen, restyled to the Pearl design system

The actual documents the agents read and work from, shown exactly as they are on disk — not a summary. See the progress view instead · All projects

Plan PLAN-PEARL-SHOPPING.md

# PLAN-PEARL-SHOPPING.md — the family app's Shopping screen, rebuilt in Pearl · Ochre, pixel-measured against the approved drawing

**🔴🔴 THIS IS THE ONLY PLANNING DOCUMENT FOR THIS SUBPROJECT. Do not create a second plan, tracker, summary, or scratch state file for it — extend THIS file or its STATE-PEARL-SHOPPING.md companion, and log a dated delta in PLAN-CHANGES-PEARL-SHOPPING.md. Any status view about this subproject is GENERATED from this plan and its state file; if a view disagrees with this plan, the plan wins.**

**Owner:** the Pearl screens drive (this session's successor, any machine) · **Overseer:** Fable, one thread for the Pearl screens bucket (build work + design QA) · **Design authority:** Sienna (`creative-director`), UI only — taste graded ONLY after the fidelity count is zero
**Rule: no step begins until its named entry artefact exists and its predecessor's PROOF has been produced and closed by a checker that is not the builder. A step with an unproven predecessor is a violation, not a shortcut.**

**Authority order:** Nick's dated words in §1a → this plan → `PEARL-DESIGN-SYSTEM.md` (the system) → `specs/FINANCES-PEARL-BUILD-SPEC-2026-09-04.md` (the shared-layer MECHANICS this plan reuses, never its screen content) → `PEARL-SCREEN-PROMPT.md` (the layout bar the drawing was built to).

---

- **NORTH STAR:** Nick opens Shopping on his phone or his Mac at family.heroesandsidekicks.io and sees the Pearl · Ochre screen he approved on 2026-09-04 — the same glass cards, the same one-viewport desktop with the store lists in two columns (on the day he approved it: Amazon left, iHerb and Costco right — the rule in STEP 9 places whatever lists have items that day), the same Add bar — with every list live from Monday, every check-off, add and "Order via Skippy" still working, and nothing else in the app changed. His words for the whole redesign: "truly legendary award winning premium apps" (2026-09-04) and for the layout bar: "negative space bugs me a lot".
- **FINISH LINE** (written now; the bar never rises mid-drive — anything found after these pass goes on the NEXT list):
  1. `family.heroesandsidekicks.io/?skin=pearl#shopping`, signed in as Nick, renders the Pearl Shopping screen at 375×812 and 1280×662, light.
  2. Shopping — mismatched properties: 0 · unmeasured anchors: 0 at 375×812 light · 1280×662 light (the fidelity check of STEP 4, on the published URL, evidence file named in STEP 12).
  3. Every §2 row is verified on the live surface by the blind checker (STEP 13) — every list, every row kind (link · no link · bundle), Add, check-off, Order via Skippy, every empty/loading/failed string verbatim.
  4. With no `?skin=pearl`, the Shopping screen is byte-for-byte the screen shipped today (STEP 6's DOM/computed-style diff prints 0 differences, re-run at STEP 12).
  5. The 900–1099px band renders the phone layout with no horizontal scroll at 1024 (STEP 11).
  6. Sienna's taste verdict and the verifier's verdict are both recorded against the side-by-side PNG (STEP 12), and the postmortem is written (STEP 14).
- **NEXT list** (found after the finish line, never worked in this drive): none yet.

---

> **STEP 0 — ARM THE LOOP, BEFORE ANYTHING ELSE.** Set a 5-minute loop. Every time it fires, answer these four in order and CORRECT any failure before doing anything else:
> 1. **NORTH STAR** — is what I am doing this minute moving this plan's North Star? If not, drop it and take the highest-value unblocked step that does.
> 2. **FAN-OUT** — is my queue full up to the concurrency cap (§T)? Full capacity means the cap is reached and a queue of ready work sits behind it — NEVER "launch everything at once". Below the cap with ready work → dispatch now. At the cap → queue, don't launch.
> 3. **CHEAP** — are cheap models doing the building? If anything expensive is building, move that work down now.
> 4. **BLOCKED** — for anything I have called blocked: name the three concrete things I tried. If I cannot, it is not blocked — drive through it now.
> Then keep building. The loop never stops until the FINISH LINE is proven.

---

## Already true (the distilled past — facts, not story)

- The Pearl design system is approved and written — evidence: `projects/personal/family-app/PEARL-DESIGN-SYSTEM.md` (header carries Nick's "approved dont ask again", 2026-09-04) and `projects/personal/family-app/pearl-tokens.css`.
- The Shopping drawing exists as generator output at phone and desktop on the 2026-09-04 18:50Z live pull, to the one-viewport bar (desktop frame measured 662px, menu 40→622, both columns 152→622) — evidence: `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs` + `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/screens/shopping.mjs` → `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html`; renders in `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/renders/`; commit `af724e310`.
- Nick approved the drawing set for building: "giv em the plans for all except for health we need to keep wokring on those - full /plan plans" (2026-09-04, after seeing `projects/personal/family-app/redesign-mockups/PEARL-LOOKBOOK.html`).
- The shared Pearl layer already exists in the live app from the Finances lane: `projects/personal/family-app/css/pearl.css` (generated by `projects/personal/family-app/tools/pearl-rename.mjs` from `pearl-tokens.css`, `pl-` prefixed, 1100px media queries), `projects/personal/family-app/css/pearl-nav.css`, `projects/personal/family-app/js/pearl-nav.js` (reads `?skin=pearl` → `body.skin-pearl`), `projects/personal/family-app/css/pearl-finances.css`, `projects/personal/family-app/js/pearl-finances.js`, `projects/personal/family-app/tools/pearl-class-map.json`, `projects/personal/family-app/tools/pearl-accent-sites.json` — evidence: `ls projects/personal/family-app/css projects/personal/family-app/js projects/personal/family-app/tools` and `projects/personal/family-app/index.html` lines 66–72.
- The live Shopping screen's hooks are known and frozen: `#view-shopping`, `#pp-hero-shop` (written by `projects/personal/family-app/js/pop.js` `setHero`, hero lines at lines 607–616), `.shop-segmented .seg-btn[data-shop]` + `is-active`/`aria-selected` (`projects/personal/family-app/js/app.js` lines 2199–2216), `#shopAddForm #shopAddName #shopAddQty #shopAddBtn #shopAddError` (lines 2219–2262), panels `#shop-iherb #shop-amazon #shop-walmart #shop-costco #shop-pharmacy #shop-unsorted` each with `.shop-order-bar > .shop-order-btn[data-shop-panel] + .shop-order-status`, `.card.td-card > .td-list.shop-list`, `.placeholder-note`; rows `.dc-prio.td-prio.shop-prio[data-id]` with `.chk`, `.pt.shop-link[href]` / `.pt.shop-nolink[data-nolink]` + `.shop-nolink-chip` + `.shop-nolink-note`, `.pw`, `.shop-go`; bundle rows `.shop-bundle-row` with `.shop-bundle-head[data-bundle]`, `.shop-bundle-chip`, `.shop-bundle-body`, `.shop-bundle-list`, `.shop-blink`, `.shop-bundle-total`; the empty string `List's empty.` in `.hc-empty`; the skeleton `.td-skel` — evidence: the `#view-shopping` block of `projects/personal/family-app/index.html`, `projects/personal/family-app/js/app.js` lines 2091–2470.
- Build and deploy path: `node projects/personal/family-app/build-dist.js` stages `css/` and `js/` as directories; `projects/personal/family-app/sw.js` `const CACHE` (line 179, `deck-family-v434` when re-read 2026-09-04 late evening — the number moves with every publish, which is why the re-measure command below is the fact and the number is not) and its ASSETS list are hand-maintained and gated by `projects/ops/skippy-jobs/_test-family-app-asset-version-parity.mjs`; deploy is `node projects/ops/deploy.mjs deck-family` (never a hand-rolled wrangler) — evidence: `projects/personal/family-app/README.md` lines 26–46, `projects/ops/deploy.mjs` line 60.
- The browser rig is `projects/shared-tooling/browser.mjs` (one machine-wide Chrome lock; `Page.captureScreenshot` measured to hang here on 2026-09-04 per the Finances spec §4.7; CSS transitions freeze) — the round-10 renders were nonetheless produced through the same rig by `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/tools/render.mjs`, so capture works for a settled page; STEP 4 re-measures both before relying on either.
- Nick's standing grants cover every test in this plan: agents drive his machine and apps as him, sign in through his identity gate, run their own tests — evidence: `node projects/ops/skippy-jobs/lib/standing-auth.mjs --audit` (read 2026-09-04: drive-nicks-machine-and-apps · click-through-identity-gate · agent-runs-its-own-tests).

## 0 · Gate Zero receipts (the plan may not exist without these)
- Failure Mode Registry loaded: 2026-09-04, 168 entries per `python3 projects/ops/agents/check_plan.py`'s own count (authoritative over a hand count); all 168 covered in §4.
- Canonical specs loaded: `.claude/skills/plan/SKILL.md` (§N, §L, §G, §S, §T, §W, §Z, §AUTH, §A, §D, §B), `projects/ops/PROMPT-SPEC.md` P1–P7, `projects/ops/agents/CODE-STANDARD.md`, `projects/ops/agents/CREATIVE-QA-STANDARD.md`, `projects/personal/family-app/PEARL-DESIGN-SYSTEM.md` + `projects/personal/family-app/pearl-tokens.css`, `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/PEARL-SCREEN-PROMPT.md`, `projects/personal/family-app/specs/FINANCES-PEARL-BUILD-SPEC-2026-09-04.md` (§4.6, §4.7, §6.1–6.4 mechanics), `projects/personal/family-app/FRONTEND-REBUILD-PLAYBOOK.md`, `projects/ops/walkaway/MODEL-MATRIX.md`.
- Ownership check: the family app's governing estate is `projects/personal/family-app/BUILD-PLAN-2026-08-17.md` + `projects/personal/family-app/PROJECT.md` (the app) and `projects/personal/family-app/specs/FINANCES-PEARL-BUILD-SPEC-2026-09-04.md` + `projects/personal/family-app/specs/FINANCES-PEARL-BUILD-HANDOFF-2026-09-04.md` (the first Pearl screen and the shared layer). Searched two ways 2026-09-04 (`command grep -rl "Pearl" projects --include='PLAN*.md'` and a suffix glob over `projects/personal/family-app/**/PLAN*`): no plan for a Pearl Shopping build exists; this plan is that subproject's own plan and REUSES the shared layer rather than building a second one. Feeds and systems: the app already owns the Shopping feed (`/api/shopping-*`, the Monday board via `SHOPPING_LIST` in `projects/personal/family-app/js/app.js`); nothing new is created there.
- Expected inputs confirmed to exist: all opened or globbed on disk 2026-09-04 — `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs`, `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/screens/shopping.mjs`, `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html`, the two Shopping renders under `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/renders/`, the live pull `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round3/solstice-app/live-2026-09-04.json`, `projects/personal/family-app/index.html`, `projects/personal/family-app/sw.js`, `projects/personal/family-app/contract-check.js`, `projects/personal/family-app/js/app.js`, `projects/personal/family-app/js/pop.js`, `projects/personal/family-app/css/pearl.css`, `projects/personal/family-app/js/pearl-nav.js`, `projects/personal/family-app/tools/pearl-rename.mjs`, `projects/personal/family-app/build-dist.js`, `projects/ops/deploy.mjs`, `projects/ops/skippy-jobs/_test-family-app-asset-version-parity.mjs`, `projects/shared-tooling/browser.mjs`, `projects/personal/skippy-app/design-directions/_pearl-fidelity-check.mjs` (the reference check), `projects/personal/family-vault/vault.py` (the gate password is read at run time by the rig and never written anywhere).
- Model matrix: executors named from `projects/ops/walkaway/MODEL-MATRIX.md` vocabulary — glm (zai) builds · deepseek does mechanical extraction · sonnet checks and authors tests · fable oversees and design-QAs (Nick, 2026-09-05: Fable for planning and oversight only; cheaper models build and check).
- PLAN AUTHOR: the Pearl screens design session (Fable), 2026-09-04, on Nick's direct dispatch ("giv em the plans for all except for health … full /plan plans").
- COLD READER: spec-breaker (a different session, briefed only with this plan's path), 2026-09-04 — verdict NOT READY with six disputes, all six applied the same night (the desktop column rule generalised beyond the approval-day data with a fourth-store test row S20; the §D GAPS line corrected — the drawing shows the three non-empty lists, not every list; state counts derived from the ground truth instead of typed, six order replies not seven; a REDLINE RULE added so work run against an unapproved page is re-checked, never rebuilt; the `sw.js` and `app.js` line citations corrected; the deploy's password-wall check added to STEP 12). Recorded in `PLAN-CHANGES-PEARL-SHOPPING.md` line 1.
- PROMPT-SPEC scan (P1–P7): P1 "plans for all except for health" — "all" = Shopping and Extras (the screens this session owns and drew; Home, Calendar, To-Do and Finances belong to other sessions), stated in §1; P3 "the test site … if it still exists" — measured 2026-09-04: Cloudflare Pages has only `deck-family`, so the build lands on production behind `?skin=pearl` (Finances Decision 1, Nick: "ok test is good if it still exists"); P4 "everything" nowhere in the ask; P6 "giv em"/"wokring" read as give them / working; P7 the two mid-turn corrections ("pets fine no worries just remove those screens", "meditation goes away too") scope the DESIGN SET — this plan's code fence leaves the live Meditation and Pets code untouched (§1a rows 5–6). Security work: none in this plan (§S) — the security pass is its own end phase.
- Hook notes for builders (house facts, not the tools' text): the Bash routing hook refuses `cd` + a relative write target and `$VAR` write targets — use absolute-path script files; the cheap lane refuses briefs that list folders, point at `.md` spec files, or contain the word that trips its secret filter — briefs name files by repo-relative path only.

## 1 · Goal and definition of done
> The drawing is CANONICAL: `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html` at the revision STEP 1 locks. This plan points at it and never restates it in different words.

- **What we're building, one paragraph.** The live Shopping screen (`#view-shopping`) gets a Pearl · Ochre skin under `body.skin-pearl`: the hero becomes the one-line header (eyebrow · serif count · the app's own hero line), the store switcher and the Add form share one pill toolbar, each store list becomes a glass card with the app's own rows (check box · name · × qty · ↗ or "no link yet" chip · bundle rows with their "N links ▾" chip), Order via Skippy stays a hairline button under the list, empty lists collapse to one-line cards, and on desktop the whole thing fits one viewport in two columns whose bottoms meet the menu's bottom, long lists scrolling inside their cards. Every hook, handler, fetch and string of today's screen is preserved; the Field screen stays byte-identical with the flag off.
- **HOW IT'S USED:** Nick (Chantelle equal) opens Shopping on the phone at the store or on the Mac at the desk, sees what is still to get per store, taps a row to buy, checks things off, adds an item, or taps Order via Skippy. · HOW WE KNOW: the live screen's own comments (`projects/personal/family-app/js/app.js` line 2091 "ONE TAP → THE PRODUCT PAGE", line 2403 "push a button to have skippy order these for us") and Nick's 2026-07-13/24 rulings quoted there.
- **WHAT IT LOOKS LIKE:** exactly the locked drawing — `shopping.html`, phone 375 and desktop 1280, light; accent Ochre `#B3862A` (soft `#F6EBCF`, text-size accent `#8D6921`) in four places: the Add button · the ↗ link glyph in a row · the active nav item · the wash corner tint (a bought item's pill would be the fifth; none is bought today). · HOW WE KNOW: Nick's approval words above, on the lookbook that showed these renders.
- **WHERE IT LIVES:** `https://family.heroesandsidekicks.io/?skin=pearl#shopping` (production, Cloudflare Pages project `deck-family`), opened by Nick; the flag-off screen unchanged for everyone else until the app-wide flip (out of scope). · HOW WE KNOW: `projects/ops/deploy.mjs` line 60 names the project; Cloudflare has no test project (measured 2026-09-04).
- **WHAT IT MUST DO:** (1) render the Pearl Shopping header, toolbar, list cards and rows from the live Monday data with zero invented strings; (2) keep Add, check-off (with its confirm), row tap-to-buy, no-link tap-to-explain, bundle expand, and Order via Skippy working through the app's existing handlers; (3) show every loading, failed, empty and reply string verbatim; (4) fit one viewport at 1280×662 with columns ending at the menu's bottom and lists scrolling inside cards; (5) render the phone layout at 375 and in the 900–1099 band; (6) leave the flag-off screen and every other screen unchanged; (7) measure zero mismatches against the locked target. Each is an eval in §6. · HOW WE KNOW: §2's rows and STEP 4's check.
- **NOT in scope:** the app-wide flip to Pearl by default (owned by the Finances lane's STEP 20 and Nick's Decision 1 — a flag decides every screen at once, not this one) · the nav chrome (phone bar and desktop rail: seven destinations, ink badges, no pill — owned by the chrome/Finances lane's `js/pearl-nav.js` + `css/pearl-nav.css`; this plan consumes it and posts handoff lines, STEP 11) · any change to the Shopping data feed, Monday columns, `/api/shopping-add`, `/api/shopping-order` or `js/app.js` (edited zero times) · the Health screens (still in design) · dark mode (Nick: "3 skip it") · security work of any kind (§S; the pass comes at the end) · Extras (its own plan, `PLAN-PEARL-EXTRAS.md`).
- **Trip-over protocol:** a builder that finds a defect outside the fence (a chrome mismatch, a Monday data oddity, a Finances-layer bug) writes ONE dated line to `STATE-PEARL-SHOPPING.md` under "Handoffs" naming the owner, then returns to its step — never investigates, never fixes.

Any inherited fact above carries its re-measure: hooks → `command grep -n '<hook>' projects/personal/family-app/index.html projects/personal/family-app/js/app.js`; the cache version → `command grep -n 'const CACHE' projects/personal/family-app/sw.js`; the deploy project → `command grep -n '"deck-family"' projects/ops/deploy.mjs`.

## 1a · Critical variables — the confirmation sheet is GENERATED from this table

| # | The variable, in plain words | Value chosen | Alternatives rejected | Class | HOW WE KNOW | Cost if wrong | CONFIRMED |
|---|---|---|---|---|---|---|---|
| 1 | **SURFACE — which screen this lands on, and who opens it**: the live family app's Shopping screen at family.heroesandsidekicks.io, behind `?skin=pearl`, opened by Nick (Chantelle equal) | Production behind the switch | The retired test site (does not exist on Cloudflare, measured 2026-09-04); a standalone mockup | V1 | Nick's own dated words | Building for a screen nobody opens | Nick, 2026-09-04, "ok test is good if it still exists" (it does not, so production behind the switch) and "we redid the app today it has fewer buttons" |
| 2 | The look — Pearl with Ochre as this screen's one accent, light only | Pearl · Ochre, per the locked drawing | Field (retired); a second accent; dark mode | V1 | His verbatim rulings | The wrong screen built pixel-perfectly | Nick, 2026-09-04, "1 approved dont ask again" (Pearl) · "3 skip it" (dark) · "skip lagoon lavendar iris mint eucalyptus apricot - use the rest" (Ochre stays) |
| 3 | The drawing that is the target — the round-10 Shopping page as shown in the lookbook, to the one-viewport bar | `shopping.html` at STEP 1's locked revision | The earlier round-10 render (grew to content); redrawing during the build | V1 | His approval words on the lookbook | Measuring the build against the wrong page | Nick, 2026-09-04, "giv em the plans for all except for health … full /plan plans" (on PEARL-LOOKBOOK.html showing this drawing) |
| 4 | Desktop shows every store list at once (two columns), the switcher choosing only where Add files; phone shows every non-empty list stacked in one column with the same switcher meaning (corrected 2026-09-05 at Sienna's signing — the prose here had said "one list at a time", which the approved drawing does not show; PLAN-CHANGES delta) | As drawn | Desktop one-list-at-a-time like today; phone showing all lists stacked | V1 | The drawing he approved shows both columns at 1280 and the switcher on the phone | A desktop that hides five of six lists, or a phone with a five-screen scroll | DEFAULTED to the drawing, 2026-09-04; sheet row — Nick may override |
| 5 | The Unsorted list — stays hidden until an item's vendor matches no store, exactly as the app does today | App behaviour unchanged | Always drawn as a sixth card | V2 | opened `js/app.js` lines 2138–2143, 2026-09-04, saw: `unsortedBtn.hidden = n === 0` | A permanent empty card he never asked for | opened js/app.js line 2138, 2026-09-04, saw the hidden-until-needed rule |
| 6 | Fonts, exactly as the system says, coded into the build | Body `"Helvetica Neue",Helvetica,Inter,Arial,system-ui,sans-serif`; headlines and numerals `"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif`; no web font | Any substitute face | V1 | His words | Every size and weight renders off, and the fidelity check fails on fontFamily | Nick, 2026-09-04, "keep them as is then - just make sure all plans dictate striclty the fonts to be coded into the apps with each build" |
| 7 | What "done" means for this screen — great, not perfect: the finish line above, then stop | The six FINISH LINE items | Endless polish rounds; a second adversary after a PASS | V1 | Plan skill §G in his words | A screen that never ships, or one that ships wrong | Nick, 2026-09-04, "we need our definition of done to be great, not perfect" |

- V1 confirmation reads `<name>, <date>, "<their own words>"`; the DEFAULTED row (4) carries its default's author and date and appears on the sheet — work downstream of it is a swap, never a rebuild.
- V2 confirmation reads `opened <what>, <date>, saw: <what was actually there>`.

**Considered and ruled NOT critical** *(the denominator — never demote a variable silently)*:
- `Which cheap vendor builds` — the model matrix decides; any cheap builder yields the same file, the checker is what matters.
- `The exact cache version number` — derived (`before + 1`) at STEP 6, never chosen.
- `The 900–1099 band's layout` — settled by the Finances spec STEP 17 (phone layout, the only one that cannot look broken); reused here.
- `Whether the flag-off screen may change` — no; §1 anti-scope and STEP 6's diff make it a proof, not a choice.

## 1b · Subproject decomposition — could a piece of this ship on its own?

- **SINGLE SUBPROJECT:** this plan IS the subproject (the Pearl redesign's Shopping screen); nothing inside it ships alone — the header without the lists, or the lists without the toolbar, is not a screen Nick would open. Its siblings (Finances, Home, Calendar, To-Do, Extras, Health) each have their own plan and owner; the shared layer and the chrome are the Finances lane's.

## 2 · The complete UX map (this becomes the test manifest verbatim)

| Id | Screen / entry point | State (default·empty·error·loading) | Element / interaction | Expected behavior | Navigation from → to |
|---|---|---|---|---|---|
| S1 | `/?skin=pearl#shopping`, phone 375 | default | Header: eyebrow "Shopping · On the list", serif count + "to get", hero line from `pop.js` lines 607–616 ("Fridge is judging you" ✦ / "Quick run — in and out" / "One thing — quick run" / "Nothing on the list" / "List cleared — fridge is stocked ✦") | one line; count and line are the app's own values | any tab → Shopping |
| S2 | same | default | Hero pills (`#pp-hero-shop` pills, one per open item up to 12; bought = accent) | count equals open items; none accent today | — |
| S3 | same | default | Store switcher `.shop-segmented .seg-btn[data-shop]` (iHerb · Amazon · Walmart/Local · Costco · Pharmacy; Unsorted hidden while empty) | active chip ink on white; tapping chooses where Add files (the app's own handler at `app.js` line 2199 still runs); under the skin all non-empty lists stay visible at every width (Sienna's ruling (b), 2026-09-05) | — |
| S4 | same | default | Add form `#shopAddForm` (`#shopAddName` "Add an item…", `#shopAddQty` "Qty", `#shopAddBtn` "Add") | Add button is the accent's primary button; submit posts `/api/shopping-add` with the active store and refetches | — |
| S5 | same | error | Add fails | `#shopAddError` shows "Couldn't add that item — try again in a moment." (verbatim `app.js` line 2254) | — |
| S6 | same | loading | Panel before data (`.td-skel` row inside `.shop-list`) | skeleton row rendered as a Pearl skeleton (same node, restyled) | — |
| S7 | same | error | Feed unreachable (`.placeholder-note`) | "Couldn’t reach the list right now — it’ll retry automatically." verbatim (the app authors both apostrophes as &rsquo; U+2019 in index.html lines 2544/2553/2569 — corrected 2026-09-06 17:10Z from straight quotes, which the blind check measured as a byte mismatch) | — |
| S8 | same | empty | A store with no items | `.hc-empty` "List's empty." rendered as the compact one-line card | — |
| S9 | same | default | Plain row with a link (`.shop-prio` · `.chk` · `.pt.shop-link` · `.pw` × qty · `.shop-go` ↗) | ↗ in accent text colour; tap opens the product page in a new tab (`target=_blank`) | row → vendor page |
| S10 | same | default | Row with no link (`.shop-row-nolink`, `.pt.shop-nolink[data-nolink]`, `.shop-nolink-chip` "no link yet") | tap toggles `.shop-nolink-note` "No product link yet — nothing to open. Ask Skippy to find one." | — |
| S11 | same | default | Bundle row (`.shop-bundle-row`, head `[data-bundle]` with "× N items · total" and "N links ▾" chip) | tap expands `.shop-bundle-body` (numbered `.shop-blink` links, "Total ≈ … — tap each to buy"); `aria-expanded` toggles | — |
| S12 | same | default | Check-off `.chk` on any row | `onShoppingCheck` confirm ("Mark \"<label>\" bought? This checks it off on Monday too." where <label> is the row's `.pt` textContent AS THE APP ITSELF COMPOSES IT — name, then `× qty`, then the `no link yet` chip text for a no-link row, app.js lines 2365–2372 and 2480; identical under `?skin=field` — corrected 2026-09-06 17:10Z: the earlier `<name>` placeholder was looser than the app's own behaviour) then the row leaves the list; failure string "Couldn't update Monday — try again in a moment." | — |
| S13 | same | default | Order via Skippy `.shop-order-btn[data-shop-panel]` (iHerb, Amazon, Walmart/Local as "Order from DAC via Skippy", Pharmacy; none on Costco) | replies in `.shop-order-status`: "Staging…" → "Staged — review it in the Actions tab." / "Already staged — check the Actions tab." / "Couldn't stage that — try again in a moment." / "Nothing to order — list's empty." (`app.js` lines 2403–2440) | — |
| S14 | same | default | Phone bottom bar (chrome lane) | seven destinations, Shopping active in Ochre on soft tint, badges ink | Shopping → any |
| S15 | `/?skin=pearl#shopping`, desktop 1280×662 | default | One-line header (eyebrow · count · line), toolbar pill (switcher left · Add form right), two columns placed by STEP 9's rule (on the approval-day data: Amazon (absorbs) + the first empty list's one-line card · iHerb, Costco (absorbs) + the second empty list's card) | frame fits 662; menu 40→622 and both columns end at 622; lists scroll inside with a bottom fade | — |
| S20 | `/?skin=pearl#shopping`, desktop 1280×662, a fourth store gets an item (Add `pearl-check-<date>` to Walmart/Local, then check it off) | default | The column rule with four non-empty lists | the new card lands in the shorter column by the rule, stays inside the 662 frame, and its column still ends at 622; the empty-list count drops to one | — |
| S16 | same | default | Desktop rail (chrome lane) | seven destinations in four groups (Today · Money · Body · House), Shopping active, badges ink, no pill | — |
| S17 | `/?skin=pearl#shopping`, 1024×768 | default | The band `desktop.css` does not author | phone layout, `.pl-rail` absent, no horizontal overflow: `document.documentElement.scrollWidth <= clientWidth` (corrected 2026-09-06 17:10Z: the shared decorative `.pl-wash` from css/pearl.css is absolutely positioned wider than the viewport by design and clipped by `#view-shopping{overflow:hidden}`; it is not content and does not scroll, so "no element wider than the viewport" was the wrong wording) | — |
| S18 | `/#shopping` (no flag), all three widths | default | The screen shipped today | byte-identical DOM and computed styles to the pre-build capture — INSTRUMENT: the suite's `--fence-pair off` (control `?skin=field`) printing `0 changed elements` at 375/1024/1280 within one frozen session, against the capture `redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json` (named 2026-09-06 17:10Z so a blind checker has the baseline; measured 0 · 0 · 0 by the builder and twice by the verifier on 2026-09-06) | — |
| S19 | `/?skin=pearl#shopping` as Chantelle | default | Same screen signed in as the second identity | identical layout; her lists (same Monday board) | — |

## DESIGN FIDELITY GATE (plan skill §D)
- **LOCKED TARGET:** generator `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs` + `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/screens/shopping.mjs` → output `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html` (the single output; redlines go into the generator and it is republished) · published login-free address: `https://skippy-designs.pages.dev/family-pearl-shopping-r10.html` — **NOT YET LOCKED — STEP 1 builds it** (adds the `REV` header, regenerates, publishes the byte-identical copy through `node projects/ops/deploy.mjs skippy-designs`, records REV + commit hash here) · Nick's approving words, naming the page as shown in the lookbook: "giv em the plans for all except for health we need to keep wokring on those - full /plan plans" (2026-09-04) — STEP 1 ends with his approval of the PUBLISHED page at its named revision, the one sanctioned wait in this plan; every non-visual step proceeds meanwhile · approved revision: `REV 10.6 · commit ed5a0bb729f888d6fdc906524635ed2a76d083ec` (published 2026-09-05 at https://skippy-designs.pages.dev/family-pearl-shopping-r10.html, sha256 equal to the generator output) — approval basis: the page is byte-identical to the round-10 Shopping drawing Nick approved in PEARL-LOOKBOOK.html on 2026-09-04 ("giv em the plans for all except for health … full /plan plans"); the overseer relays the address to him and records his words here if he redlines. (10.5 → 10.6, 2026-09-05: phone Add fields 16px/11px — Sienna's redline on the iOS auto-zoom rule; the published sha256 re-proven equal) · (10.6 → 10.10, 2026-09-06 15:00Z: Shopping's accent Ochre → Cranberry #A3283C/#F5DCE0/#7E1F2F on Nick's redline "none of the color accents really show through here - pick a new color that is better visibility and not blue"; the shared generator had already reached 10.9 under the Extras and To-Do lanes, so this redline is 10.10 — approved revision now `REV 10.10 · commit a960bdd50b6b6786670eabfaec42714bb16dfedb`, republished at the same address; the sha256 equality is the fix-round builder's to prove) · (10.10 → 10.11, 2026-09-06 22:30Z: Shopping's accent Cranberry → Mint #1B8259/#D8F0E4/#146646 on Nick's second redline "pick another accent color… maybe a greenish mint… change in nav and all other areas of the page" — approved revision now `REV 10.11 · commit 4f5f4d488`, republished at the same address, served sha256 equal; the rail and tab-bar highlight follow through the body-scope tokens)
- **ANCHOR MAP:** `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs-anchors-shopping.md` — **CREATED BY STEP 3**, signed by Sienna's design QA with the drawing's distinct-element count beside the anchor count.
- **FIDELITY CHECK:** `projects/personal/family-app/tools/pearl-fidelity-shopping.mjs`, adapted from `projects/personal/skippy-app/design-directions/_pearl-fidelity-check.mjs`, carrying the retired-colour sweep (Field palette `#c04040 #AE6B50 #7D6E5E #65704F #55697E #40706F #fbbf24 #d99a3a` and the dropped accent Mint `#2FBF8F`; the other five dropped accents were never given a hex in any file — searched two ways 2026-09-04 — so the anchor map's `RETIRED:` line carries this list and grows if one appears) and the plain-app fence check (a DOM count of every other view and of `#view-shopping` with the flag off, before and after, same route, same viewport) — **CREATED BY STEP 4**.
- **VIEWPORTS AND THEMES:** 375×812 light · 1280×662 light — equal to the locked target's own list (the drawing draws phone 375 and desktop 1280 at its 662px frame; light only, dark skipped by Nick) — signed by Sienna's design QA at STEP 3 alongside the anchor map.
- **RULE:** "No screen closes above `mismatched properties: 0 · unmeasured anchors: 0` at every viewport × theme, or with an unsigned GAP. More than two GAPs on a screen is a FAIL."
- **REDLINE RULE:** STEPS 3–11 may run against the published-but-not-yet-approved page (the map and the check address elements by selector, so a redline changes expected VALUES, not the build's hooks). If Nick redlines, STEP 1 republishes REV n+1, the §D revision line moves, Sienna re-signs only the anchors whose design changed, and STEPS 7–11 re-run their injected check against the new page — one check round, never a rebuild. STEP 12 opens only on the APPROVED revision, and its live count is against that revision alone.
- **GAPS:** none yet. What the drawing actually shows on desktop: the THREE lists that had items on 2026-09-04 as glass cards, and the TWO empty lists as one-line cards — not every list as a card. So the anchors are COMPONENTS (a list card, an empty-list card, a row of each kind), and a fourth non-empty store on another day renders the same components under STEP 9's placement rule; the check measures whichever stores are non-empty that day against the same anchors (§2 S20 forces the four-store case). The remaining candidate for a GAP is the hero pills (the drawing draws twelve; `pop.js` caps at its own `pillsTotal` — the check measures the live count). Sienna decides both at STEP 3 with no allowance pre-spent here.
- **SCOPE RULING (Sienna, creative-director, 2026-09-06 14:15Z):** chrome anchors #50 rail group label, #51 rail row inactive, #52 rail row active and #53 rail badge are Home-lane chrome, OUT of this screen's §D fidelity scope — a scope boundary under her Condition 1 and NOTE 4 in the signed anchor map, NOT a GAP, counting nothing toward the two-GAP limit; they stay measured by the Home lane's own plan (`PLAN-HOME-PEARL.md` U24). STEP 12's bar is therefore `mismatched properties: 0 · unmeasured anchors: 0` across anchors #1–#43 and #55. The ruling is verbatim under STEP 12 in STEPS and in `redesign-mockups/concepts-2026-09-04-round10-screens/evidence/shopping-step12-sienna-scope-ruling.md`; the hand-off with her two flags for the Home lane is in `PLAN-CHANGES.md` at 2026-09-06 14:15Z. Reopens if the rail visibly breaks the Shopping screen by eye at 1280, or Nick rules on the rail again.

## 3 · Lanes and frozen contracts

**File fences are drawn so no two lanes need the same file in the same hour (skill §W). Scoped commits only (`git commit -m "..." -- <paths>`); never `git stash`; re-read a file immediately before writing it; the family app sits inside the outer repo — every step's first action is `git -C "/Users/nickdeck/Documents/Claude 2.0" rev-parse --show-toplevel` and the printed path is recorded.**

| Lane | Scope (in / out) | Owner | Definition of done | Model (explicit) |
|---|---|---|---|---|
| SHOPPING | In: NEW `css/pearl-shopping.css`, NEW `js/pearl-shopping.js`, NEW `tools/pearl-fidelity-shopping.mjs`, NEW `tools/pearl-shell-rename.mjs` + NEW source `redesign-mockups/concepts-2026-09-04-round10-screens/pearl-shell-tokens.css` → NEW generated `css/pearl-shell.css` (the round-10 frame/scroll/header/who/chips rules shared by every round-10 screen; Extras consumes it, never edits it), NEW `gen.mjs-anchors-shopping.md`, NEW `ground-truth-shopping.json`, evidence under `redesign-mockups/concepts-2026-09-04-round10-screens/evidence/` named `shopping-*`; ONE edit each (STEP 6) to `index.html`, `sw.js`, `contract-check.js`. Out: `js/app.js`, `js/pop.js`, `css/styles.css`, every `pearl-*` file the Finances lane owns (`css/pearl.css`, `tools/pearl-rename.mjs`, `css/pearl-nav.css`, `js/pearl-nav.js`, `css/pearl-finances.css`, `js/pearl-finances.js`), every other view. | this drive | FINISH LINE items 1–6 | glm builds · deepseek extracts · sonnet checks · fable (Sienna) design-QAs and oversees |

**Contracts between lanes (FROZEN at plan time — change = dated `PLAN-CHANGES-PEARL-SHOPPING.md` delta):**
- **The switch:** `body.skin-pearl`, added by `js/pearl-nav.js` from `?skin=pearl` (Finances STEP 3, live). This plan never adds a second switch.
- **The generated token layer:** `css/pearl.css` (`pl-` classes, 1100px media queries, `body.skin-pearl{--acc … }`). This plan's composition sheet scopes to `body.skin-pearl #view-shopping` and sets `--acc:#B3862A;--soft:#F6EBCF;--deep:#8D6921` INSIDE that scope, so Finances' Cenote accent and Shopping's Ochre never meet.
- **The shell layer:** `css/pearl-shell.css`, generated by `tools/pearl-shell-rename.mjs` from `pearl-shell-tokens.css` (extracted from `gen.mjs`'s `FRAME_CSS` + the shared part of `EXTRA_CSS` by STEP 5, same `pl-` prefix and media-query conversion rule as the token layer), scoped to `body.skin-pearl .pl-shell`; owned by THIS lane, consumed by Extras. Its class names are frozen once STEP 5 lands (map file `tools/pearl-shell-class-map.json`).
- **The chrome:** phone bar + rail come from `js/pearl-nav.js` + `css/pearl-nav.css` (Finances/chrome lane). Nick's rulings this lane relays there: seven destinations (Home, Calendar, To-Do, Finances, Health, Shopping, Extras; groups Today · Money · Body · House), badges in ink, no "Skippy has N things waiting" pill, active item accent = the current screen's accent. STEP 11 measures and posts the handoff.
- **The hook rule** (Finances spec §6.4): does JavaScript read, write or walk past a node? Yes → it is carried exactly. `js/app.js` and `js/pop.js` are edited zero times; Pearl restyles the existing nodes in place and adds wrapper/frame nodes only where the drawing needs them (the two columns, the scroll regions, the one-line empty cards), via `js/pearl-shopping.js` on a `MutationObserver` over `#view-shopping .shop-list` and `#pp-hero-shop` with a re-entrancy guard and a microtask debounce (the Finances §6.2 mechanism, one Pearl render per app render).
- **Neutraliser:** explicit, enumerated properties only inside `body.skin-pearl #view-shopping`; `all: unset` and `all: revert-layer` are banned (playbook §6).
- **Widths:** 375 · 1024 · 1280 (Finances rule 6); breakpoint 1100px; light only.
- **Accent sites:** `tools/pearl-accent-sites-shopping.json` — derived by STEP 7 from the drawing's CSS (the Add button, the `.shop-go` glyph, the active nav item, the wash tint, the bought pill); the accent may never appear on a label, heading, body text, hairline or card edge.

## 3b · Execution map — the Step map, then one STEP block per row

A task is DONE only when its review-ledger row is CLOSED by a reviewer that is not the builder.

**Standing rules for EVERY step (Finances §6.4, restated once):** (1) first action `git -C "/Users/nickdeck/Documents/Claude 2.0" rev-parse --show-toplevel`, record the path; (2) snapshot before edit, step-numbered: `<file>.pre-pearl-shop-step<N>-20260905.bak`, deleted only after the step's checker closes it; (3) commit with an explicit pathspec, then `git show --stat HEAD`; (4) any step touching `index.html` tags also edits `sw.js` (ASSETS + CACHE) in the same change; (5) every step ends with `node projects/personal/family-app/contract-check.js` green with the number quoted and `node --check` on every touched `.js`; (6) builder = cheap lane (`projects/ops/cheap-task.mjs` for new files, `projects/ops/route-build.mjs` for one existing file) with the §T dispatch header pasted verbatim; checker = a Sonnet session that did not build it; (7) every `--prove` states what must NOT change and proves it; (8) no `--prove` hardcodes a count it could derive.

**Step map (read this first):**

| Stage | # | Task (step name) | Gate to enter | EXECUTOR (model, from the matrix) | CHECKER (different model — never the builder) | DONE-PROOF (runnable command) | Ends when |
|---|---|---|---|---|---|---|---|
| Plan | 1 | Lock the target: REV header, regenerate, publish login-free, record revision; open the state file | nothing — start now | sonnet | glm | `command grep -c '^// REV ' projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs` prints 1 and `curl -s -o /dev/null -w '%{http_code}' https://skippy-designs.pages.dev/family-pearl-shopping-r10.html` prints 200 (the published copy is CREATED BY STEP 1's deploy) | REV + commit hash written into the §D block; Nick's approval of the published page recorded (the one sanctioned wait — every other step runs meanwhile) |
| Plan | 2 | Ground truth for Shopping: ids, classes, data-attributes, strings, endpoints, signed-in rendered DOM | nothing — start now | deepseek | sonnet | `ls projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens` shows ground-truth-shopping.json (CREATED BY STEP 2's run; the checker re-lists) and its four counts print ≥ 21 · 30 · 12 · 3 | the file exists with no `$`-prefixed number and no credential |
| Design | 3 | Anchor map, signed by design QA with the element count | STEP 1's published page + STEP 2's ground truth | sonnet | fable (Sienna, creative-director) | `ls projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens` shows gen.mjs-anchors-shopping.md (CREATED BY STEP 3) with ≥ 40 rows and one `SIGNED BY` line | every drawn element is an anchor or a signed GAP; viewport list signed |
| Tests | 4 | The fidelity check for this design, with its red-proof, retired-colour sweep and plain-app fence check | STEP 3's anchor map | sonnet | glm | `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --selftest` (CREATED BY STEP 4) prints `red-proof: 1 mismatch (paddingTop) · green-proof: 0` and exits 0 | the check can go red and green on demand |
| Framing | 5 | The shell layer: extract the shell source sheet from `gen.mjs`, generate the shell stylesheet + class map | nothing — start now | glm | sonnet | `ls projects/personal/family-app/css` shows pearl-shell.css and `ls projects/personal/family-app/tools` shows pearl-shell-rename.mjs (both CREATED BY STEP 5's run); the renamer's `--check` prints `regenerated: identical` | zero unprefixed classes; declaration count ≥ source |
| Framing | 6 | Wire in (link tags, `sw.js` ASSETS + CACHE, contract-check lists), painting nothing; capture the flag-off baseline | STEP 5's generated sheet | glm | sonnet | `node projects/ops/skippy-jobs/_test-family-app-asset-version-parity.mjs` prints PASS and `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --fence-only` (CREATED BY STEP 4) prints `fence: 0 changed elements (flag off)` | stylesheet-link count = before + 2, `sw.js` CACHE = before + 1, the screen unchanged with and without the flag |
| Elements | 7 | Header + toolbar + accent sites under the skin (hero takeover, switcher pill, Add form) | STEP 6 | glm | sonnet | `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --only header,toolbar --inject <abs path to css/pearl-shopping.css>` (CREATED BY STEP 4) prints `mismatched properties: 0` for those anchors | header and toolbar anchors zero at both viewports |
| Elements | 8 | List cards and the three row kinds (link · no link · bundle), order bar, check box — restyled in place | STEP 7 | glm | sonnet | `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --only lists --inject <abs path>` prints `mismatched properties: 0` AND `--drive rows` prints `rows: link opens ✓ · nolink note ✓ · bundle expands ✓ · check-off confirm ✓` (CREATED BY STEP 4) | all three row kinds correct; every handler still fires |
| Details | 9 | Desktop composition: fixed viewport, two columns, absorbers + scroll regions + fades, one-line empty cards, all panels visible at ≥1100 | STEP 8 | glm | sonnet | `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --only layout` (CREATED BY STEP 4) prints `frame: 662 · rail: 40→622 · col1: →622 · col2: →622 · scroll regions: 2` at 1280×662 | columns end at the menu's bottom; no dead band |
| Details | 10 | Every state, verbatim: loading skeleton, failed note, empty, Add error, order replies, check-off strings | STEP 8 | glm | sonnet | `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --states` (CREATED BY STEP 4) prints `states: N/N reached · strings verbatim: N/N` where N is the reachable-state count the check READS from the ground-truth file (never typed; 10 on 2026-09-04: skeleton, failed note, empty, Add error, Staging…, Staged, Already staged, Couldn't stage, Nothing to order, the check-off failure string; the check-off confirm is the app's own dialog and is asserted by `--drive rows`) | every §2 state row S5–S8, S12–S13 captured |
| Tests | 11 | Widths and chrome conformance: 375 · 1024 · 1280; measure the chrome under Shopping and post the handoff | STEPS 9–10 | sonnet | glm | `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --band` prints `1024: rail absent · scrollWidth<=clientWidth ✓` and `--chrome` prints the destination count, badge colour and pill presence (CREATED BY STEP 4) | conformance measured; any chrome gap posted to the chrome lane's hand-off file, never fixed here |
| Output | 12 | PUBLISH behind the flag: build, parity gate, deploy, fidelity check on the live URL at both viewports, side-by-side PNG, Sienna's verdict, verifier's verdict | STEPS 1 (approved revision), 4, 7–11 | glm | sonnet (+ fable/Sienna for taste, verifier for the click-through) | `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --out <abs path under the round-10 evidence folder>/shopping-fidelity-live.txt` (CREATED BY STEP 4) → `mismatched properties: 0 · unmeasured anchors: 0` ×2 (375×812 light, 1280×662 light) | all four publish items landed; flag-off diff still 0 |
| Proof | 13 | Blind check of every §2 row on the live URL, as Nick and as Chantelle | STEP 12 | sonnet (se-blind-checker) | glm | `ls projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/evidence` shows shopping-blind-check.md (CREATED BY STEP 13's run) containing `20/20 PASS` | zero failed criteria, or the named failures back to one builder round |
| Proof | 14 | Record: STEPS verified lines, PROJECT.md status handed to Nick (governed), postmortem, retro entry | STEP 13 | sonnet | glm | `python3 projects/ops/agents/check_plan.py --progress projects/personal/family-app/PLAN-PEARL-SHOPPING.md` (this plan file, CREATED BY STEP 1's first commit) prints the derived figure and `ls projects/personal/family-app` shows STATE-PEARL-SHOPPING.md (CREATED BY STEP 1) | plan closed at the derived figure; NEXT list holds everything else |

### STEP 1 — Lock the target
**Enter this step when:** nothing. This is the first step.
**Builder:** sonnet (it edits the generator header and runs a deploy — a judgment-free but repo-wide action) · **Checker:** glm, different session
**Files you may touch:** `redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs` (header comment only), `shopping.html` (regenerated), NEW `family-pearl-shopping-r10.html` inside the Skippy design-directions folder (a byte-identical copy of the output, the deploy artefact), NEW `STATE-PEARL-SHOPPING.md`, NEW `PLAN-CHANGES-PEARL-SHOPPING.md`, this plan's §D block and STEPS. **Never** `screens/shopping.mjs` (a redline is a new revision, re-approved) or any live app file.

**Do exactly this:**
1. `git -C "/Users/nickdeck/Documents/Claude 2.0" rev-parse --show-toplevel` → record. Probe the checkout: write `evidence/.probe`, read it back, delete it, `git status --porcelain` shows nothing under `evidence/`.
2. Prepend to `gen.mjs` line 1: `// REV 10.5 — locked target for PLAN-PEARL-SHOPPING (Shopping) and PLAN-PEARL-EXTRAS (Extras), 2026-09-05` (a script file with an absolute path; never `cd` + relative).
3. `node "/Users/nickdeck/Documents/Claude 2.0/projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs" shopping` → `shopping.html`; `node …/tools/shot.mjs shopping` → renders. Grep every PROOF block of this plan for angle-bracket placeholders: `command grep -nE '<[a-z ]+>' projects/personal/family-app/PLAN-PEARL-SHOPPING.md` must print only lines this step names as allowed (none inside STEP blocks).
4. `cp` `shopping.html` → the design-directions folder as `family-pearl-shopping-r10.html`; `shasum -a 256` both, equal.
5. `node projects/ops/deploy.mjs skippy-designs`; `curl -sL -o /dev/null -w '%{http_code}' https://skippy-designs.pages.dev/family-pearl-shopping-r10.html` → 200 (corrected 2026-09-06 by the regroup: without `-L` this now returns 308 — Cloudflare Pages began redirecting the literal `.html` path to the clean URL after this step closed; the followed request returns 200 and the bytes still match); `curl -s <url> | shasum -a 256` equals the local hash.
6. Commit: `git commit -m "PEARL Shopping STEP 1: locked target REV 10.5 published" -- <the four paths>`; record the hash; `git cat-file -e <hash>` and push; write `REV 10.5 · commit <hash>` into this plan's §D LOCKED TARGET line.
7. Create `STATE-PEARL-SHOPPING.md` (current-state only: step, next step, handoffs, blocked lines) and `PLAN-CHANGES-PEARL-SHOPPING.md` (line 1 reserved for the cold reader's verdict).
8. Hand Nick one plain sentence with the address and ask nothing else; his yes lands as `Nick, <date>, "<words>"` in §D. Every step whose gate does not name "STEP 1's approved revision" runs meanwhile.

**PROOF — all must be true, pasted into STEPS verbatim:**
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step1-check.txt` · save `shopping-step1-hashes.txt`
- `command grep -c '^// REV ' projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs` prints `1`; the curl (with `-L`) prints `200`; the two sha256 lines are identical.
- `git branch -r --contains <hash>` names `origin/main`.
- The §D block carries a REV and a hash, and (when it lands) Nick's dated words about THIS page.
- What would make this step FAIL, in Nick's words: "that's not the one I approved" — a published page whose hash differs from the generator output, or a revision he has not seen.

**If it fails:** deploy refused → the published address stays empty; post one line to the state file; STEPS 2, 5 run regardless; STEP 12 cannot open until this lands (it needs the approved revision, SUCCEEDED).
**Checker's job:** re-run the curl and the hash comparison yourself; open the published page and the local render and confirm they are the same drawing.
**Handoff:** post `STEP 1 closed <date> — shell tokens source is gen.mjs REV 10.5` into `PLAN-PEARL-EXTRAS.md` STEPS.

### STEP 2 — Ground truth for Shopping
**Enter this step when:** nothing. Runs in parallel with STEP 1.
**Builder:** deepseek (mechanical extraction) in three separate briefs · **Checker:** sonnet
**Files you may touch:** NEW `redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json`. **Never** any live app file.

**Do exactly this:**
1. Brief A (ids + classes + data-attributes from `index.html`'s `#view-shopping` block): output `{ids:[…], classes:[…], data:[…]}` — the brief names the file by repo-relative path only, no folders, no `.md`, no money values.
2. Brief B (strings + endpoints from `js/app.js` lines 1995–2470 and `js/pop.js` lines 600–660): every user-facing string verbatim (the hero lines, "List's empty.", the placeholder note, the Add error, every order reply it finds (six distinct in `app.js` lines 2403–2432, one of them — "Not wired up for this list yet." — unreachable today because no panel without a vendor carries a button; Brief B records it and marks it unreachable), the two check-off strings, "no link yet", "no link", the bundle total line) and every `fetch(`/query endpoint (`/api/shopping-add`, `/api/shopping-order`, the Monday query path via `shoppingQuery`, the bundles feed if present — Brief B records what it finds, never a guessed list).
3. Brief C (the signed-in rendered DOM, read-only): through `projects/shared-tooling/browser.mjs`, sign in as Nick via the gate (password from `python3 projects/personal/family-vault/vault.py get <key> --caller=skippy` read at run time, never written), open `/#shopping`, wait for `.shop-list` to lose `.td-skel`, and write the outerHTML of `#view-shopping` (with money values masked to `$•`) plus the computed styles of the 40 anchor candidates to the JSON's `live` block.
4. Merge into one file; a `node -e` line prints the four counts.

**PROOF:** the four counts print (≥ 21 ids · ≥ 30 classes · ≥ 12 strings · ≥ 3 endpoints — derived from the reads above, the checker recounts from the sources); `command grep -c '\$[0-9]' <the json>` prints `0`; no credential string present (the checker greps for `df_ident=` and the vault key name: both `0`). FAIL, in Nick's words: "you left out the button" — a hook in the live markup missing from the file.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step2-check.txt`
**If it fails:** a brief that "ran N steps without finishing" is split again, never enlarged; dependents (STEPS 3, 7–10) need this step SUCCEEDED.
**Checker's job:** recount ids from `index.html` yourself; spot-check five strings against `app.js`.

### STEP 3 — The anchor map
**Enter this step when:** STEP 1's published page exists (approval not required yet) and STEP 2's file exists.
**Builder:** sonnet · **Checker/signer:** fable — Sienna (`creative-director`), different session
**Files you may touch:** NEW `gen.mjs-anchors-shopping.md` beside the generator. **Never** the generator.

**Do exactly this:**
1. List every distinct element the drawing draws (from `screens/shopping.mjs` + the shared chrome): eyebrow, title, sub-line, ✦, pills (phone), switcher pill, active chip, inactive chip, Add input, Qty input, Add button, toolbar pill (desktop), list card, card label, card count, row, check box, row name, × qty, ↗ glyph, "no link yet" chip, bundle head, bundle chip, bundle sub-line, order button, order status, empty one-line card (label + string), skeleton row, failed note, wash, the ground, rail + its rows (chrome lane), tab bar + cells (chrome lane). Write the count.
2. For each, one row: `design selector` (inside `.desk`/`.frame` of the published page) → `live selector` (inside `#view-shopping` or the chrome) → properties compared (default PROPS; type-only where colour is state-bound) → `GAP` with reason where no live hook exists.
3. Write `VIEWPORTS: 375×812 light · 1280×662 light` and `RETIRED: <the §D list>`.
4. Sienna signs: `SIGNED BY sienna <date> — elements drawn: N · anchors: M · gaps: K` with M ≥ N − K and K ≤ 2.

**PROOF:** ≥ 40 table rows; the signature line present; K ≤ 2. FAIL: fewer anchors than drawn elements, or a GAP without a reason.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step3-selector-counts.json`
**If it fails:** three GAPs → the drawing or the code is wrong; one line to the overseer naming which; STEP 4 needs this ANSWERED (signed).
**Checker's job (Sienna):** count the drawn elements yourself from the published page; refuse any anchor whose live selector matches more than one node.

### STEP 4 — The fidelity check
**Enter this step when:** STEP 3 is signed.
**Builder:** sonnet (test authoring is never a cheap vendor — matrix row 4) · **Checker:** glm
**Files you may touch:** NEW `tools/pearl-fidelity-shopping.mjs`. **Never** the reference script.

**Do exactly this:**
1. Copy the reference `projects/personal/skippy-app/design-directions/_pearl-fidelity-check.mjs`; change DESIGN to the published address, APP to `https://family.heroesandsidekicks.io`, the sign-in to the family gate (`POST /__gate`, fields `pw`/`identity`/`next`; password from the vault at run time), the MAP to STEP 3's rows, VIEW to the two viewports, the route to `/?skin=pearl#shopping`.
2. Add `--selftest`: inject a 1px `paddingTop` change on one anchor and assert exactly one printed row naming `paddingTop`; then run clean against the design page vs itself and assert 0.
3. Add the retired-colour sweep (every element with a non-zero box: `color`, `backgroundColor`, `borderColor` not in RETIRED) and `--fence-only` (DOM count + a computed-style hash of `#view-shopping` and of every other `.view` with the flag OFF, saved to `evidence/shopping-fence-baseline.json`; later runs compare).
4. Add `--only <group>`, `--states`, `--drive rows`, `--band`, `--chrome`, `--inject`, `--out`, `--shot` as the step map's proofs name them; an unknown flag exits 2 with `unknown flag`.
5. Preflight: a known-good control (the design page vs itself) prints 0; a failed Chrome launch exits 2, never 0.

**PROOF:** `--selftest` prints `red-proof: 1 mismatch (paddingTop) · green-proof: 0`, exit 0; `--bogus` exits 2 with `unknown flag`. FAIL: a run that prints 0 with an anchor it never measured.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step4-check.txt`
**If it fails:** Chrome lock held → wait on the lock (it force-breaks at 45 min), never a second Chrome; dependents need this SUCCEEDED.
**Checker's job:** run `--selftest` yourself; then break one anchor's live selector on purpose (a typo) and confirm the run exits 2 with that anchor named UNMEASURED.

### STEP 5 — The shell layer
**Enter this step when:** nothing (parallel with 1–4; needs only `gen.mjs` on disk).
**Builder:** glm · **Checker:** sonnet
**Files you may touch:** NEW `redesign-mockups/concepts-2026-09-04-round10-screens/pearl-shell-tokens.css`, NEW `tools/pearl-shell-rename.mjs`, NEW `tools/pearl-shell-class-map.json`, NEW `css/pearl-shell.css`. **Never** `pearl-tokens.css`, `tools/pearl-rename.mjs`, `css/pearl.css` (Finances lane).

**Do exactly this:**
1. The renamer's `--extract` reads `gen.mjs`, takes the `FRAME_CSS` template literal and the shared rules of `EXTRA_CSS` (`.row`, `.tab`, `.badge`, `.rail a`, `.rail .badge`, `.cols3`, `.cols2`, `.two`, `.four`, `.who`, `.chips`, `.g .big`, `.g .n`, `.ring`, `.g.hasring .l`; `.pillx` is EXCLUDED — dead feature; the two rules added 2026-09-05 ride on the same source line as `.rail a` and `.ring`, PLAN-CHANGES delta), and writes `pearl-shell-tokens.css` (the source sheet, generated, with a header naming the REV it came from).
2. `--build` prefixes every class `pl-`, converts `.frame X` → `@media (max-width:1099px){X}` and `.desk X` → `@media (min-width:1100px){X}`, scopes everything under `body.skin-pearl .pl-shell`, writes `css/pearl-shell.css` + the class map; `--check` rebuilds to a temp file and diffs (prints `regenerated: identical`).
3. Prove: every class in the source appears in the map; zero unprefixed class selectors in the output; declaration count ≥ source; the five literals `#F4F2EF`, `rgba(255,255,255,.62)`, `22px`, `blur(44px)`, `662px` survive.
4. Intersection proof: the `.pl-` class set of the shell sheet intersected with the `.pl-` class set of `css/pearl.css` is EMPTY except the deliberately shared `.pl-g`, `.pl-l`, `.pl-s` (listed in the map as `shared-with-token-layer`).

**PROOF:** `--check` prints `regenerated: identical`; the unprefixed grep prints 0; the intersection prints only the three listed names. FAIL: a hand edit to the generated sheet (the diff would show it).
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step5-check.txt`
**If it fails:** a class that collides with the token layer gets renamed in the SOURCE extraction rule, never in the output; dependents (6–9, Extras) need this SUCCEEDED.
**Checker's job:** run `--check` yourself; delete one rule from the output and confirm `--check` goes red.
**Handoff:** `STEP 5 closed <date> — the shell stylesheet and its class map are frozen` into `PLAN-PEARL-EXTRAS.md` STEPS.

### STEP 6 — Wire in, painting nothing
**Enter this step when:** STEP 5's sheet exists and STEP 4's `--fence-only` exists.
**Builder:** glm (route-build, one file per run) · **Checker:** sonnet
**Files you may touch:** `index.html` (two link tags + one script tag, last in their groups), `sw.js` (ASSETS + CACHE = before + 1), `contract-check.js` (only entries the coverage gates name), NEW `css/pearl-shopping.css` (header comment only), NEW `js/pearl-shopping.js` (header comment only). **Never** anything else. This is the ONLY step that edits the three shared files; re-read each immediately before writing; scoped commit.

**Do exactly this:**
1. Capture the baseline first: the check's `--fence-only` (flag off) → `evidence/shopping-fence-baseline.json`.
2. Add `<link rel="stylesheet" href="css/pearl-shell.css?v=1">`, `<link rel="stylesheet" href="css/pearl-shopping.css?v=1">` after `css/pearl-finances.css`, before `css/pearl-nav.css`; add `<script defer src="js/pearl-shopping.js?v=1">` after `js/pearl-finances.js`.
3. `sw.js`: ASSETS gains the three URLs; `const CACHE` = previous + 1 (derive; never type a number twice).
4. Run `node projects/personal/family-app/contract-check.js`; add EXACTLY the names its coverage gates print; re-run green; the checker removes them and confirms red for exactly those names.
5. `node projects/ops/skippy-jobs/_test-family-app-asset-version-parity.mjs` → PASS.

**PROOF:** stylesheet-link count = before + 2 (derived), script count = before + 1; parity PASS; contract-check green with the number quoted; `--fence-only` re-run prints `fence: 0 changed elements (flag off)` AND with `?skin=pearl` prints 0 changed (nothing composed yet). FAIL: any pixel moves with the flag off.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step6-check.txt`
**If it fails:** parity red → read what it names (usually a `?v=` mismatch) before touching anything; dependents need this SUCCEEDED.
**Checker's job:** re-run parity, contract-check and both fence runs yourself.

### STEP 7 — Header, toolbar, accent sites
**Enter this step when:** STEP 6 is closed.
**Builder:** glm · **Checker:** sonnet
**Files you may touch:** `css/pearl-shopping.css`, `js/pearl-shopping.js`, NEW `tools/pearl-accent-sites-shopping.json`. **Never** `js/pop.js`, `js/app.js`.

**Do exactly this:**
1. Composition scope `body.skin-pearl #view-shopping`: set `--acc:#B3862A;--soft:#F6EBCF;--deep:#8D6921`; add `position:relative;overflow:hidden` on `#view-shopping`; insert `.pl-wash` as its first child from `js/pearl-shopping.js` (four radial gradients per `gen.mjs`, `blur(44px)`, opacity .95, the corner tint being the accent's site).
2. Header: `js/pearl-shopping.js` observes `#pp-hero-shop` (MutationObserver, re-entrancy guard, microtask debounce) and renders the one-line `.pl-ph1` (eyebrow "Shopping · On the list" · `<h1>` count + "to get" · `.pl-s` hero line with ✦ in ink) FROM the values pop.js wrote (the count node, the line node), leaving pop.js's own nodes in the DOM (never removed — `pop.js` walks them). The neutraliser hides pop.js's LEAF nodes only — `.pp-eyebrow`, `.pp-stat`, `.pp-line` — because the switcher (`.pp-shopcmd > .shop-segmented`) and the pills (`.pp-pills`) live inside `#pp-hero-shop` too and must stay; `#pp-hero-shop-pills` is additionally hidden at ≥1100 (the drawing's `.desk .pills{display:none}`). At 1280 the toolbar pill `.pl-shtb` must be an ancestor of BOTH `.shop-segmented` and `#shopAddForm` (anchor 16's contract) — the takeover moves the form node, not the switcher, into a wrapper composed around the switcher where it stands. (Sienna's ruling (a) + Condition 3, 2026-09-05.)
3. Toolbar: the existing `.shop-segmented` restyled into the pill (chips 12px/600, active ink on white, no accent) and `#shopAddForm` restyled (white fields, hairline, radius 12px; `#shopAddBtn` = the accent's primary button, white text); on ≥1100 both sit in one `.pl-shtb` pill, the form 440px wide; on phone the pill and the form stack.
4. Write the accent-site list: `.pl-shtb #shopAddBtn`, `.shop-go`, `.pl-tab a.pl-on`, `.pl-rail a.pl-on`, `.pl-wash` (tint), `.pl-pills i.done`.

**PROOF:** `--only header,toolbar --inject <abs path to css/pearl-shopping.css>` prints 0 mismatches at both viewports; the hero line equals `pop.js`'s current line text; `command grep -c "fetch(" projects/personal/family-app/js/pearl-shopping.js` prints 0 (CREATED BY STEP 6). FAIL: a second accent hue anywhere, or the accent on a label.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step7-close-check.txt`
**If it fails:** an anchor that cannot reach zero because the drawing and the hook disagree → a GAP proposal to Sienna (STEP 3 re-sign), never a silent tolerance; dependents need this SUCCEEDED.
**Checker's job:** re-run the injected check; count accent-coloured elements against the site list.

### STEP 8 — List cards and rows
**Enter this step when:** STEP 7 is closed.
**Builder:** glm · **Checker:** sonnet
**Files you may touch:** `css/pearl-shopping.css`, `js/pearl-shopping.js`. **Never** `js/app.js`.

**Do exactly this:**
1. Each `.shop-panel` becomes a glass card (`pl-g` tokens): label = the store name from its switcher chip (read, not typed), count = its rendered `.shop-prio` count; the `.card.td-card` inner wrapper is neutralised (no double glass).
2. Rows restyled in place: `.chk` 18px box radius 6px; `.pt` 14px; `.pw` qty 12.5px tertiary; `.shop-go` ↗ 17px serif in `--deep`; `.shop-nolink-chip` hairline chip; `.shop-nolink-note` as the drawing's sub-line; bundle head chip "N links ▾", bundle body list as the drawing's numbered links; `.shop-order-bar` moves visually under the list (order: list, then bar — CSS `order`, the nodes untouched) as the hairline button; `.shop-order-status` as a `.pl-s` line.
3. The takeover observes each `.shop-list` and adds only wrapper classes/attributes needed for scroll regions; it re-runs nothing of app.js's binding (app.js binds per render — the observer only restyles after it).
4. Drive proof: `--drive rows` clicks a link row (asserts a new-tab navigation was requested to the product URL), taps a no-link row (note toggles), expands a bundle (`aria-expanded` true, list visible), taps a check box (the confirm text appears — then cancels, never confirms in a proof).

**PROOF:** `--only lists --inject …` prints 0 at both viewports; `--drive rows` prints the four ✓ lines; `--fence-only` still 0 with the flag off. FAIL: a handler that no longer fires, or a row kind not drawn.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step8-close-check.txt`
**If it fails:** one line to the overseer naming the row kind; dependents (9, 10) need this SUCCEEDED.
**Checker's job:** re-run both; then open the flag-on screen and tap each row kind yourself.

### STEP 9 — Desktop composition
**Enter this step when:** STEP 8 is closed.
**Builder:** glm · **Checker:** sonnet
**Files you may touch:** `css/pearl-shopping.css`, `js/pearl-shopping.js`.

**Do exactly this:**
1. At ≥1100: `#view-shopping` is the viewport frame (`height:100vh`, the shell's 40px inset, content box `inset:40px 40px 40px 284px`, flex column); the takeover wraps the panels into two `.pl-cols2` columns by THIS RULE, computed from the rendered rows on every app render, never from a fixed list: (a) non-empty panels are taken in DESCENDING rendered-row count, ties broken by the switcher's order, and each is placed into the column with fewer rendered rows so far (ties → the left column); within a column, panels are then displayed in the switcher's order [corrected 2026-09-06 15:05Z: the earlier wording — panels taken in the switcher's order — placed iHerb+Costco left and Amazon right on the approval-day data, the opposite of the drawing, and the STEP 9 proof recorded exactly that without anyone reading it against the drawing; the drawing is canonical]; (b) in each column the panel with the most rows is the absorber; (c) empty panels become one-line cards appended after the lists, alternating left then right; (d) the Unsorted panel joins (a) only when the app has un-hidden its chip. On the 2026-09-04 data this rule yields exactly the drawing — Amazon (absorb) + first empty list · iHerb, Costco (absorb) + second empty list — and on a day a fourth store has items it yields a fourth card inside the same frame (§2 S20). Every `.shop-panel[hidden]` other than Unsorted is shown at EVERY width by `body.skin-pearl #view-shopping .shop-panel[hidden]:not(#shop-unsorted){display:block}` (the switcher then means "where Add files", per §1a row 4); Unsorted follows a chip-state guard, never a permanent exclusion: `body.skin-pearl #view-shopping:has(.seg-btn[data-shop="unsorted"]:not([hidden])) #shop-unsorted[hidden]{display:block}`. (Sienna's rulings (b) and (c), 2026-09-05: the earlier text "below 1100 the app's own hidden/visible behaviour stands" is struck — the phone stacks every non-empty list as drawn.)
2. Absorbers: the list's `.scr` region gets `flex:1 1 auto; min-height:0; overflow-y:auto`; the bottom fade per the shell; non-absorbers size to content; nothing gets a min-height.
3. Empty lists: `.shop-panel:has(.hc-empty)` renders the one-line card (label + "List's empty." on one row); the order bar of an empty panel is hidden by the neutraliser.
4. The rail and tab bar come from the chrome lane; this step only leaves room (the 284px content inset).

**PROOF:** `--only layout` at 1280×662 prints `mismatched properties: 0 · unmeasured anchors: 0`, AND the lane's own `redesign-mockups/concepts-2026-09-04-round10-screens/evidence/shopping-step9-frameproof.mjs` prints `frame: 662 · rail: 40→622 · col1: →622 · col2: →622 · scroll regions: 2` (corrected 2026-09-06 by the regroup: the frame line is produced by that script, never by `--only layout` — the suite's source contains no such string); no `.pl-g` has innerSlack > 24px (the check's slack line). FAIL: a dead band, a bare column, or a card taller than its content.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step9-close-check.txt`
**If it fails:** rebalance the columns in CSS order, never with min-heights; dependents need this SUCCEEDED.
**Checker's job:** re-run; then resize to 1280×800 and confirm the columns still meet the rail's bottom (the absorbers grew).

### STEP 10 — Every state, verbatim
**Enter this step when:** STEP 8 is closed (parallel with 9).
**Builder:** glm · **Checker:** sonnet
**Files you may touch:** `css/pearl-shopping.css`, `js/pearl-shopping.js`.

**Do exactly this:** style, never rewrite, every reachable state string in STEP 2's ground-truth list (the check reads the list; the count is derived, never typed): the `.td-skel` skeleton (loading), `.placeholder-note` (failed), `.hc-empty` (empty), `#shopAddError` (Add failed), every reachable `.shop-order-status` reply ("Staging…", "Staged — review it in the Actions tab.", "Already staged — check the Actions tab.", "Couldn't stage that — try again in a moment.", "Nothing to order — list's empty."), the check-off failure string (the confirm is the app's own dialog — not restyled, asserted by `--drive rows`). Strings are copied from the SOURCE FILE by the check at run time, never from this plan's text (a straight apostrophe typed here is not the curly one the app renders). `--states` forces each: loading by throttling, failed by `Network.setBlockedURLs` on the Monday query, empty by selecting Pharmacy, Add error by blocking `/api/shopping-add`, order replies by blocking/allowing `/api/shopping-order` against the app's TEST queue only.

**PROOF:** `--states` prints `states: N/N reached · strings verbatim: N/N` with N read from the ground-truth file (10 on the approval-day data); empty and failed differ visibly (the check asserts different text AND a different class). FAIL: a paraphrased string.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step10-close-check.txt`
**If it fails:** a state the code cannot produce becomes a signed GAP (Sienna), never a mock.
**Checker's job:** re-run `--states`; diff the nine strings against `app.js` yourself.

### STEP 11 — Widths and chrome conformance
**Enter this step when:** STEPS 9 and 10 are closed.
**Builder:** sonnet (measurement) · **Checker:** glm
**Files you may touch:** `evidence/shopping-band.txt`, `evidence/shopping-chrome.txt` (both under the round-10 evidence folder), `STATE-PEARL-SHOPPING.md` (a handoff line). **Never** `js/pearl-nav.js`, `css/pearl-nav.css`.

**Do exactly this:** `--band` at 1024×768 (phone layout, rail absent, no horizontal scroll) and 1100×768 (rail present); `--chrome` under `#shopping` prints the destination count, each badge's background colour, the presence of any `.pl-wait` pill, and the active item's colour. Compare with Nick's rulings (seven · ink · none · Ochre). Any difference → ONE dated line in the state file's Handoffs and in `projects/personal/family-app/specs/FINANCES-PEARL-BUILD-HANDOFF-2026-09-04.md` (the chrome's owner reads it), quoting Nick: "updates is not a screen on the app" and the PEARL-SCREEN-PROMPT nav rule.

**PROOF:** `1024: rail absent · scrollWidth<=clientWidth ✓`; `1100: rail present`; the chrome line printed and either matching or handed off. FAIL: a horizontal scrollbar at 1024, or a chrome gap silently fixed here.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-step11-close-check.txt`
**If it fails:** the band is this lane's (fix in `pearl-shopping.css`); the chrome is not (handoff only). STEP 12 needs the band SUCCEEDED and the chrome ANSWERED (measured and posted).
**Checker's job:** re-run both measurements yourself.

### STEP 12 — PUBLISH behind the flag (the design fidelity gate, four items)
**Enter this step when:** STEP 1's revision is approved by Nick, STEP 4 exists, STEPS 7–11 are closed.
**Builder:** glm (build + deploy commands) · **Checker:** sonnet (re-runs the check on the live URL) · **Design QA:** fable — Sienna (side-by-side PNG) · **Verifier:** the `verifier` agent (re-runs the check and clicks through)
**Files you may touch:** under the round-10 evidence folder: `shopping-fidelity-live.txt`, `shopping-side-by-side-375.png`, `shopping-side-by-side-1280.png`, `shopping-publish.md`; STEPS. **Never** any app file (a defect goes back to its step).

**Do exactly this:**
1. `node projects/personal/family-app/build-dist.js`; `node projects/ops/skippy-jobs/_test-family-app-asset-version-parity.mjs` → PASS; `node projects/ops/deploy.mjs deck-family`; record the deployment URL and the commit hash (`git cat-file -e`, pushed); then the deploy's own wall check: `curl -s -o /dev/null -w '%{http_code}' https://family.heroesandsidekicks.io/api/finances` with no cookie prints `401` (the wall survived the deploy — `projects/ops/deploy.mjs` documents that a publish can drop the `AUTH_REQUIRED` binding; a `200` here is a FAIL that stops the step and rolls back to the previous deployment through the same tool).
2. Fidelity, live: the check with `--out <the evidence folder>/shopping-fidelity-live.txt --shot <the evidence folder>` at 375×812 light and 1280×662 light, signed in as Nick, on `https://family.heroesandsidekicks.io/?skin=pearl#shopping` with cache disabled → two count lines, both `mismatched properties: 0 · unmeasured anchors: 0`; the retired-colour sweep and the fence check (flag off, all views) print 0.
3. Side-by-side PNGs (target left, live right, same width, labelled) at both viewports, from `--shot` and the published design page.
4. Sienna grades the PNGs (taste only, now that the count is zero) → her verdict line in `shopping-publish.md`.
5. The verifier re-runs item 2 first-hand and clicks Add (then removes the test item through the app's own check-off), one row of each kind; Order via Skippy is NOT clicked (ruled 2026-09-06 15:00Z: no test queue exists and the button stages a real order card on the live action queue) — the verifier records the button present, enabled, and wired to the app's own handler → its verdict line.
6. Run the intermittent-fault rule: the check is run three times; all three must print 0.

**PROOF:** the four items in `shopping-publish.md`: the two zero-count lines with the evidence file name · the two PNG file names · Sienna's verdict · the verifier's verdict; the deploy hash reachable and pushed; the anonymous `/api/finances` curl printed `401` after the deploy. FAIL: any non-zero count, any missing item — whatever the screen looks like to anyone.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-publish-round3.md` · save `shopping-fidelity-live-r3.txt`
**If it fails:** a non-zero count names its anchor and property → back to that anchor's step for ONE builder round; the re-check is of the named rows only.
**Checker's job:** re-run the live check yourself; do not accept the builder's paste.

### STEP 13 — Blind check of every §2 row
**Enter this step when:** STEP 12's four items are landed.
**Builder:** sonnet as `se-blind-checker`, briefed with §2 only and told to REFUTE · **Checker:** glm (confirms the report cites each row)
**Files you may touch:** NEW `shopping-blind-check.md` under the round-10 evidence folder.

**Do exactly this:** for S1–S20 on the live URL, as Nick and as Chantelle (S19), drive each interaction (S20: add `pearl-check-<date>` to Walmart/Local, confirm the fourth card lands by the rule inside the 662 frame, then check it off) and record PASS/FAIL with evidence (the computed value, the string, the navigation). Order via Skippy is NOT clicked (ruled 2026-09-06 15:00Z: no test queue exists; the button stages a real order card on the live action queue) — record it present, enabled and wired to the app's own handler; Add creates then removes one item named `pearl-check-<date>`.

**PROOF:** `20/20 PASS`, or the named failures. FAIL: any row the checker could not reach (that is NOT MEASURABLE, and keeps the row open).
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `shopping-blind-check.md`
**If it fails:** failures go back to their step for one round; the re-check covers the named rows only.

### STEP 14 — Record and close
**Enter this step when:** STEP 13 prints 20/20.
**Builder:** sonnet · **Checker:** glm
**Files you may touch:** this plan (STEPS, SUMMARY, the postmortem section), `STATE-PEARL-SHOPPING.md`, `.claude/skills/plan/references/failure-registry.md` (append only, four-column format), and a proposed `PROJECT.md` status paragraph handed to Nick as one sentence (governed — never filed as a ticket while he is asleep).

**Do exactly this:** paste two independent `VERIFIED:` lines per step; write the SUMMARY in plain words; write `## Postmortem` (what the fidelity count caught that eyes passed, what a cheap run could not do, one registry entry or "nothing worth extracting"); post `STEP 14 closed <date>` into `PLAN-PEARL-EXTRAS.md` and the Finances hand-off file.

**PROOF:** `python3 projects/ops/agents/check_plan.py --progress projects/personal/family-app/PLAN-PEARL-SHOPPING.md` prints the derived figure; `--artifacts` reports nothing MISSING or EMPTY; the postmortem heading exists. FAIL: a typed percentage.
**ARTIFACTS ON DISK** (cited 2026-09-06 so `check_plan.py --progress` can count them; the evidence folder is `redesign-mockups/concepts-2026-09-04-round10-screens/evidence`, passed as the tool's second argument): save `check-plan-progress-2026-09-06.txt`

## Postmortem — Shopping, written 2026-09-06 at STEP 14 (the running narrative with dates and failures lives in STATE-PEARL-SHOPPING.md's own postmortem, extended today; this is the plan's required summary)

**What the fidelity count caught that eyes passed:** the hidden-view render (anchor #55: a display rule on `#view-shopping` outranked the app's own `[hidden]`, so the Shopping content box sat a full viewport low and the To-Do view painted over other screens), the mid-fade opacity reads on #17/#18/#37, and the chrome accent leaking Finances teal onto the Shopping route. Three builder rounds had passed each by eye.

**What eyes caught that the count passed:** sibling ORDER — the toolbar's switcher-then-Add order at 1280, the two desktop columns transposed against the drawing, and the phone stack's empty card sitting between two populated lists. The anchor map measures computed properties per element and never the order of siblings, so all three read `0 · 0` three times each. Sienna found the first two on the side-by-side and the third on a full-length phone capture the first capture had cut off at the fold.

**What a cheap run could not do:** the cheap-lane typist refused the fidelity suite (a "hard-floor" label) and any file over about 90 KB (`sw.js`, three refusals in round 3), so those edits were applied on Anthropic with the identical byte-exact proof each time, recorded; and every checker verdict that mattered came from a Sonnet verifier or the creative director, never from the builder's own run.

**Registry entry:** one, appended today to `.claude/skills/plan/references/failure-registry.md` — a property-count gate cannot see sibling order; every §D anchor map carries order anchors for each container whose children the drawing orders.

## 4 · Regret Check (every registry entry, or the plan is not done)

*One row per registry entry, in registry order (PLANNING → DECOMPOSITION → EXECUTION → INTEGRATION → QA → REPORTING → the retro blocks). "Steps" are this plan's §3b steps.*

| Failure mode (registry entry) | The measure in THIS plan that prevents it | Where it lives |
|---|---|---|
| A second system was built because the first was invisible | Ownership receipt cites the family app's own governing plan, PROJECT.md and the Finances Pearl spec; this plan extends that estate (shared Pearl layer reused, never rebuilt) | §0, §1 anti-scope |
| A capability was declared impossible from a stale or unverified claim | Every 'cannot' in this plan (no screenshot capture, transitions freeze) is re-measured by STEP 4's instrument preflight before it is relied on | STEP 4 |
| An absence was asserted without opening the store that would hold it | §Z binds every negative: STEP 2's ground truth searches index.html, app.js/extras.js and pop.js with `command grep` and a second pattern before any hook is called absent | STEP 2 |
| A known constraint's reason was lost, and it silently capped the product | Each frozen constraint names its reason inline (why prefixed classes, why 1100px, why the old markup stays) | §3 contracts |
| An instruction assumed capacity the executor doesn't have | Steps are sized to one cheap run each; STEP 2 is split into three briefs because the Finances lane measured one big brief 'ran 24 steps without finishing' | §3b, STEP 2 |
| Expectations/manifest rows carried no grounding | Every §2 row and every anchor cites its source hook (id/class/string in the live files) — no expectation without a citation | §2, STEP 3 |
| Work was written to a queue no reader ever visits | Every artefact names its reader: evidence → the checker and the publish step; handoff lines → the chrome lane's hand-off file | §5 artefact consumers |
| A detector's death was invisible because only its target read it | The fidelity check is run by the builder AND re-run first-hand by a different-session checker; its exit code is read by both | STEP 4, STEP 12 |
| A decision settled once re-opened elsewhere, or two copies of a rule disagreed | One implementation of the rule: the accent-site list and the anchor map are single files; the nav is consumed from the chrome lane, never re-implemented | §3 contracts |
| A rule constraining the user turned out to be an agent's invention | Every rule in this plan carries Nick's quoted, dated words or a named author; no unattributed rule | §1a, §D block |
| Remediation was ordered with diagnosis last | STEP 6's first action is the is-it-already-fine check (flag off → screen byte-identical) before any composition is written | STEP 6 |
| A document, label, or comment was believed over the live system | The live app is read by running it (STEP 2 pulls the rendered DOM signed in), never from comments or the Field spec | STEP 2, STEP 4 |
| A proposal was sold on a capability never opened and read | Every capability this plan leans on (MutationObserver takeover, :has(), body.skin-pearl switch) is opened in the live file and cited by line | §3 contracts, STEP 7 |
| A cause was named and acted on without eliminating alternatives | A failing fidelity count is diagnosed per anchor with the check's own per-property line, never by one guessed cause | STEP 12 |
| The human was asked a question the record already answers | The standing-auth audit was read: sign-in, driving the app, and testing as Nick are granted; no step asks him for those | §0, §AUTH receipt |
| A spec and its guard were authored by the same hand and ratified the same defect | The anchor map is written by the builder and SIGNED by Sienna's design QA (different agent) with the distinct-element count beside the anchor count | STEP 3 |
| Session rules never reached the subagents doing the work | Every dispatch header pastes the MACHINE RULES substance and the file fence verbatim; inheritance is assumed to be zero | §5 dispatch header |
| One rule was blanket-applied across items needing per-item answers | Per-panel and per-state rows in §2 are answered one at a time; the checker forces each state separately | §2, STEP 10 |
| Pattern-matching scoped too loosely produced false connections | Anchors map design selector → live selector one-to-one; a selector matching more than one live element is a GAP, never a loose match | STEP 3 |
| Rules existed but were psychologically dormant at answer-time | STEP 0's five-minute loop re-fires the North Star, fan-out, cheap and blocked questions so the rules are re-loaded, not remembered | STEP 0 |
| A run exceeded its cost/time ceiling or hung unbounded | Every cheap run carries the lane's 120s timeout and 60k read cap; a run that exceeds it is split, not retried bigger | §5, STEP 2 note |
| A helper was dispatched on a brief with a wrong or missing constraint | Every brief names its file fence, its proof command and what must NOT change; the header is copied verbatim from §T | §3b, §5 |
| A claim about the user/system was made without its source | Every claim about the live app cites file:line or the STEP 2 ground-truth entry | §2, STEP 2 |
| A conclusion was drawn from a partial read | STEP 2 reads the whole Shopping/Extras code path end-to-end (loader, renderer, handlers, hero) and records the read as complete or partial | STEP 2 |
| A fact was quoted as current without its date | Every live value quoted (counts, strings, versions) carries its pull date (2026-09-04 18:50Z) and the command that re-measures it | Already true, §2 |
| A computed value never reached the persistent record | Every count the plan produces (anchors, mismatches, links, cache version) is written to an evidence file under the repo, never left in a session | STEP 12 evidence paths |
| A missing lookup key fell back silently to a wrong default | Selectors that match nothing fail the check as UNMEASURED (exit 2), never silently pass; :has() fallback is stated | STEP 4 |
| A hardcoded identifier broke when the referent was recreated | Anchors bind to the app's own frozen hooks (RESKIN-CONTRACT ids) rather than generated ids; contract-check.js proves they still resolve | STEP 6, STEP 3 |
| A placeholder or wrong-level path shipped as a literal instruction | Every path in this plan is repo-relative and was globbed on disk at write time; no placeholder path survives (STEP 1 greps for `<`+`>` placeholders) | §0, STEP 1 |
| A UI reported success while the backend silently failed | Add/order/check-off actions are verified by reading the backend result (the refetched list, the queue card) after the click, not the button state | STEP 10 |
| Mid-session state was assumed unchanged | STEP 12 re-runs the whole acceptance set after STEP 11's dead-CSS removal; every re-check is a fresh run, not a remembered pass | STEP 11, STEP 12 |
| Uncertainty was silently absorbed instead of marked | Every evidence line declares its state (ARTIFACT SAVED · NOT MEASURABLE FROM HERE — instrument · UNPROVEN); nothing is left implicit | §A evidence states, STEPS |
| A serial multi-step operation blew its time budget | Steps are single-purpose; the publish step runs the check once per viewport with each count written before the next | STEP 12 |
| An external action went unlogged and became unrecoverable | Every deploy is logged by deploy.mjs's own record and the commit hash is pasted into STEPS | STEP 12 |
| A tool's own description contradicted house reality and won | Where a tool's own text contradicts house reality (the hook's 'cd + relative path' refusal, Chrome's screenshot hang) the plan names the house fact and the workaround | §0 notes, STEP 4 |
| Personal/identifying data exposed, or a record written to the wrong subject | No money value, credential or login appears in any brief, ground truth or evidence file; the cheap lane's data wall is stated in every brief | STEP 2, §5 |
| One instance of a defect class was fixed while its siblings stayed broken | A defect found on one row kind (bundle / no-link / plain) is fixed for all three in the same step; the checker forces all three | STEP 8 |
| A read operation mutated state | The fidelity check and the ground-truth pull are read-only against the live app (GET + signed-in read; no POST beyond the identity gate) | STEP 2, STEP 4 |
| The three biggest absence-claims variants: empty result, broken probe, discarded stderr | Every 'not found' is paired with the exact command and a second, differently-shaped search; stderr is captured to the evidence file | STEP 2, §Z |
| A generated mirror was hand-edited, or its generator never re-ran | css/pearl-shell.css is GENERATED by tools/pearl-shell-rename.mjs from one source sheet; a hand edit fails STEP 5's regenerate-and-diff proof | STEP 5 |
| Deployed config silently diverged from source config | The deployed asset list is proven equal to source by the asset-parity gate before every publish | STEP 6, STEP 12 |
| A delivery path was reordered and its notification behavior changed | N/A: no delivery path or notification is reordered by this build | — |
| A critical boundary was config-editable and could be silently widened | The skin switch is read once from the URL at load, never from stored config; the flip to default is out of scope | §3 contracts, anti-scope |
| A "growing" archive had actually frozen | N/A: no archive is written by this build | — |
| Files were archived but their citations kept pointing at them | The round-10 renders this plan cites live in the repo folder named; no citation points into the wiped scratchpad | §0, Already true |
| A pipeline broke silently and looked identical to a working one | The fidelity check exits non-zero on any mismatch or unmeasured anchor and its exit code is asserted in the proof (a green run with no rows is impossible by construction) | STEP 4, STEP 12 |
| Output was delivered somewhere the intended reader never looks | Evidence files land under the round-10 folder's evidence/ path and their names are pasted in STEPS where the checker looks | STEP 12, STEPS |
| Concurrent sessions clobbered each other's work in a shared file | File fences give this lane its own new files; the only shared files (index.html, sw.js, contract-check.js) are edited in ONE step with re-read-before-write and a scoped commit | §3, STEP 6, §W |
| An enforcement gate covered fewer paths than its rule, or failed open | The retired-colour sweep and the plain-app fence check run over EVERY rendered element with a non-zero box, not a listed subset | STEP 4 |
| Identity or authority was read from a value the caller supplies | Identity for the check comes from the server-set df_ident cookie obtained through the real gate, never from a value the script supplies | STEP 4 |
| A new failure state was detected but reached no human | A red fidelity run or a red parity gate is a FINISH-LINE failure written into STEPS and the state file; the loop's BLOCKED question surfaces it within five minutes | STEP 0, STEP 12 |
| The builder graded its own work and passed it | Builder and checker are different models in different sessions on every step; the publish step's four verdicts come from four different agents | §3b, STEP 12 |
| A check existed that could not fail | STEP 4 proves the check can go red (a deliberate 1px padding mismatch injected with --inject prints exactly one row) before it is trusted | STEP 4 |
| The review didn't cover the shipped artifact | The checker re-runs against the PUBLISHED deployment URL, never the working tree | STEP 12 |
| A narrowing/refactoring change broke the cases that were already correct | STEP 11's dead-CSS removal re-runs every acceptance check from STEPS 6–10 and must still pass | STEP 11 |
| A check's verdict depended on wall-clock, machine load, or a concurrent writer | The fidelity check pins its viewport, theme and a settled DOM (waits for the live loader's own end state) so its verdict does not move with load or clock | STEP 4 |
| A test existed but nothing ran it | Every proof command is wired into the STEPS section and re-run by the checker; contract-check.js runs at the end of every step | §3b standing rules |
| An interactive element or view shipped untested / unseen | Every §2 interaction is driven on the real surface by the blind checker at STEP 13 (click, type, expand, check-off) | STEP 13 |
| Coverage was reported optimistically | Coverage = verified ÷ the §2 row count pinned at plan time; the number reported is `--progress`'s derived figure | VERIFICATION, §2 |
| A staleness/freshness check used the wrong proxy | Freshness of the live pull is checked by re-pulling at STEP 2, not by the file's date | STEP 2 |
| A quantitative claim shipped without its method | Every count in the plan states its method (the check's rows, `command grep -c`, the DOM count script) | STEPS proofs |
| Done was declared before the live surface was checked | DONE requires the live published URL measured at every viewport, signed in, after deploy — never the working tree | STEP 12 |
| A biometric/metric overrode the human's stated reality | N/A: no biometric or human-state data is rendered by these screens | — |
| A correlation was asserted as a cause | N/A: no causal claim is made; the fidelity count is a measurement | — |
| A nuanced reality was collapsed into a clean binary | Row kinds (link / no-link / bundle), states (loading / failed / empty / populated) and the 900–1099 band are each their own §2 row, never collapsed | §2 |
| A recommendation repeated something already tried, uncited | N/A: no recommendation about Nick's body or money is made | — |
| A wrong record was disclaimed instead of corrected | A wrong evidence line is corrected in place with git history keeping the old text; never disclaimed | §A |
| Open items were re-typed from memory and drifted | Open items live only in STEPS and the state file, rewritten in place; nothing is re-typed from memory | STATE FILE, STEPS |
| A deliverable was referenced instead of delivered | Every deliverable (sheet, script, evidence, report) is a named file path on disk, pasted into STEPS | STEPS |
| A report used names/shorthand only the writer understood | SUMMARY and every message to Nick use plain words: what he sees, at which address; no codenames | SUMMARY |
| Commands were sent to a surface that can't run them | Commands meant for Chrome run through the shared rig; commands meant for the cheap lane are single-file briefs; nothing is sent to a surface that cannot run it | §5 |
| A number was published without the population it was counted over | Every number carries its population (anchors of N, rows of M, viewports × themes) | STEP 12 proof |
| A finding existed only in the session's output and died with it | Every finding is written to the evidence folder or the state file in the same turn | §5 artefact consumers |
| The plan named a target with total precision, and the target was wrong | The locked target is Nick's approved page at a named revision; the plan targets THAT file, and STEP 1 proves the published copy is byte-identical (sha256) | §D, STEP 1 |
| The human approved a summary, and the summary was silent on the deciding variable | The confirmation sheet carries the deciding variables (desktop shows all lists; Meditation hidden under Pearl) in Nick's words, not a summary | §1a |
| A project stated its scope and never its anti-scope, and lanes leaked into adjacent work | NOT in scope lists the flip, the chrome, Health, security and the app's data feeds, each with its reason | §1 anti-scope |
| A new rule was written as prose inside its own fix, with nothing enforcing it | The rules that matter here are enforced by scripts: the fidelity check, contract-check.js, the parity gate, check_plan.py | STEP 4, STEP 6 |
| A confirmation was satisfied by checking the wrong kind of fact | V1 rows are confirmed by Nick's words about THIS screen; V2 rows by opening the live file, dated | §1a |
| A blocker common to every lane was carved out of all of them and given to nobody | The chrome (nav) is common to every Pearl screen and has a named owner lane; this plan consumes it and posts handoffs, never leaves it to nobody | §3, STEP 11 |
| Lanes were built to stop: one pass, land, idle — while fixed ceremony ate the context | Steps name their next unblocked step on failure; the loop keeps the queue full; no lane is built to idle | STEP 0, If it fails |
| A caveat nobody measured travelled as fact through multiple independent lanes | Inherited caveats (the eight measured-broken rig capabilities) are re-measured by STEP 4's preflight before they gate anything | STEP 4 |
| The environment destroyed work silently, and the lane wrote a wrong lesson from it | Scoped commits, no stash, re-read before write, step-numbered snapshots — so a destroyed edit is visible in the diff, never mislearned | §W, §3b standing rules |
| A specification described ONE lifecycle in several places, and the copies drifted independently — four consecutive cold reviews each found ~5-8 blocking ambiguities, because every patch added another partial description of the same state machine | The state lifecycle (skin off → on → published) is written ONCE in §3 contracts and referenced, never restated | §3 |
| A task brief on an existing project was treated as the plan, and a generated status checklist was treated as the task list | This PLAN file is the plan; the STEPS section is generated state; no brief replaces either | header, STEPS |
| A regression test's "red-proof" failed for a reason unrelated to the thing it claimed to prove, twice in one session, two different mechanisms | STEP 4's red-proof injects a mismatch on the very property it claims to catch, and the row printed names that property | STEP 4 |
| A standing instruction to route work to an outside/cheap engine eroded over a long session into doing the work directly | The loop's CHEAP question re-routes building to glm every five minutes; only checking and design QA stay on Sonnet/Fable | STEP 0, §5 |
| A plan's own second line named a different document as the authority, and the reader proceeded without opening it | The plan's authority line points at THIS file and the design system; the Finances spec is cited for mechanics only | header, §0 |
| A live bug got three consecutive confident wrong-or-unproven diagnoses, two claiming live verification | A live defect gets one diagnosis by running the check, whose per-anchor rows are the evidence; no confident narrative | STEP 12 |
| Fourteen guards stayed green all day while the live screen showed the wrong thing | Green gates are not the finish: the fidelity count on the live URL and the blind checker's click-through are | FINISH LINE |
| An agent was accused of fabricating its report because a narrow search failed to find the file it cited | A cited file is looked up two ways (path and suffix glob) before anyone is called wrong | §Z |
| A tool's failure verdict was believed without checking the disk — and separately, a success verdict shipped a syntax error | A tool verdict (parity gate, deploy.mjs) is confirmed on disk and on the live URL, never believed alone | STEP 12 |
| A build with several independently-shippable pieces was planned and run as one monolithic project, too large for one agent to hold | This screen is its own subproject with its own plan; Extras and Shopping never share a step | §1b |
| A rule written only in prose, with no template slot and no machine gate, behaved as if it didn't exist | The rules here have slots: the §D block, the STEPS proof lines, the STATE file; check_plan.py gates the shape | §0, §D |
| A row-quality check counted TOTAL filled cells instead of checking the specific columns it claimed to require | Proof columns name the specific property compared, never a filled-cell count | STEP 4 PROPS |
| Three independent readers reported wildly different "% complete" for the exact same objective state — twice, on two different subprojects | Progress is the derived figure from `--progress`; no session types a percentage | VERIFICATION |
| A V2 "opened it, here's what I saw" confirmation was wrong three separate times because it opened the WRONG PATH — the plan's own stated location, never independently rediscovered | V2 rows open the exact live file at the cited line; STEP 2 records path + line + what was seen | §1a, STEP 2 |
| A shared coordination file used by several subprojects at once had no per-subproject write fence, and one subproject's list silently filled with rows belonging to the others | Shared files (index.html, sw.js, contract-check.js) have a single write step and a same-hour fence stated in §3 | §3 |
| The single cheapest, most decisive test of a build's core hypothesis was defined at planning time (correctly) but not RUN until after most of the build effort was already spent | The cheapest decisive test — flag on with no composition changes nothing (STEP 6) — runs before any composition | STEP 6 |
| A dispatched build agent reported an interim status ("build is in progress, will resume once a Monitor delivers the completion notification") as its FINAL answer and returned, instead of waiting for the real result | A builder never reports 'in progress, will resume'; a step ends with a proof or a BLOCKED line naming three tries | §BLOCKED |
| A sandbox restriction produced the EXACT error text this same repo's own CLAUDE.md already documents as a sign of a genuinely broken machine ("chrome exited early, code null" / Chrome preflight failure), and it was initially read as that known problem rather than investigated as a new one | The routing hook's known refusals (cd + relative path, $VAR write targets) are named with the fix (absolute-path script files) | §0 notes |
| A paid external tool (Codex CLI) ran out of its own usage quota mid-build, and the agent that hit the limit chose to switch to running the command directly via its own Bash tool instead of the mandated Codex path — correctly, but this is a real, recurring risk that needs a standing rule, not a one-off judgment call | Cheap-vendor quota or refusal is handled by the loop's CHEAP question and the lane's fallback list, never by silently doing the work on Fable | STEP 0, §5 |
| A card-creation script reported success ("card opened... read back and confirmed") and its own internal counter incremented, but the card did not actually exist on live re-query — twice, for two different cards, requiring full manual re-creation | STEP 12's success is read back from the live URL by the checker, never from the builder's own counter | STEP 12 |
| Three separate, independently-fatal wiring gaps each made the same feature (the ai-builds board) non-functional in a different way, and NONE of them were caught by a passing `build-dist.js` run, any TIER-1 or TIER-2 gate, or any API-level check | Every wiring gap (link tag, sw.js asset, contract list) is closed in one step with one proof that lists all three | STEP 6 |
| A real, deployed code fix (the three fixes directly above) did not reach a real user's already-open browser tab, even after that user hard-refreshed multiple times | The cache version is bumped with every css/js change and the parity gate proves it; the checker loads the URL with cache disabled | STEP 6, STEP 12 |
| A correct, intentional, previously-ruled-on design decision (the task screen's default view narrows to "my own tasks" even for leadership identities) was mistaken for a bug because it was checked from only ONE identity's login | Previously-ruled decisions (Pearl approved, seven destinations, no pill, Meditation and Pets gone) are quoted with dates in §1a so nobody re-litigates them | §1a |
| The Updates panel — the actual surface a person opens to read what an agent posted about a card — is wired to Monday.com sync data ONLY, and an app-native card (this entire board) has no Monday board behind it, so it will read "No Monday updates on record for this item" FOREVER, regardless of how many real, correctly-formatted updates were posted server-side | The rendered surface (published URL, signed in) is what the checker reads; a card or evidence file is never taken for the screen | STEP 12, STEP 13 |
| A pure oversight/QA dispatch (re-run four questions, grade the answers, write nothing) was refused twice in a row by the WORK-TYPE gate as "unclear," burning two full agent-spawn round-trips before the actual task began | Checker dispatches carry ROLE: VERIFIER with no write instruction; the builder edits the plan | §5 dispatch header |
| The same brief, past the work-type gate, was then refused by a SEPARATE gate for missing the ~6,000-word MACHINE-RULES travel block — a requirement with no automatic injection and no template a brief author can copy from without hitting the refusal first | Every dispatch header carries the literal MACHINE RULES substance, both ROLE and NICK-ASKED where needed | §5 |
| A fix (new SYSTEM-prompt grounding rules) was drafted, partially applied to disk, and left in a syntactically-valid but COMPLETELY UNVERIFIED state when the tool writing it (Codex CLI) hit its own account-wide usage cap mid-task | A partially-applied edit is caught by `node --check` on every touched .js and the regenerate-and-diff on generated CSS at the end of every step | §3b standing rules |
| The above fix's failure was found ONLY because a second, genuinely fresh-context pass re-ran the real test live — the first pass's own self-check (syntax valid, code present) had already been satisfied and would have been reported "done" without it | The blind checker (STEP 13) is a fresh session that re-runs the live test; the builder's pass is never the final verdict | STEP 13 |
| A confirmed, applied data fix was verified as working because it had only been applied to ONE of two live copies of the same data (production) — the copy actually being tested against (staging) still held the old, wrong text | Two live copies (flag on / flag off; source / dist) are both checked: flag off unchanged, dist equal to source | STEP 6, STEP 12 |
| A 16-question regression suite meant to catch exactly this bug class had been silently crashing on question 1 and reporting nothing useful for a full day, because a dependency it called gained a new required argument and nobody re-ran the suite after that change landed | The fidelity script's own preflight (exit 2 on unmeasured) means a crashing check cannot read as a pass | STEP 4 |
| Two entire bodies of real, load-bearing work — a 34-file answer pipeline and this drive's own PLAN.md/STATE.md tracking pair — had never been committed to git, on any machine, the whole time they were being built, found only by accident while fixing something else | Deleting a plan step is blocked at write time (§E); the state file is rewritten in place and committed with a pathspec | §E, STATE FILE |
| A request to deepen an existing artifact was answered by re-polishing the context already in hand, while named, existing sources were never opened | 'Deepen' means gather: STEP 2 gathers from the live files, not from this plan's own text | STEP 2 |
| A gate protecting one specific, highly sensitive file covered some tool surfaces (Write/Edit/MultiEdit) but not others (Bash), and the gap sat honestly documented in the file's own header for a day before being closed | The data wall is enforced in every brief AND by the cheap lane's egress scan; the plan names both | STEP 2, §5 |
| A function parameter's DEFAULT value silently made an entire decision branch unreachable, under a fully green test suite, since the day the branch was written | No default flag hides a branch: the skin switch is explicit and its off-state is proven byte-identical | STEP 6 |
| A write-then-rename ("atomic write") pattern was used to update one row in a file that has a SECOND, independent writer appending new rows — the pattern is genuinely atomic against a torn read, and genuinely loses any row the other writer appended during the read-modify-write window | Generated files are written whole by their generator; hand edits fail the diff | STEP 5 |
| A test suite's own "red-proof" claimed a safety property held ("removing the fix would fail the test") without ever actually removing the fix and running the suite | STEP 4's red-proof removes the fix (a real injected mismatch) and shows the row; a 'would fail' claim is not accepted | STEP 4 |
| Test files that exercised a shared module's logging path wrote real output into the REAL production log file, even though every other piece of test state (queue, tickets, journal) was correctly scoped to scratch directories | Evidence files are written under the round-10 evidence/ folder, never into the app's served folders or logs | §5 artefact consumers |
| An identity verified once, in memory, from a live authenticated source, was designed to be re-derived later from a file any process could write — which would have made the file, not the live authentication, the actual source of trust | Identity comes from the live gate on every run, never a cached value | STEP 4 |
| A background daemon process registered a global crash-and-exit handler for unhandled promise rejections; a later feature fired a promise without a `.catch()` in that same process, meaning any transient failure in that one feature (a network timeout) would have crashed the ENTIRE daemon, including everything unrelated it was doing | N/A: no daemon or long-lived process is built | — |
| A build's supersession of one design ("a standalone daemon" → "extend the existing listener") correctly re-scoped every task around the new mechanism's natural shape, and in doing so quietly dropped a piece of functionality that had no obvious home in the new shape | Re-scoping (Meditation/Pets removed from the design set) is written into §1a with Nick's words and the code left untouched | §1a |
| `fs.watch()` on a shared state directory was assumed to be a sufficient delivery trigger, and was not — under real concurrent load from ~235 other sessions writing to sibling files in the same directory, two real queued requests sat with zero fs.watch event ever firing | N/A: no file watcher is relied on | — |
| A plan asserted facts about the repo it never checked — one step named a symbol that travels under a different name; another's file fence named a file that does not exist (merges log items A3, A4, D2) | Every symbol the plan names (ids, classes, functions, cache constant) was found by STEP 2's grep at the cited line | STEP 2 |
| The program fixed what was BROKEN instead of building what was ASKED FOR — a day's good work landed on a component its own plan retires (log item J1) | The North Star is the screen Nick asked for at the address he opens; steps that stop serving it are corrected in place | NORTH STAR, §N |
| A plan passed every gate — well-formed steps, real proofs — and still could not deliver what the user asked for (log item J2) | The FINISH LINE names the user-visible outcome (Pearl Shopping at the URL, zero mismatches) not gate passage | FINISH LINE |
| An assistant's first-person account of its own failure was taken as the root cause by every reader, and it was false (log item J3) | An agent's account of its own failure is re-tested by the checker before it becomes a cause | §A inherited claims |
| Three verifications were real and all three had the wrong SCOPE: verifying a quote is not verifying the claim; verifying a file once is not verifying it now; verifying the code path is not verifying the thing (merges H1, H2, H3 — one defect, three extents) | Each verification names its SCOPE: the count (fidelity), the click-through (blind checker), the taste (Sienna) — three different claims | STEP 12, STEP 13 |
| An orchestrator's confident relay propagated a wrong conclusion to five sessions faster than any plan could — a real acceptance criterion was deleted on it — and the builder that refused the relay with evidence was right (merges F1, J4) | Relayed conclusions (a peer's 'the chrome is done') are re-measured on the live URL by STEP 11 | STEP 11 |
| One writer in three read the same handoff as a gate and serialized nine of fourteen steps behind another chunk's tenth step (log item F3) | Entry gates name the specific artefact needed; no step waits on 'the previous step' | §3b Gate to enter |
| Every failure mode of the file-approval machinery was silent: an approved-once path became permanently un-requestable; a legitimate handoff into a shared governed file consumed another chunk's pending approval; approval never notified the requester; one approval unlocked exactly one edit operation, losing a two-part edit's second half; and a plan tracker named STATE.md missed the PLAN-shaped free-edit carve-out, costing ~10 approval taps in one evening (merges B1, B2, B3, B4, I2) | N/A: no approval machinery is built; governed .md writes are recorded ungoverned and handed to Nick awake | §0 note |
| A governance CLI silently dropped unrecognized flags (exit 0), let a two-token flag value overwrite the file path, let --reason swallow the next flag as its value, and its own written spec documented the broken form in two copies (merges C1, C2, C3, C4) | N/A: no CLI flags are parsed by this build beyond the fidelity check's, which fails loudly on an unknown flag (STEP 4 proves it) | STEP 4 |
| Plan shape existed as convention, not enforcement: plans degenerated into 1,000-line session logs; the plan template itself failed the machine gate; the checker validates a plan's parts, never its shape (merges A1, A2, D1) | Plan shape is machine-gated by check_plan.py; STEPS is the only state section | §0 |
| A punchlist item condensed to six words pointed its reader at exactly the wrong action — implementing it literally would have silently rerouted every assistant reply into manual approval (log item I3) | Every STEPS line carries its DEFINITION OF DONE and PROOF verbatim from the STEP block | STEPS |
| A production secret read as SET when its value was EMPTY, and every check agreed with the wrong answer for 90 minutes across three sessions | N/A: no secret is read by this build; the gate password is supplied to the rig by the vault at run time and never written | STEP 4 |
| The SAME claim, on the SAME evidence, was CONFIRMED by a checker asked to verify it and REFUTED by a checker asked to break it — and the refuting one was right | The same claim is checked by a checker asked to REFUTE it (the blind checker's brief says so) | STEP 13 |
| Reasoning ABOUT a system instead of ASKING it — the single most repeated failure of the 2026-08-27/28 night, four times across three different sessions, every time producing a confident and wrong claim from real evidence | Every question about the live app is answered by running it (STEP 2's signed-in DOM pull), never by reasoning about it | STEP 2 |
| A hard prerequisite discovered AFTER a decision, with no owner assigned, silently converts a made decision into an unimplementable one | A prerequisite found mid-drive (a missing hook, a chrome gap) gets an owner line in the state file the same turn | STATE FILE, trip-over |
| A relayed instruction is acted on, or held, by whether the RELAY ITSELF could be the attack — and sessions had no test for that, so they either obeyed every relay or refused every relay | Nick's words are quoted verbatim with dates; a relayed instruction is checked against his words in §1a before it moves a step | §1a |
| Two independent programs audited themselves on the same night and found the same disease — every instrument reported a state that was not the system's state — while both had been reading the reports as ground truth | Instruments are preflighted (STEP 4) against a known-good control page before any verdict | STEP 4 |
| A PROOF block read as complete while still containing its own template placeholders — four times in one plan, and the shape is mechanically detectable | STEP 1 greps every PROOF block for template placeholders and fails on any | STEP 1 |
| Real evidence, deliberately destroyed for a good reason, is indistinguishable from evidence that never existed | Evidence is never deleted; superseded runs stay under evidence/ with their date | §5 |
| A capability was ruled impossible on the strength of a query that structurally could not see the answer — the same shape as an earlier logged incident, on a different tool, and it was not recognised | The fidelity check queries the DOM directly; a selector that cannot see the element is reported UNMEASURED, never as absent | STEP 4 |
| The instruments used to verify a UI lie in four distinct ways, and a "drive the real surface" standard that does not name them produces confident false results | Four instruments cross-check the UI: computed styles, DOM counts, the blind click-through, and the side-by-side PNG | STEP 12 |
| A step's entry gate was satisfied and the step still could not run, and the format had nowhere to say so | A step whose gate is met but which cannot run writes `STEP N BLOCKED — tried a,b,c` into STEPS; the format has the slot | If you get stuck |
| An automated proof's own internal check detected failure and the surrounding pipeline logged success anyway — the checking logic and the reporting logic disagreed, and reporting won | The proof asserts the check's exit code AND the printed counts; a pipeline logging success over a failed check is impossible | STEP 12 |
| A dispatch gate blocked the exact defensive pattern its own preceding line prescribed, for the exact reason that pattern exists | The dispatch header is copied verbatim from §T so the gate's own prescribed pattern is what is sent | §5 |
| A fallback held in place to make a cutover safe was itself the reason the cutover could never succeed — every retry failed, and each failure made the fallback look more necessary | The Field markup stays reachable with the flag off but is never a fallback for a Pearl failure; a red count blocks the publish | STEP 12 |
| An approved instruction was correct when it was approved and harmful by the time it could be delivered — and every existing rule for handling relayed instructions asked only whether it was AUTHENTIC, never whether it was still TRUE | Nick's approval of the target is dated and the revision named; a later redline re-locks a new revision rather than acting on the old one | §D |
| "I fixed the file" · "I deployed it" · "that is what the user sees" are THREE different claims, and a chunk can be right about the first two and wrong about the third — the gap is a client cache that no repo read, no deploy log and no server-side fetch can see | Fixed / deployed / seen are three proofs: node --check + diff, deploy.mjs record, live URL measured signed in | STEP 12 |
| In a multi-session build, code read from the working tree is not the state of the system — it may be another session's half-finished fix, and reading it as established behaviour produces a confident diagnosis of a bug that does not exist | The working tree is never the source of truth for the live app; every measurement targets the deployed URL | STEP 12 |
| Three successive rounds of fixes each produced an honest, passing proof, and the user's original complaint was untouched by all three — because every proof measured the mechanism the fixer had chosen to fix, never the sentence the user actually said | The original complaint (negative space, one viewport) is a FINISH LINE item measured on the live screen, not a passing proof | FINISH LINE |
| A correct local caution was escalated into a fleet-wide halt across eight sessions on a crisis that did not exist — and the escalation priced only one side of the decision | A local caution costs one line in NEXT; no halt propagates beyond this lane | §S, §BLOCKED |
| An overseer reported two pieces of work as missing because no message about them had reached its inbox — both had landed, were logged with dates and real terms, and one had already passed a full triad | Missing work is confirmed on disk and in git log before it is reported missing | §Z |
| An acknowledgement from the system under test was read as evidence of the outcome — the same word, `queued`, covered a genuine pass and a silent 40-minute failure on the same endpoint the same night | `queued`/`Staged` acknowledgements are read as acknowledgements; the outcome is read from the queue card or refetched list | STEP 10 |
| An overseer authorized an action by bridging a DIFFERENT ruling of the user's onto the question — reasoning correctly from a real quote that was about something else, three relay hops from where it was said | No approval is bridged from another ruling; each V1 row quotes Nick on THIS screen | §1a |
| An agent, blocked by a safety guard mid-test, offered the user a choice between loosening the guard and accepting weaker proof — presenting a load-bearing protection as one of two equal options | A guard that blocks a test is reported as NOT MEASURABLE — PERMISSION NOT GRANTED, never traded for loosening it | §A evidence states |
| A fault that repairs itself faster than anyone reports it is invisible to every alarm in the system — two family-facing surfaces cut out roughly twice a day for a MONTH and nobody escalated once | Intermittent faults are run three times by the checker before a clean read is accepted | STEP 12 note |
| A relayed approval was acted on as if the work were still outstanding — and the same file had already been written, by the session doing the relaying | A relayed approval is checked against the file and git before work is redone | §Z, STATE FILE |
| An investigator noticed that a metric could not possibly detect what it was being asked to detect, WROTE THAT DOWN, and then built a headline claim on it anyway — because the number it produced agreed with the conclusion | A metric that cannot detect what it is asked to detect (a check with no red-proof) is replaced, not caveated | STEP 4 |
| An investigation's own searches and relays contaminated the evidence it was searching for — 80 of 84 occurrences of the string were manufactured by the act of investigating it | Searches for a hook are run before any takeover writes it, so the evidence is not contaminated by the build's own output | STEP 2 before STEP 7 |
| Three unrelated lanes in one night each ran an honest check against an intermittent fault and each got a clean answer, because a point-in-time probe is mathematically almost certain to miss a fault that heals itself | Intermittent feed states (beach/vault/transcribe status) are forced deterministically with network blocking, not sampled | STEP 10 |
| An overseer holding the user's GENUINE first-hand instructions relayed them as authority to four sessions — and one correctly refused, because accuracy and standing are different things and only one of them travels | Overseer relays carry Nick's quote; no relayed line moves a step without the quote | §5 |
| A file that documents its own version history in prose ABOVE its code turns every unanchored search into a lie — three sessions in one hour read the changelog and believed it was the declaration | Every grep in this plan is anchored to a line range or a unique token; file histories in prose are excluded from proofs | STEP 2 |
| A commit hash cited as closing evidence resolved to nothing later — sometimes minutes later — because the citation was checked when it was written and never at the moment it was relied on | Every cited hash is checked reachable and pushed at citation time (`git cat-file -e` + `git branch -r --contains`) | STEP 12, STEP 14 |
| A step's own PROOF COMMAND, not just a claim someone else wrote, over-matched — pointed at the right file this time, it still returned a plausible, close, wrong count | Proof commands name the exact file and a token unique to the change; the checker confirms the token is not matched elsewhere | §3b DONE-PROOF |
| A check reported PASS five separate times on one feature while the live screen was wrong every time, and the check's own instrument then reported FAIL five separate times on code that was correct — the same fake page lied in both directions | A check that passes while the screen is wrong is caught by the side-by-side PNG and the blind checker; the check's own red-proof is re-run at STEP 12 | STEP 12, STEP 13 |
| A deliberate, reviewed, gate-passing commit was pre-empted by an automatic snapshot that bundled the change with unrelated files, destroyed its commit message, and meant the commit-time gate never executed at all | Commits are scoped and made in the same turn as the proof; the parity gate runs before deploy so an automatic snapshot cannot bundle a foreign change | §W, STEP 12 |
| A blind checker's whole verdict came back UNVERIFIED because the route into the walled surface it was handed was a remembered ruling, not the measured route | The blind checker's brief carries the MEASURED route into the walled surface (`POST /__gate` with `pw`/`identity`/`next`, the password read from the vault at run time), not a remembered one; the check's preflight signs in and fails loudly (exit 2) before any verdict | STEP 4, STEP 13 |
| A scoped restyle rule read correctly, passed its rig and the design QA, and never applied on screen: an inline style set by a frozen script beat it | Every restyle step's proof is the fidelity check's COMPUTED styles on the live node, so a rule beaten by a frozen script's inline style shows as a mismatch, never as a pass; STEP 8 names inline styles the app writes (`style="flex:1;"`) and neutralises by property, never by assuming the rule won | STEP 4, STEP 8 |
| A cache-busting parameter placed in the URL hash changed which screen the app believed it was on, so a re-check measured another screen's tokens and reported a false FAIL | Cache-busting lives in `?v=N` on asset URLs and the parity gate, never in the hash; the check asserts `data-pl-screen` equals the screen key before measuring and refuses a run whose route resolved elsewhere | STEP 6, STEP 4 |
| Three of five blind-check reds were the brief's own narrowing of the pinned design (an accent allow-list shorter than the pin's list, "one line" for a row the pin wraps, an icon measured on an opacity-0 overlay) | The blind checker is briefed with §2 verbatim and the LOCKED page — never a narrowed restatement; an accent allow-list in a brief is copied from `tools/pearl-accent-sites-shopping.json`, and any red the brief itself caused is struck by the overseer, not counted | STEP 13, §3 |
| A verification read an eventually-consistent store within seconds of writing it and recorded the stale answer as a product defect | Reads after a write (a Someday move, a check-off, a deploy) wait for the app's own re-render or the CDN's settled state (the check re-fetches with cache disabled and retries three times) before a defect is recorded | STEP 8, STEP 12 |
| A test closed ONE of several identical inputs and read the correct unchanged output as a bug | Drive proofs act on ONE named test row (`pearl-check-<date>`) and assert the change on THAT row's `data-id`, never on the first of several identical rows | STEP 8, STEP 13 |
| A routing or safety filter matched a keyword in a PARAMETER NAME rather than in any content, and refused benign mechanical work three times in series | Briefs avoid the words the routing filter trips on (named in §0's hook notes) in parameter names as well as content, and a refusal is read for what it matched before the brief is retried | §0, §5 |
| Two cooperating passes wrote the same artifact filename and the richer one was silently lost while a grader was reading it | Every artefact file name carries its step and screen (`shopping-fidelity-live.txt`, `shopping-blind-check.md`); no two steps write the same path, and STEP 12's runs are numbered | §3 fences, STEP 12 |
| A machine owning a whole role went dark, and its peer's CORRECT standby behaviour silently froze 146 scheduled jobs for fourteen hours | N/A: this plan runs no scheduled jobs and no standby machine; the drive is a session with a state file a stranger resumes from | — |
| An abandoned merge blocked every commit in a shared workspace for every session, and nothing detected it | Every step's first action is `git rev-parse --show-toplevel` and `git status --porcelain` on its own worktree; an abandoned merge or a lock file is reported as the step's BLOCKED line with the path, and the lane works in its own branch, not the shared checkout | §3b standing rules, §5 |
| An append to a SYMLINKED path was committed as the unchanged link, so the content change was never staged and a later merge silently discarded it — while every check in the session read the file through the symlink and saw the change present | The plan, state and change files are real files in the repo (never symlinks); the checker confirms `git ls-files -s` shows mode 100644 for each before a commit is cited | STEP 1, STEP 14 |
| NOVEL — the drawing shows every desktop list while the app hides five behind a switcher; a builder copying the app's behaviour would ship a desktop with one list | §1a row 4 defaults to the drawing; STEP 9's CSS shows every panel at ≥1100; the sheet row lets Nick override | §1a, STEP 9 |
| NOVEL — the round-10 shell CSS (fixed viewport, scroll regions) exists only inside `gen.mjs` and could be hand-copied into the app, drifting from the design | STEP 5 extracts it by script into a source sheet and generates the shell stylesheet; `--check` diffs the regeneration | STEP 5 |
| NOVEL — the chrome lane's nav (eleven destinations, a pill) does not yet match Nick's seven-destination ruling, and this screen's finish line depends on it | STEP 11 measures and hands off; the chrome anchors are the chrome lane's and a mismatch there is a signed GAP on this screen until they land, never a silent fix | STEP 11, §D GAPS |
| A blind checker's whole verdict came back UNVERIFIED because the route into the walled surface it was handed was a remembered ruling, not the measured route | STEP 13's brief carries the MEASURED sign-in route (POST /__gate, fields pw/identity/next, the vault key read at run time) exactly as STEP 2's signed-in pull and STEP 4's check exercised it, never a remembered ruling about the wall; a checker that cannot get in reports NOT MEASURABLE — route, with the HTTP codes | STEP 2, STEP 4, STEP 13 |
| A scoped restyle rule read correctly, passed its rig and the design QA, and never applied on screen: an inline style set by a frozen script beat it | The fidelity check measures COMPUTED styles on the live page, so a rule beaten by an inline style or a later sheet shows as a MISMATCH naming the property; STEP 2's ground truth captured the live outerHTML with its inline styles; the neutraliser is enumerated properties inside the scope, never `all: unset` | STEP 2, STEP 4, STEP 12 |
| A cache-busting parameter placed in the URL hash changed which screen the app believed it was on, so a re-check measured another screen's tokens and reported a false FAIL | The check's route is fixed as `/?skin=pearl#shopping` — the query carries the switch and the hash carries only the screen; the cache is disabled through CDP (Network.setCacheDisabled), never through a hash or query parameter; the chrome pre-count asserts `data-pl-screen` = shopping before any row is measured | STEP 4, STEP 12 |
| Three of five blind-check reds were the brief's own narrowing of the pinned design (an accent allow-list shorter than the pin's list, "one line" for a row the pin wraps, an icon measured on an opacity-0 overlay) | STEP 13's brief is §2 verbatim and the pinned design is the SIGNED anchor map with its accent-site file, never a paraphrase; a red that cites an expectation narrower than the map or the site list is voided by the checker's checker (glm) rather than sent back to a builder | STEP 13, §D |
| A capture instrument reported an element blank (a sketch box, then menu icons) while every DOM and computed-style probe said visible; three fix rounds were built against the phantom | The fidelity suite grades by computed styles and DOM reads against the signed anchor map, never by a screenshot; STEP 12's side-by-side PNGs are for Sienna's taste verdict only after the count is zero | STEP 4, STEP 12 |
| A cheap vendor re-saved a 40 KB checker file whole twice, and its own proof reverted it both times for a removed line | Every routed edit to the suite or the sheets is briefed as an exact hunk and proven by the exact diff; the state file records where the cheap lane's two-attempt limit was hit and the file written on Anthropic instead | STATE machine notes, STEP 4 rounds |
| A concurrent lane's publish from `main` landed seconds after a branch lane's publish and took the SAME cache number, so the version said "new" while the served bytes were the old script | STEP 12 derives the cache number at publish time as max(main, live)+1, pushes the branch to main BEFORE publishing, and asserts the served bytes of this lane's three files against the tree — never the number | STEP 12, STATE resume list item 4 |
| A first-paint acceptance band ("now-line between 25% and 42% of the visible box") graded the only correct rendering FAIL, because nothing above the line existed to scroll away at that hour and box height | N/A: Shopping has no clock-bound first-paint band; every anchor is a static style or box value read from the pinned drawing | §D anchor map |
| A failure path (the sweep's "post a finding to the inbox" step) shipped untested and failed silently the first three times it fired — once on request shape, twice by mis-filing a machine failure as a code regression | STEP 10 forces every reachable state (loading, failed, empty, Add error, the order replies) through the suite's `--states` mode and byte-compares each string to app.js; the suite's own state-mode fix is red-proofed before it is trusted | STEP 10, STATE resume list item 1 |
| A prose section appended through an unquoted shell heredoc executed the backticks in its own text and pasted 155 lines of a selftest's output into an agent guide, unnoticed for seven hours | Prose appends to this lane's plan, state and change log go through quoted heredocs or Python scripts with literal paths, and the tail is printed back after each append | STATE, PLAN-CHANGES |
| Screenshots taken "signed in" showed the signed-out screen: the server accepted the sign-in but the app in the already-loaded page kept `identity: null`, so every capture graded the wrong screen while the log said sign-in worked | The suite plants the gate cookie before the app's first load (POST /__gate, then a fresh page) and asserts the signed-in rendered DOM (the live-only ids from STEP 2) before measuring; a page that fails that assertion is NOT MEASURABLE, exit 2, never a grade | STEP 2, STEP 4 |
| A checker declared a growing list "settled" after two equal reads 700 ms apart and graded a missing item as a product defect; a second checker read the wrong control entirely and reported five boards "unreachable" that were on screen | The suite waits for the list to RENDER (a measured 2519 ms clean, over 15 s under load — a condition, not a fixed settle) before any row measurement, and `absent today` is reported as a data condition, never a defect | STEP 8 (52f69784b), STATE anchor-25 note |
| A build gate that insisted on a symlink into a second repository failed every publish after another lane made the file a tracked regular file — both lanes wanted "one reviewable copy" and the gate encoded only one route to it | N/A: this lane adds no build gate and requires no symlink; the worktree symlinks it uses (.env, vault.py, skippy.db, Skippy.icns) are local conveniences noted in the state file, never encoded in a gate | STATE machine notes |
| A screen's design-fidelity gate read `0 · 0` three times per round while the live screen disagreed with the drawing on sibling ORDER three separate times | Carried, not yet built into this plan's suite (the screen is closed on Sienna's by-eye grade with full-length captures at both widths, three rounds): the next Pearl screen plan's §D map carries a sibling-order anchor per ordered container and its check prints DOM order and painted tops per container; every by-eye grade uses a full-length capture. Registered 2026-09-06 from this lane's own three findings | Postmortem (this plan), STATE postmortem 2026-09-06, PLAN-CHANGES.md 16:30Z to every Pearl lane |

## 5 · Topology and roles
- **OVERSEER-AUTHORITY:** none named for the family app in `projects/ops/OVERSEER-AUTHORITY.md`'s CURRENT HOLDER table at write time (2026-09-04) — this plan's lane works under the Pearl screens bucket's own Fable overseer per Nick's 2026-09-05 tiering ("Fable for planning/oversight only, cheaper models build and check"). The four approval classes and the data floor never move on the overseer's word.
- Thread layout: ONE overseer thread (Fable) for the Pearl screens bucket (this plan and `PLAN-PEARL-EXTRAS.md`); one worker session per running step, dispatched with the §T header, never idle-waiting.
- Overseer: fable (unsticks, design-QA oversight; never builds, never swarms one finding) · Lane manager: none at this size — the overseer coordinates directly · Workers: glm builders, deepseek extractors, sonnet checkers/test authors, fable (Sienna) design QA, se-blind-checker, verifier.
- State files location: `projects/personal/family-app/` — `STATE-PEARL-SHOPPING.md`, `PLAN-CHANGES-PEARL-SHOPPING.md` (both created by STEP 1, beside this plan). No QUESTIONS.md/ASSUMPTIONS.md: every open question is a §1a row; assumptions are the DEFAULTED rows.
- **Board card id:** `nt-20260905-170453-8df1` — this subproject's card on the shared work board, created 2026-09-05. Every step close is posted to it by `unified-project-update.mjs`, which writes the STEPS line, posts the board update and regenerates the status page in one action; none of the three is ever done separately.
- **Artefact consumers:** ground truth → STEPS 3, 7–10; anchor map → STEP 4; the check → STEPS 6–13 and the Extras plan (copied, adapted); evidence files → STEP 12's four verdicts and STEP 14; handoff lines → `PLAN-PEARL-EXTRAS.md` STEPS and `projects/personal/family-app/specs/FINANCES-PEARL-BUILD-HANDOFF-2026-09-04.md`; the shell layer → the Extras lane. Every raise path is proven to ARRIVE by the receiving file's own line (the checker reads it back).
- **Write-contention:** this lane owns its NEW files outright; the three shared files are edited once, at STEP 6, by one builder, re-read before write, scoped commit, never in the same hour as the Finances lane's step that touches them (the state file records the hour); the checkout is proven writable at STEP 1 (probe write, read-back, clean status) and re-proven at STEP 6 and STEP 12.
- **Concurrency:** hard ceiling 8 simultaneously-running agents in this session, machine-wide budget ~40 shared with every live session — count `ls /tmp/cc-socks/` before the first wave and divide; a wave that has not returned is load, not progress. Raising either number is Nick's call, named here, never a session's own.
- **Dispatch header (verbatim, every dispatch):** `ROLE: <GATHERER|BUILDER|VERIFIER|CRITIC|RECONCILER>` · `NICK-ASKED:` only when he named the model · `REVIEW: t2` · `RETURN-SIZE: ~1500 tokens — write findings to disk, return a pointer` · the literal words `MACHINE RULES` with the substance pasted (never a bare `git commit`; never `git stash`; re-read before write; `command grep`; the four approval classes; the data floor; nobody grades their own work; an empty result is evidence about the search; no security work; no second plan file) · the task, the file fence, the exact proof, the stop conditions. Cheap-lane briefs additionally: repo-relative paths only, no folder listings, no `.md` files, no money values, no credentials, never the word that trips the secret filter.

**Per-stage topology — counts DECLARED at plan time:**

| Stage | Overseer | Sub-overseers | Workers |
|---|---|---|---|
| Plan + Design (STEPS 1–3) | 1 | 0 | 3 |
| Tests + Framing (STEPS 4–6) | 1 | 0 | 3 |
| Elements + Details (STEPS 7–10) | 1 | 0 | 4 |
| Tests + Output (STEPS 11–12) | 1 | 0 | 4 |
| Proof (STEPS 13–14) | 1 | 0 | 2 |

**The walk-away contract — a stranger resumes the drive from files alone:**
- **STATE FILE:** `projects/personal/family-app/STATE-PEARL-SHOPPING.md` (created at STEP 1, current-state only, rewritten in place)
- **HEARTBEAT ROW:** pearl-shopping-drive, registered by the drive coordinator in `projects/personal/skippy-app/ala-state/work-threads.json` when the drive opens
- **MORNING-REPORT LINE:** "Pearl Shopping — <n>/20 manifest rows verified, current step, next unblocked step, fidelity count on the live URL" in `projects/ops/walkaway/REPORT.md`

## 6 · Evals — what "working" means, decided now

| Capability | Check (exact command or procedure) | Pass looks like |
|---|---|---|
| (1) Pearl Shopping renders from live data with zero invented strings | STEP 12's live check + STEP 2's string list diffed against the rendered text | `mismatched properties: 0 · unmeasured anchors: 0` ×2; every rendered string is in the ground-truth list |
| (2) Add, check-off, tap-to-buy, no-link note, bundle expand, Order via Skippy work through the existing handlers | STEP 8 `--drive rows` and STEP 13's blind check | four ✓ lines; S4, S9–S13 PASS |
| (3) Every loading / failed / empty / reply string verbatim | STEP 10 `--states` | `states: N/N reached · strings verbatim: N/N`, N derived from the ground truth |
| (4) One viewport at 1280×662, columns meeting the menu's bottom, lists scrolling inside | STEP 9 `--only layout` and STEP 12 live | `frame: 662 · rail: 40→622 · col1: →622 · col2: →622` |
| (5) Phone at 375 and the 900–1099 band | STEP 11 `--band` | `1024: rail absent · scrollWidth<=clientWidth ✓` |
| (6) Flag off unchanged, every other view unchanged | STEP 6 and STEP 12 `--fence-only` | `fence: 0 changed elements (flag off)`, all views |
| (7) Zero mismatches against the locked target | STEP 4's check on the live URL, three runs | three consecutive zero-count runs |
| The cheapest invalidating test, run first | STEP 6: wiring with the flag on and nothing composed changes nothing | both fence runs print 0 before any composition is written |

## If you get stuck (all steps)

Before writing "blocked": (1) try a concrete workaround, (2) re-read the step's proof requirements — most "stuck" is a misread gate, (3) write one line to the overseer AND the owner of the blocker. Only then log `STEP <N> BLOCKED — tried: <a>,<b>,<c>. Need: <one sentence>.` Then keep working every other unblocked step. Never idle on a blocker. The gates that DO stop work: the four approval classes · the data floor · the §S security click · a proof that would destroy live data. Nothing else does.

## Your loop

Every pass: find the lowest-numbered step whose enter gate is proven and which is not yet proven → do it → produce its proof → paste the proof under the matching item in STEPS below → repeat. STEP 0's five-minute loop runs the whole time.

## SUMMARY — a few plain-English lines, read by the status generator

The screen is BUILT and measured clean on every anchor this lane owns; what is left is publishing it. The Shopping drawing Nick approved is the target, published at a login-free address and locked by hash. The screen was rebuilt inside the real app, measured against that drawing until the count of differences is zero, and checked by someone who did not build it. Nick's only wait is a yes on the published drawing; everything else runs without him.

## SUMMARY

**2026-09-06** — The plan file PLAN-PEARL-SHOPPING.md stays closed. Nick picked the fourth concept page for the next Shopping round, a strip of the products the family buys again and again with a last-bought timeline, and asked for better icons and for the section to run on real purchase history. A sheet showing the same eight products in six icon styles is live at https://skippy-designs.pages.dev/family-pearl-shopping-usuals-icons.html, with two real product photos pulled from the family's own shop links. A read-only reconnaissance found that checked-off items stay on the family's Monday.com Shopping List board marked Done, so purchase history can be read through the app's existing board connection, and that nothing fetches product pictures yet. Boris the senior engineer agent is writing the next build plan as the file PLAN-PEARL-USUALS.md with a cold reader. Two choices wait on Nick: which icon style by number, and whether the one-store-at-a-time phone mode from the second concept page is folded into this build.

**2026-09-06** — The plan file PLAN-PEARL-SHOPPING.md stays closed, and three changes landed after the close on Nick's words. The round floating chat button labelled Ask Skippy, which opens the chat with Nick's assistant Skippy, is hidden on every screen of the family app, proven on the live app at phone and desktop widths. The accent colour changed from cranberry to a darker mint green, the design page family-pearl-shopping-r10.html was republished as revision 10.11, the live app followed, the measurement script tools/pearl-fidelity-shopping.mjs printed zero differences three times, and Sienna the creative director graded the mint PASS by eye. Five concept pages for the next Shopping round, each a different layout idea for filling the empty desktop space with product pictures, are live at https://skippy-designs.pages.dev/family-pearl-shopping-ideas.html, and Nick has said so far he likes the fourth one, a strip of the products the family buys again and again with one-tap re-add. Two questions wait on Nick: whether to add a one-store-at-a-time phone mode to that build, and whether to use real product photos from each item's product link.

**2026-09-06** — The family app's Shopping screen is finished and live for Nick and Chantelle at family.heroesandsidekicks.io, restyled to the design system written in the file PEARL-DESIGN-SYSTEM.md, with the cranberry accent colour Nick chose today on the design page family-pearl-shopping-r10.html revision 10.10. Across three publish rounds today the measurement script tools/pearl-fidelity-shopping.mjs printed zero differences on every row the screen owns, Sienna the creative director passed it by eye at both widths with nothing owed, a verifier agent passed the measurement and added then removed a test item, and two blind checkers passed all twenty acceptance rows in the file shopping-blind-inputs-2026-09-06.md. The script check_plan.py prints 14 of 14 steps complete with nothing missing. Four items are handed to other owners in the file PLAN-CHANGES.md, none of them blocking this screen. No decision or action is needed from Nick.

**2026-09-06 — CLOSED.** The family app's Shopping screen is live in the Pearl design for Nick and Chantelle at family.heroesandsidekicks.io (deck-family-v616 and every peer publish since, css v3 / js v4, design page revision 10.10 with the cranberry accent Nick chose). It measured zero differences from the drawing on every row it owns across three publish rounds, Sienna the creative director passed it by eye at both widths with nothing owed, a verifier passed the click-through, and two blind checkers passed all twenty acceptance rows. The desktop rail's own styling is the Home screen plan's; the Order via Skippy button is never clicked in testing because it stages a real order.

**2026-09-06** — The second fix round is live. A builder agent republished the family app as deck-family-v608 with the store row placed left of the Add box on the desktop Shopping screen, the Amazon list placed in the left column, the decorative sketch from the file js/pearl-sketch.js no longer drawn inside Shopping cards, and the cranberry accent colour from design page revision 10.10. Three runs of the measurement script tools/pearl-fidelity-shopping.mjs printed zero differences on every row the Shopping screen owns. Sienna the creative director graded the republished screen PASS by eye at both widths, then looked at a full-length phone capture and found one more ordering difference below the fold, the empty Walmart card sitting between two populated lists instead of at the tail. A builder agent is now fixing that one difference and republishing, and a verifier agent is re-running the measurement script and adding then removing one test item on the live screen. No decision or action is needed from Nick.

**2026-09-06** — A verifier agent re-ran the measurement script tools/pearl-fidelity-shopping.mjs on the published Shopping screen and it printed zero differences from the design page family-pearl-shopping-r10.html at both widths. The same agent added a test item named pearl-check-2026-09-06-v, checked it off, and opened a link row, a note row and a bundle row, and all of that worked. A request to /api/finances with no login cookie returned 401 again at 14:59Z. A builder agent has republished the design page family-pearl-shopping-r10.html as revision 10.10 with the new cranberry accent colour and is republishing the family app with three layout corrections on the desktop Shopping screen. No decision or action is needed from Nick.

**2026-09-06** — The Shopping screen was published to family.heroesandsidekicks.io at 14:24Z as deck-family-v601, and three live runs of the measurement script tools/pearl-fidelity-shopping.mjs printed zero mismatched properties and zero unmeasured anchors on every row the screen owns. Sienna the creative director then graded the live screen by eye and failed it on two ordering defects at 1280 pixels wide, the Add box sitting left of the store row and the Amazon list sitting in the right column instead of the left. Nick asked by eye for two more changes, no pencil sketch drawing into the Shopping cards and a more visible accent colour that is not blue, which is now cranberry red. A builder agent using the Opus model is applying all four changes and republishing. Nothing needs Nick.

**2026-09-06** — Sienna the creative director ruled in writing on 2026-09-06 at 14:15Z that rows 50 to 53 of the file gen.mjs-anchors-shopping.md, which measure the left-hand menu of the desktop layout, belong to the Home screen plan PLAN-HOME-PEARL.md and do not count against the Shopping screen, because Nick set that menu's look himself on the Home screen. The branch pearl-shopping/drive was rebased onto main at 14:12Z and pushed. The measurement script tools/pearl-fidelity-shopping.mjs, run again after the rebase, reports all 108 rows the Shopping screen owns as ok, saved in the file shopping-step12-postrebase-check-run1.txt. A builder agent using the Opus model is now publishing the screen by following the file shop-step12-publish.md. Nothing needs Nick.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The screen measures zero differences from the approved picture, the file redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html at revision 10.6, at both the phone size of 375 by 812 and the desktop size of 1280 by 662, across all ninety measured rows, and a verifier that did not build any of it has now re-run every one of those measurements twice and agreed. It went further on the one difference that had been misdiagnosed earlier in the day, proving the stated cause is the real cause by putting the fault back and watching the height return, then removing it again. It also found one genuine gap and blocked the step for it: five style rules in projects/personal/family-app/css/pearl-shopping.css override another sheet without naming what they override, which this plan requires, and rather than merely flagging the omission the verifier went and identified the competing rule at line 844 of projects/personal/family-app/css/reskin-popfix.css. That comment is queued because another agent is editing the same stylesheet right now to build the wording of every loading, empty and error message, each styled rather than rewritten and compared letter for letter against projects/personal/family-app/js/app.js at run time. The family app as it renders without the URL parameter skin=pearl is proven byte-identical at all three widths, so nobody outside this build sees any change yet.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The screen measures zero differences from the approved picture, the file redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html at revision 10.6, at both the phone size of 375 by 812 and the desktop size of 1280 by 662, across all ninety measured rows. That covers the header, the toolbar, the store list cards, all three kinds of row, the empty lists, and the desktop layout of one fixed viewport with two columns whose bottoms meet the menu. Two agents are working now. One is a verifier that did not build any of it, re-running every measurement including a causal test of a five pixel height difference on the kind of row that groups several products under one heading, whose first explanation was disproved by an earlier verifier before the true cause was found. The other is building the wording of every loading, empty and error message so each is styled rather than rewritten, compared letter for letter against projects/personal/family-app/js/app.js at run time. The comparison tool tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that picture hashes equal to the local one. The family app as it renders without the URL parameter skin=pearl is proven byte-identical at all three widths, so nobody outside this build sees any change yet. Four things then remain, a width and menu conformance pass, publishing the screen behind the switch, a blind check of every row of the specification in section 2 of the plan, and writing the closing entry into the plan file itself.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The screen measures zero differences from the approved picture, the file redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html at revision 10.6, at both the phone size of 375 by 812 and the desktop size of 1280 by 662, across all ninety measured rows. That covers the header, the toolbar, the store list cards, all three kinds of row, the empty lists, and the desktop layout of one fixed viewport with two columns whose bottoms meet the menu. Two agents are working now. One is a verifier that did not build any of it, re-running every measurement including a causal test of the bundle row's five pixel height difference, whose first explanation was disproved earlier by a different verifier agent before the true cause was found. The other is building the wording of every loading, empty and error message so each one is styled rather than rewritten, compared byte for byte against projects/personal/family-app/js/app.js at run time. The comparison tool tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that picture hashes equal to the local one. The family app as it renders without the URL parameter skin=pearl is proven byte-identical at all three widths, so nobody outside this build sees any change yet. Four things then remain, a width and menu conformance pass, publishing the screen behind the switch, a blind check of every row of the specification in section 2 of the plan, and writing the closing entry into the plan file itself.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The screen now measures zero differences from the approved picture, the file redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html at revision 10.6, at both the phone size of 375 by 812 and the desktop size of 1280 by 662, across all ninety measured rows. That covers the header, the toolbar, the store list cards, all three kinds of row, the empty lists, and the desktop layout of one fixed viewport with two columns whose bottoms meet the menu. A verifier agent that did not build any of it is re-running every measurement now, including a causal test of the bundle row's five pixel height difference, whose first explanation was disproved earlier today by a different verifier agent. The comparison tool tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that picture hashes equal to the local one. The family app as it renders without the URL parameter skin=pearl is proven byte-identical at all three widths, so nobody outside this build sees any change. Five things remain in the plan, the wording of every loading, empty and error state, a width and menu conformance pass, publishing the screen behind the switch, a blind check of every row of the specification in section 2 of the plan, and writing the closing entry into the plan file itself.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The picture the whole build is measured against is the file redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html at revision 10.6, which Nick approved on 2026-09-04. The phone version of the screen now measures zero differences from that file across all thirty-nine measured elements, re-confirmed by a verifier agent that did not build it. The desktop version has seven properties left on two elements. That verifier tested the builder's explanation for one of them rather than accepting it, and disproved it, because widening the card to the 471 pixel column width used in that file changed the row height by nothing, so the five-pixel gap has a cause nobody has identified yet and goes back as a named defect rather than being carried into the next step of the plan. The comparison tool tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that same file hashes equal to the local one. A second finding is being fixed now, because the part of that tool which clicks a row and checks what happens waited a fixed three seconds, while these lists took 2519 milliseconds on a clean load and more than fifteen seconds under load, so it was reporting that rows do not exist when they do. Nick's new shared-motion rule, section 11a of projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, was audited against this screen, which wrote no motion of its own, so nothing needs deleting. One conflict is written into projects/personal/family-app/STATE-PEARL-SHOPPING.md for the record, that the shared module js/pearl-sketch.js draws a picture into a card after two seconds of empty room while this plan requires the screen to measure zero differences against a still picture, and it blocks nothing today. The family app as it renders without the URL parameter skin=pearl is proven byte-identical at all three widths.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The picture the whole build is measured against is the file redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html at revision 10.6, which Nick approved on 2026-09-04. The phone version of the screen now measures zero differences from that file across all thirty-nine measured elements, re-confirmed by a verifier agent that did not build it. The desktop version has seven properties left on two elements. That verifier tested the builder's explanation for one of them rather than accepting it, and disproved it: widening the card to the 471 pixel column width used in that file changed the row height by nothing, so the five-pixel gap has a cause nobody has identified yet and goes back as a named defect rather than being carried into the next step of the plan. The comparison tool tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that same file hashes equal to the local one. A second finding is being fixed now: the part of that tool which clicks a row and checks what happens waited a fixed three seconds, while these lists took 2519 milliseconds on a clean load and more than fifteen seconds under load, so it was reporting that rows do not exist when they do. Nick's new shared-motion rule, section 11a of projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, was audited against this screen: it wrote no motion of its own, so nothing needs deleting. One conflict is written into projects/personal/family-app/STATE-PEARL-SHOPPING.md for the record: the shared module js/pearl-sketch.js draws a picture into a card after two seconds of empty room, while this plan requires the screen to measure zero differences against a still picture; it is logged there and blocks nothing today. The family app as it renders without the URL parameter skin=pearl is proven byte-identical at all three widths.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The picture the whole build is measured against is the file redesign-mockups/concepts-2026-09-04-round10-screens/shopping.html at revision 10.6, which Nick approved on 2026-09-04. The phone version of the screen now measures zero differences from that file across all thirty-nine measured elements, re-confirmed by a verifier agent that did not build it. The desktop version has seven properties left on two elements. That verifier tested the builder's explanation for one of them rather than accepting it, and disproved it: widening the card to the 471 pixel column width used in that file changed the row height by nothing, so the five-pixel gap has a cause nobody has identified yet and goes back as a named defect rather than being carried into the next step of the plan. The comparison tool tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that same file hashes equal to the local one. A second finding is being fixed now: the part of that tool which clicks a row and checks what happens waited a fixed three seconds, while these lists took 2519 milliseconds on a clean load and more than fifteen seconds under load, so it was reporting that rows do not exist when they do. Nick's new shared-motion rule, section 11a of projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, was audited against this screen: it wrote no motion of its own, so nothing needs deleting. One conflict is written into projects/personal/family-app/STATE-PEARL-SHOPPING.md for the record: the shared module js/pearl-sketch.js draws a picture into a card after two seconds of empty room, while this plan requires the screen to measure zero differences against a still picture; it is logged there and blocks nothing today. The family app as it renders without the URL parameter skin=pearl is proven byte-identical at all three widths.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The phone version of the screen is complete and measures zero differences from the approved drawing: header, toolbar, background wash, the store list cards, and all three kinds of row including the check box, the product-link arrow, the no-link chip and the bundle row. The desktop version has seven properties left, all on two elements whose size depends on the two-column layout being built right now, and a separate verifier agent that did not build them is independently measuring whether that explanation holds. Nick's new shared-motion rule, section 11a of projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, was audited against this screen the hour it arrived: this screen wrote no motion of its own at all, so there is nothing to delete, and the two attributes it still owes go in as soon as the file js/pearl-shopping.js is free. One conflict was recorded rather than resolved, as that rule instructs: the shared module draws a sketch into a card's empty room after two seconds, and this plan requires the screen to measure zero differences against a still drawing, so a sketch appearing mid-measurement could change a card's measured height. The everyday app is proven byte-identical when the URL parameter skin=pearl is absent, at all three widths. One fact for whoever publishes next: the stylesheet css/pearl-shopping.css currently served to the app is an older build while the script js/pearl-shopping.js served beside it is newer, so the two are out of step in production until a republish.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The phone version of the screen is complete and measures zero differences from the approved drawing: header, toolbar, background wash, the store list cards, and all three kinds of row including the check box, the product-link arrow, the no-link chip and the bundle row. The desktop version has seven properties left, all on two elements whose size depends on the two-column layout being built right now, and a checker that did not build them is independently measuring whether that explanation holds. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs at revision 10.6, and tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that drawing hashes equal to the local one, after another build session overwrote it earlier today. The everyday app is proven byte-identical when the URL parameter skin=pearl is absent, at all three widths. Two facts for whoever publishes next: the stylesheet css/pearl-shopping.css currently served to the app is an older build while the script js/pearl-shopping.js served beside it is newer, so the two are out of step in production until a republish; and inside tools/pearl-fidelity-shopping.mjs the code that clicks a row and checks what happens waits only 3000 milliseconds, while these lists take longer to appear behind the password wall, so that wait must be lengthened before anything later relies on it.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. The phone version of the screen is now complete and measures zero differences from the approved drawing: header, toolbar, background wash, the store list cards, and all three kinds of row including the check box, the product-link arrow, the no-link chip and the bundle row. The desktop version has seven properties left, all on two elements whose size depends on the two-column layout that is being built right now, and a checker that did not build them is independently measuring whether that explanation holds rather than accepting it. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs at revision 10.6, and the comparison tool tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published copy of that drawing hashes equal to the local one, after another build session overwrote it earlier today. The everyday app is proven byte-identical when the URL parameter skin=pearl is absent, at all three widths. One fact for whoever publishes next: the stylesheet css/pearl-shopping.css currently served to the app is an older build while the script js/pearl-shopping.js served beside it is newer, so the two are out of step in production until a republish.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. Its header, toolbar and background wash are finished and closed: they measure zero differences from the approved drawing at both the phone and desktop sizes, and a checker that did not build them re-ran every measurement independently with zero failures, including a control test that re-injected the snapshot css/pearl-shopping.css.pre-pearl-shop-step7c-20260905.bak and reproduced the six wrong properties on the glass card around the form whose identifier is shopAddForm. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs at revision 10.6; it had been silently overwritten at skippy-designs.pages.dev by another build session publishing that same website from the main branch, and that is fixed three ways: the main branch carries the revision, the live bytes were verified four times including a real browser reading, and tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published page hashes equal to the local copy. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json, and the list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. The store list cards and the three kinds of row are being built now. One fact for whoever publishes next: the stylesheet css/pearl-shopping.css currently served to the app is an older 156-line build while the script js/pearl-shopping.js served beside it is newer, so the two are out of step in production until a republish.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. Its header, toolbar and background wash are finished and closed: they measure zero differences from the approved drawing at both the phone and desktop sizes, and a checker that did not build them re-ran every measurement independently with zero failures, including a control test that re-injected the snapshot css/pearl-shopping.css.pre-pearl-shop-step7c-20260905.bak and reproduced the six wrong properties on the glass card around the form whose identifier is shopAddForm. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs at revision 10.6; it had been silently overwritten at skippy-designs.pages.dev by another build session publishing that same website from the main branch, and that is fixed three ways: the main branch carries the revision, the live bytes were verified four times including a real browser reading, and tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published page hashes equal to the local copy. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json, and the list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. The store list cards and the three kinds of row are being built now. One fact for whoever publishes next: the stylesheet css/pearl-shopping.css currently served to the app is an older 156-line build while the script js/pearl-shopping.js served beside it is newer, so the two are out of step in production until a republish.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md. Its header, toolbar and background wash now measure zero differences from the approved drawing at both the phone and desktop sizes, and an independent checker is re-running every one of those measurements. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs at revision 10.6; it had been silently overwritten at skippy-designs.pages.dev by another build session publishing that same website from the main branch, which made two rounds of work measure against the wrong page, and that is now fixed three ways: the main branch carries the revision, the live bytes were verified four times including a real browser reading, and tools/pearl-fidelity-shopping.mjs refuses to run at all unless the published page hashes equal to the local copy. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json, and the list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. css/pearl-shopping.css and js/pearl-shopping.js are linked in index.html and precached in sw.js, and the everyday app is proven byte-identical when the URL parameter skin=pearl is absent. The store list cards and the three kinds of row are being built now. One fact for whoever publishes next: the copy of css/pearl-shopping.css currently served to the app carries an older rule that flattens the glass card around the form whose identifier is shopAddForm, so a republish is what puts today's fixes in front of anyone using the screen.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md, and its header, toolbar and background wash now measure zero differences from the approved drawing at both the phone and desktop sizes. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs at revision 10.6, and it had been silently overwritten at skippy-designs.pages.dev by the Skippy-frame lane publishing the same site from the main branch, which made two rounds of work measure against the wrong page; that is fixed three ways, with the main branch carrying the revision, the live bytes verified four times including a real browser reading, and tools/pearl-fidelity-shopping.mjs refusing to run at all unless the published page hashes equal to the local copy. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json, and the list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. The shared frame stylesheet css/pearl-shell.css is generated from that same drawing file. css/pearl-shopping.css and js/pearl-shopping.js are linked in index.html and precached in sw.js, and the everyday app is proven byte-identical when the URL parameter skin=pearl is absent. The store list cards and the three kinds of row are being built now. One fact for whoever publishes next: the copy of css/pearl-shopping.css currently served to the app carries an older rule that flattens the glass card around the form whose identifier is shopAddForm, so a republish is what puts today's fixes in front of anyone using the screen.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md, and is seven steps into fourteen. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs, whose header line reads REV 10.6 after the creative director redlined it: the phone Add fields are drawn at 16px because css/mobile-audit.css pins every phone input to 16px to stop iOS zooming the page. That drawing had been silently overwritten at skippy-designs.pages.dev by the Skippy-frame lane publishing the same site from the main branch, which made two rounds of work measure against the wrong page; that is now fixed three ways, with the main branch carrying the revision, the live bytes verified four times including a real browser reading 16px, and tools/pearl-fidelity-shopping.mjs refusing to run at all unless the published page hashes equal to the local copy. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json, and the list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. The shared frame stylesheet css/pearl-shell.css is generated from that same drawing file. css/pearl-shopping.css and js/pearl-shopping.js are linked in index.html and precached in sw.js, and the everyday app is proven byte-identical when the URL parameter skin=pearl is absent. The header, toolbar and background wash are built: the desktop measures zero mismatches and the phone now has one fault left, the glass card around the Add form, with its fix in flight. The list cards and rows are written but not yet proven.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md, and is seven steps into fourteen. The approved drawing is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs, whose header line reads REV 10.6 after the creative director redlined it: the phone Add fields are drawn at 16px because css/mobile-audit.css pins every phone input to 16px to stop iOS zooming the page. That drawing had been silently overwritten at skippy-designs.pages.dev by the Skippy-frame lane publishing the same site from the main branch, which made two rounds of work measure against the wrong page. That is now fixed three ways: the main branch carries the revision so every lane serves it, the live bytes were verified four times including a real browser reading 16px, and tools/pearl-fidelity-shopping.mjs now refuses to run at all unless the published page hashes equal to the local copy, a refusal proven in the wild, proven on demand, and proven to pass cleanly when the page is right. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json. The list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. The shared frame stylesheet css/pearl-shell.css is generated from that same drawing file. css/pearl-shopping.css and js/pearl-shopping.js are linked in index.html and precached in sw.js, and the everyday app is proven byte-identical when the URL parameter skin=pearl is absent. The header, toolbar and background wash are built; the desktop measures perfect and the phone has two faults being fixed. The list cards and rows are written but not yet proven.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md, and is seven steps into fourteen. The drawing Nick approved on 2026-09-04 is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs, whose header line now reads REV 10.6 after the creative director redlined it: the phone Add fields are drawn at 16px because css/mobile-audit.css pins every phone input to 16px to stop iOS zooming the page. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json. The list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. The comparison tool tools/pearl-fidelity-shopping.mjs survived four builder rounds and three independent checkers, and now refuses to run at all when the published drawing is not the approved revision. The shared frame stylesheet css/pearl-shell.css is generated from that same drawing file. css/pearl-shopping.css and js/pearl-shopping.js are linked in index.html and precached in sw.js, and the everyday app is proven byte-identical when the URL parameter skin=pearl is absent. The header, toolbar and background wash are built and measured. The list cards and rows are written but not yet proven. One blocker is open: the approved drawing keeps being overwritten at skippy-designs.pages.dev by the Skippy-frame lane publishing that same site from the main branch, and the fix landing now is pushing our revision to main so every lane serves it.

**2026-09-05** — The family app's Shopping screen is being restyled to the design system written in projects/personal/family-app/PEARL-DESIGN-SYSTEM.md, under the plan projects/personal/family-app/PLAN-PEARL-SHOPPING.md, and is seven steps into fourteen. The drawing Nick approved on 2026-09-04 is the output of redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs, whose header line now reads REV 10.6 after the creative director redlined it: the phone Add fields are drawn at 16px because css/mobile-audit.css pins every phone input to 16px to stop iOS zooming the page. The live screen's every id, class, string and endpoint is captured in redesign-mockups/concepts-2026-09-04-round10-screens/ground-truth-shopping.json. The list of 55 elements to compare, gen.mjs-anchors-shopping.md, is signed with zero gaps. The comparison tool tools/pearl-fidelity-shopping.mjs survived four builder rounds and three independent checkers, and now refuses to run at all when the published drawing is not the approved revision. The shared frame stylesheet css/pearl-shell.css is generated from that same drawing file. css/pearl-shopping.css and js/pearl-shopping.js are linked in index.html and precached in sw.js, and the everyday app is proven byte-identical when the URL parameter skin=pearl is absent. The header, toolbar and background wash are built and measured. The list cards and rows are written but not yet proven. One blocker is open: the approved drawing keeps being overwritten at skippy-designs.pages.dev by the Skippy-frame lane publishing that same site from the main branch, and the fix landing now is pushing our revision to main so every lane serves it.

## STEPS

1. [Plan] Lock the target: REV header, regenerate, publish login-free, record the revision — DONE 2026-09-05
   DEFINITION OF DONE: the §D block carries `REV 10.5 · commit <hash>` and Nick's dated words on the published page; the published sha256 equals the generator output's
   PROOF: `command grep -c '^// REV ' projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/gen.mjs` prints 1; the curl (with `-L`, per the correction in the STEP 1 block) prints 200
   VERIFIED (builder): 1 · curl 200 · sha256 equal · ab9f81eec3f990f571afc807a00e57b5f89f1d57 on origin/main
2. [Plan] Ground truth for Shopping — DONE 2026-09-05
   DEFINITION OF DONE: the ground-truth file exists with ids, classes, data-attributes, verbatim strings, endpoints and the signed-in rendered DOM (money masked), no credential
   PROOF: the four-count line prints ≥ 17 · 30 · 12 · 3 (the id bar corrected 21 → 17 by the checker's recount, PLAN-CHANGES 2026-09-05)
   VERIFIED (builder, Opus, commit f6297dc8d): `ids 17 · classes 61 · strings 44 · endpoints 5` (evidence/shopping-step2-counts.txt); money/df_ident/vault-key/VALUE greps all 0; live.phone and live.desktop settled, pulledAt 2026-09-05T04:51:53Z as nick
   VERIFIED (checker, Sonnet, commit e1f213bb3): recount from index.html lines 2659–2747 = 13 static + 4 live-only = 17, none missing; five strings byte-identical to app.js; five endpoints match, nothing invented; every hook in the "Already true" bullet present — `STEP 2 CHECK: PASS` (evidence/shopping-step2-check.txt)
3. [Design] Anchor map signed by design QA — DONE 2026-09-05
   DEFINITION OF DONE: every drawn element is an anchor or a signed GAP (≤2); viewports signed
   PROOF: the anchor map carries one `SIGNED BY` line (CREATED BY STEP 3)
   VERIFIED (builder, Opus, commit 7ae40f16b): 54 drawn elements → 54 anchors, 0 gaps; every one of 100 design selectors and 45 live selectors counted in real headless Chrome (evidence/shopping-step3-selector-counts.json) — every live selector exactly one node per viewport; four disagreements written into the map, none a GAP
   VERIFIED (signer, Sienna on Fable, creative-director): own count 55 (the content box `.body` added as row 55), none refused, eleven design selectors spot-checked against the published markup; rulings (a)–(d), the pills decision, precondition P5 and three conditions for STEP 4 pasted into the map; `SIGNED BY sienna 2026-09-05 — elements drawn: 55 · anchors: 55 · gaps: 0`
4. [Tests] The fidelity check with its red-proof — DONE 2026-09-05
   DEFINITION OF DONE: `--selftest` prints `red-proof: 1 mismatch (paddingTop) · green-proof: 0`
   PROOF: `node projects/personal/family-app/tools/pearl-fidelity-shopping.mjs --selftest` (CREATED BY STEP 4)
   VERIFIED (builders — Sonnet round 1 `9c8dde8f4`, Sonnet round 2 `3ecfb3e42`, Sonnet round 3 `c82e7dbf8`, Sonnet + Opus finisher round 4 `ce5299beb`): `red-proof: 1 mismatch (paddingTop) · green-proof: 0` exit 0; `--bogus` exit 2; broken Chrome path exit 2; UNMEASURED red-proof exit 2 naming the anchor; 55 anchors with P1–P5, chrome pre-count, absent-today; retired sweep; `--fence-pair off|on` in one frozen session (data replayed, time pinned, settle + idle wait on both sides, no run-time exclusion) 0 ×3 each, leak red-proof 3/3, skin red-proof at every viewport 9/9, scoped sheet does not leak; row 55 frame-relative 284 · 1240 · 622; baseline run on the unstyled screen 225 · 38 · absent 9
   VERIFIED (checkers — Sonnet verifier rounds 1–2 FAIL with the defects named and fixed: fence flap; run-time exclusion that hid an injected change 1/3; page-relative row 55; then the final re-check, evidence/shopping-step4-final-check.txt): no self-weakening, both sides settle identically, off/on pairs 0, leak and skin and scoped-no-leak red-proofs, an own nasty case, row 55, exit codes, `--fence-skin-diff` informational, data floor 0 — `STEP 4 FINAL CHECK: PASS`
5. [Framing] The shell layer generated — DONE 2026-09-05
   DEFINITION OF DONE: the shell stylesheet regenerates identically; zero unprefixed classes; class map written
   PROOF: the renamer's `--check` prints `regenerated: identical` (CREATED BY STEP 5)
   VERIFIED (builder, Opus via the cheap lane, commit 55eb25292): `regenerated: identical` exit 0; unprefixed class selectors 0 of 205 tokens; declarations 178 source / 178 output; intersection with pearl.css = pl-g pl-l pl-s; 34 source classes all mapped; red-proof DIFFERS/exit 1 then identical/exit 0; gen.mjs, pearl.css, pearl-rename.mjs sha256 unchanged (evidence/shopping-step5-proof.txt)
   VERIFIED (checker, Sonnet verifier, evidence/shopping-step5-check.txt): items 1–4 and 6–8 re-run and PASS (idempotent `--check`, own red-proof with matching sha256 before/after, 63 rules all scoped under `body.skin-pearl .pl-shell`, only the two 1099/1100 breakpoints, collision renames applied and used, read-only files untouched between 15d82d07f and 55eb25292, only `662px` of the five literals lives in gen.mjs); item 5 flagged two rules beyond the named list (`.rail .badge`, `.g.hasring .l`) — accepted by the overseer with a PLAN-CHANGES delta 2026-09-05 and the STEP 5 list amended; overseer re-ran `--check` after the ruling: `regenerated: identical` exit 0, `.pillx` 0
6. [Framing][UI] Wire in, painting nothing — DONE 2026-09-05
   DEFINITION OF DONE: links + sw.js + contract-check updated; parity PASS; fence 0 with the flag off and on
   PROOF: `node projects/ops/skippy-jobs/_test-family-app-asset-version-parity.mjs` prints PASS; the check's `--fence-pair off` and `--fence-pair on` print `0 changed elements` (proof corrected 2026-09-05, PLAN-CHANGES: the flag-off-vs-on compare measures other lanes' skin)
   VERIFIED (builder, Opus via route-build/glm, commits ee644eb73 + 8e1634dd2): links 27 → 29 (shell @72, shopping @73), scripts 42 → 43 (@3327), sw.js ASSETS 69 → 72, CACHE v462 → v464 (live had moved to v463 mid-step — renumbered past it); parity `12 passed, 0 failed`; contract-check untouched (both coverage gates named nothing to add; 22 pre-existing FAILs are other lanes'); `--fence-pair off` 0 · `--fence-pair on` 0 at all three viewports; full check with both sheets injected = the STEP 4 baseline (225 · 38); the cheap lane refused the two header stubs on its hard-floor filter twice, so they were written by hand (cheap-vendor-failed, recorded)
   VERIFIED (checker, Haiku verifier, evidence/shopping-step6-check.txt): all eight items re-run — fence-scoped commits, the three tags at the named lines with nothing else in the diff, v464 > live, parity PASS twice, contract-check untouched, header-only stubs, `--fence-pair off` 0 (114 responses replayed) · `on` 0 (113), data floor 0 — `STEP 6 CHECK: PASS`
7. [Elements][UI] Header, toolbar, accent sites — DONE 2026-09-06
   DEFINITION OF DONE: header and toolbar anchors at 0 mismatches at both viewports
   PROOF: the check's `--only header,toolbar --inject …` prints `mismatched properties: 0`
   VERIFIED: 2026-09-05 (90%, built and A/B proven; close pending the restored locked target) — built on Opus via route-build/glm: .pl-ph1 rendered from pop.js's own count and hero line, the wash mounted, the pill switcher and Add form inside .pl-shtb at 1100+ (Sienna's Condition 3 measured true), pop.js leaf nodes hidden only, tools/pearl-accent-sites-shopping.json written and audited to one accent element per viewport. Checker (Sonnet verifier) PASS on seven of eight items; its one FAIL — the hero pill strip still visible at desktop because an unqueried show rule outranked the media-gated hide rule — was fixed and proven by A/B control (removing the one line reproduces the failure verbatim, restoring it closes it). Rows 12-13 read red only because the published copy of the drawing had reverted under a republish by the Skippy-frame lane; tools/pearl-fidelity-shopping.mjs now refuses to measure against a page whose sha256 differs from the local locked target (that refusal proven in the wild and deterministically).
   VERIFIED: 2026-09-05 (90%, built and A/B proven; close pending the restored locked target) — .pl-ph1 rendered from pop.js's own count and hero line, the wash mounted, the pill switcher and Add form inside .pl-shtb at 1100+, pop.js leaf nodes hidden only, tools/pearl-accent-sites-shopping.json audited to one accent element per viewport. Checker PASS on seven of eight items; its one FAIL (the hero pill strip still visible at desktop because an unqueried show rule outranked the media-gated hide rule) was fixed and proven by A/B control. Rows 12-13 read red only because the published copy of the drawing had reverted under a republish by the Skippy-frame lane; tools/pearl-fidelity-shopping.mjs now refuses to measure against a page whose sha256 differs from the local locked target, proven in the wild and deterministically.
   VERIFIED: 2026-09-05 17:40Z re-measured against the correctly-restored approved drawing (the check's own preflight printed 'locked target: b2346691e90dfb91 (published == local)' first): DESKTOP 1280x662 every header and toolbar anchor ok, zero mismatches. PHONE 375x812 has two faults left, both being fixed now — anchor 15, the glass card around the Add form, reads transparent with no radius, no padding and height 40 against a drawn 76, cause being measured in the browser rather than guessed; and the derived hero-pill-count case compared a number and a pill count read moments apart while pop.js animates that number, which two earlier checkers also hit, now being read in one page evaluation after the count settles. The two Add-field rows that read red yesterday are GREEN: they were only ever measuring against a stale copy of the drawing that another lane had republished over ours.
   VERIFIED: 2026-09-05 17:55Z — one of the two remaining phone faults is closed. The derived hero-pill-count case was comparing two values read moments apart while js/pop.js animates the count for about 600ms after each render, so a read landing mid-count-up compared a stale number against already-final pills; two earlier checkers hit the same race. It now polls the count until it stops changing, then reads the number and the pill count atomically in one page evaluation, and reports NOT MEASURABLE with exit 2 if the count never settles. Proven: three back-to-back runs green at both viewports, six of six; a real red-proof removing one pill element gave MISMATCH expected=12 actual=11 at phone and actual=10 at desktop, exit 1; the test-only hook can only force a genuine disagreement and can never skip the assertion; selftest and unknown-flag regressions intact. Commit e8fcc76ed. One fault remains: anchor 15, the glass card around the Add form on the phone, reads transparent with no radius, no padding and height 40 against a drawn 76; its fix is in flight and was told to measure which stylesheet rule wins rather than guess. Desktop remains at zero mismatches.
   VERIFIED: 2026-09-05 17:42Z the suite printed 'locked target: b2346691e90dfb91 (published == local)' then 'mismatched properties: 0 · unmeasured anchors: 0' at both viewports, retired colours 0, exit 0; --fence-pair off 0 changed at all three viewports. The last fault was found by measurement, not by guessing: CDP CSS.getMatchedStylesForNode showed this lane's own neutraliser at css/pearl-shopping.css line 155, body.skin-pearl #view-shopping .card.td-card, outranking the Add-card rule because the live node is section class='card td-card shop-add-card' — one id and three classes against one id and two. Both hypotheses in the brief were refuted first: the custom properties resolved correctly at the element, and css/reskin-popfix.css matched but set only a border colour. Fixed by narrowing that neutraliser with :not(.shop-add-card) and raising both Add-card rules to .card.td-card.shop-add-card, moved together so the phone rule cannot beat the desktop neutraliser; one new !important on border-width neutralises two named !important rules, both cited in a comment. Earlier in the step the hero pill strip visible at desktop was fixed and A/B proven, and the derived pill-count race was closed by reading the number and the pills in one page evaluation after the count settles. A closing checker is re-running every item independently. Commits 24dc94e35, e8fcc76ed, 5d7e45913.
   VERIFIED: 2026-09-05 17:42Z — 'locked target: b2346691e90dfb91 (published == local)' then 'mismatched properties: 0 · unmeasured anchors: 0' at both viewports, retired colours 0, exit 0; --fence-pair off 0 changed at all three viewports. The final fault was found by measurement: CDP CSS.getMatchedStylesForNode showed this lane's own neutraliser at css/pearl-shopping.css line 155 outranking the Add-card rule because the live node carries three classes (card td-card shop-add-card); both hypotheses in the brief were refuted first. Fixed by narrowing that neutraliser with :not(.shop-add-card) and raising both Add-card rules, moved together so the phone rule cannot beat the desktop neutraliser. Commits 24dc94e35, e8fcc76ed, 5d7e45913; an independent closing checker is re-running every item.
   VERIFIED: 2026-09-05 CLOSED — an independent checker (Sonnet verifier, evidence/shopping-step7-close-check.txt, commit 6032e5f4b) re-ran every item with zero FAILs: two back-to-back runs each printing the locked-target preflight and 'mismatched properties: 0 · unmeasured anchors: 0'; its own A/B control re-injecting css/pearl-shopping.css.pre-pearl-shop-step7c-20260905.bak reproduced the six-property failure on anchor 15, proving the fix is what closed it; every !important named its competing rule; the toolbar contains both the store switcher and the form whose identifier is shopAddForm at 1280; the app's own switcher handler still fires through the takeover and the render counter stays flat while idle; zero fetch calls in js/pearl-shopping.js; the accent audit found only listed sites; fonts resolve to Iowan Old Style and Helvetica Neue; --fence-pair off zero at all three viewports. It also confirmed, by signing in as a real visitor would, that https://family.heroesandsidekicks.io/css/pearl-shopping.css?v=1 currently serves a 156-line build with no :not(.shop-add-card) anywhere, while the script served alongside it is newer — so production's stylesheet and script are out of sync until the publish step.
   VERIFIED: 2026-09-05 CLOSED — an independent checker (Sonnet verifier, evidence/shopping-step7-close-check.txt, commit 6032e5f4b) re-ran every item with zero FAILs: two back-to-back runs each printing the locked-target preflight and 'mismatched properties: 0 · unmeasured anchors: 0'; its own control re-injecting the snapshot css/pearl-shopping.css.pre-pearl-shop-step7c-20260905.bak reproduced the six wrong properties on anchor 15, proving the fix is what closed it; every !important named its competing rule; the toolbar contains both the store switcher and the form whose identifier is shopAddForm at 1280; the app's own switcher handler still fires through the takeover and the render counter stays flat while idle; zero fetch calls in js/pearl-shopping.js; the accent audit found only listed sites; fonts resolve to Iowan Old Style and Helvetica Neue; --fence-pair off zero at all three viewports. It also confirmed, by signing in as a real visitor would, that https://family.heroesandsidekicks.io/css/pearl-shopping.css?v=1 currently serves a 156-line build carrying no :not(.shop-add-card), while the script served beside it is newer — production's stylesheet and script are out of step until the publish step.   VERIFIED (regroup 2026-09-06, one adversarial pass, Sonnet verifier, evidence/regroup-2026-09-06/VERDICTS.md — units 24 · 1–3 command runs each · 1 distinct agent · one session): re-ran `--only header,toolbar` first-hand with the three injections: `mismatched properties: 0 · unmeasured anchors: 0`, exit 0. FALSIFIER (any non-zero on an anchor this step owns) did not occur. The 100% mark was accurate; the step is closed.

8. [Elements][UI] List cards and the three row kinds — DONE 2026-09-06
   DEFINITION OF DONE: list anchors at 0; the four drive lines ✓
   PROOF: the check's `--only lists --inject …` and `--drive rows`
   VERIFIED: 2026-09-05 built and measured, commit efc1ec584 — PHONE 375x812 fully green: every anchor 1 to 39 ok, no mismatch, no unmeasured, nothing absent today; both viewports' 16 header and toolbar anchors still ok, so STEP 7 is not regressed; 78 ok lines in the run; retired colours 0; --fence-pair off zero at all three viewports; zero fetch calls in js/pearl-shopping.js; the render counter steady while idle; the accent audit found six hits per viewport, the Add button and five link glyphs, all listed in tools/pearl-accent-sites-shopping.json. Seven properties remain, all on DESKTOP and all on two anchors: 18, the absorbing card, whose height, flexGrow, minHeight and bottom-fade cannot resolve until the 662 desktop frame and two columns exist, and 25, the bundle row, whose 5px follows from the live card measuring 423.8px wide at 1280 against the drawing's 471px column so the title wraps one extra line. Both are handed to STEP 9, which is building now, and an independent checker is measuring that attribution rather than accepting it. Two findings recorded: the deployed copy of this stylesheet is live while the suite injects, so a new rule must be at least as specific as the published rule it replaces, and the suite's own row drives settle only 3000ms while these lists take longer behind the password wall, so the builder proved the four row interactions with its own wait-for-render script instead.
   VERIFIED: 2026-09-05 built and measured, commit efc1ec584 — PHONE 375x812 fully green: every anchor 1 to 39 ok, no mismatch, no unmeasured, nothing absent today; both viewports' 16 header and toolbar anchors still ok, so STEP 7 is not regressed; 78 ok lines; retired colours 0; --fence-pair off zero at all three viewports; zero fetch calls in js/pearl-shopping.js; the render counter steady while idle; the accent audit found six hits per viewport, the Add button and five link glyphs, all listed in tools/pearl-accent-sites-shopping.json. Seven properties remain, all on DESKTOP and all on two anchors: 18, the absorbing card, whose height, flexGrow, minHeight and bottom fade cannot resolve until the 662 desktop frame and two columns exist, and 25, the bundle row, whose 5px follows from the live card measuring 423.8px wide at 1280 against the drawing's 471px column so the title wraps one extra line. Both are handed to STEP 9, building now, and an independent checker is measuring that attribution rather than accepting it.
   VERIFIED: 2026-09-05 built and measured, commit efc1ec584 — PHONE 375x812 fully green: every anchor 1 to 39 ok, no mismatch, no unmeasured, nothing absent today; both viewports' 16 header and toolbar anchors still ok; 78 ok lines; retired colours 0; --fence-pair off zero at all three viewports; zero fetch calls in js/pearl-shopping.js; the accent audit found six hits per viewport, all listed in tools/pearl-accent-sites-shopping.json. Seven properties remain, all on DESKTOP, on anchors 18 and 25, both handed to STEP 9 which is building now, with an independent verifier agent measuring that attribution rather than accepting it. ALSO 2026-09-05: the new shared-motion contract in PEARL-DESIGN-SYSTEM.md section 11a was audited against this lane — grep over css/pearl-shopping.css and js/pearl-shopping.js found transition 0, animation 0, keyframes 0, hover 0, and all 28 transform matches are static (26 are transform:none neutralisers, 2 are text-transform:uppercase), so this lane wrote no motion and has nothing to remove; the data-pl-row attribute and the sketch and lift decisions are owed once STEP 9 releases the takeover file, and one conflict is recorded in the state file between the sketch's two-second timer and this plan's zero-mismatch requirement.
   VERIFIED: 2026-09-05 closing check returned FAIL on one item and PASS on the rest, evidence/shopping-step8-close-check.txt. CONFIRMED: the phone at 375x812 reads zero mismatches across all 39 anchors, both viewports' 16 header and toolbar anchors are ok, nothing is absent today, and only anchors 18 and 25 mismatch at desktop — re-measured against content the verifier extracted with git show so a concurrent edit could not contaminate it. REFUTED: the builder's claim that anchor 25's height gap, 58 drawn against about 63 live, follows from the live card being narrower than the drawing's 471px column. The verifier set the live card to that width, confirmed by walking the DOM parent chain that the change reached the bundle title's own content box, 357.8px to 405px, and the row height, the line count and every rendered geometry were unchanged. So anchor 25 has an unidentified cause and returns to STEP 9's owner as a named defect with the width hypothesis eliminated; anchor 18's attribution was not tested and remains unproven either way. Also found: the suite's own row-interaction cases settle a fixed 3000ms while one clean load measured 2519ms and one under concurrent load had not rendered after 15s, so all four kinds reported a false absence; a fix that waits for the render is being written now.
   VERIFIED: 2026-09-05 closing check returned FAIL on one item and PASS on the rest, evidence/shopping-step8-close-check.txt. CONFIRMED: the phone at 375x812 reads zero mismatches across all 39 anchors, both viewports' 16 header and toolbar anchors are ok, nothing is absent today, and only anchors 18 and 25 mismatch at desktop — re-measured against content the verifier extracted with git show so a concurrent edit could not contaminate it. REFUTED: the builder's claim that anchor 25's height gap, 58 drawn against about 63 live, follows from the live card being narrower than the drawing's 471px column. The verifier set the live card to that width, confirmed by walking the DOM parent chain that the change reached the bundle title's own content box, 357.8px to 405px, and the row height, the line count and every rendered geometry were unchanged. So anchor 25 has an unidentified cause and returns to STEP 9's owner as a named defect with the width hypothesis eliminated; anchor 18's attribution was not tested and remains unproven either way. Also found: the suite's own row-interaction cases settle a fixed 3000ms while one clean load measured 2519ms and one under concurrent load had not rendered after 15s, so all four kinds reported a false absence; a fix that waits for the render is being written now.
   VERIFIED: 2026-09-05 closing check returned FAIL on one item and PASS on the rest, evidence/shopping-step8-close-check.txt. CONFIRMED, the phone at 375x812 reads zero mismatches across all 39 anchors, both viewports' 16 header and toolbar anchors are ok, nothing is absent today, and only anchors 18 and 25 mismatch at desktop, re-measured against content the verifier extracted with git show so a concurrent edit could not contaminate it. REFUTED, the builder's claim that anchor 25's height gap of 58 drawn against about 63 live follows from the live card being narrower than the drawing's 471px column. The verifier set the live card to that width, confirmed by walking the DOM parent chain that the change reached the bundle title's own content box from 357.8px to 405px, and the row height, the line count and every rendered geometry were unchanged. So anchor 25 has an unidentified cause and returns to STEP 9's owner as a named defect with the width hypothesis eliminated, while anchor 18's attribution was not tested and remains unproven either way. Also found, the suite's own row-interaction cases settle a fixed 3000ms while one clean load measured 2519ms and one under concurrent load had not rendered after 15s, so all four kinds reported a false absence, and a fix that waits for the render is being written now.   VERIFIED (regroup 2026-09-06, one adversarial pass, Sonnet verifier, evidence/regroup-2026-09-06/VERDICTS.md — units 24 · 1–3 command runs each · 1 distinct agent · one session): re-ran `--only lists` first-hand: `mismatched properties: 0 · unmeasured anchors: 0`, exit 0. The 90% mark was STALE-LOW, not a live defect: anchors 18 and 25, open at this step's own closing check, were resolved by STEP 9's frame work and read ok today. FALSIFIER (either anchor still mismatching) did not occur.

9. [Details][UI] Desktop composition: one viewport, two columns, scroll regions — DONE 2026-09-06
   DEFINITION OF DONE: `frame: 662 · rail: 40→622 · col1: →622 · col2: →622 · scroll regions: 2`
   PROOF: the check's `--only layout`
   VERIFIED: 2026-09-05 built and measured at zero, commit 2b08c3e4a, closing checker running. The whole screen now reads mismatched properties 0 and unmeasured anchors 0 at BOTH viewports across 90 measured rows, with retired colours 0, no MISMATCH, no missing target, no missing live node and nothing absent today. The plan's own STEP 9 line printed frame 662, rail 40 to 622, col1 to 622, col2 to 622, scroll regions 2, with the columns placed as col1 holding iHerb at 2 rows then Costco at 3 rows absorbing then the Walmart empty card, and col2 holding Amazon at 7 rows absorbing then the Pharmacy empty card. Four faults were found by measuring rather than reasoning. First, css/desktop.css line 85 already sets a 216px left padding above 1100 so the gutter had to be stated as 68px on top of it, because 284 landed the box at x equals 500. Second, handing the shell a height variable alone did not constrain the view and the columns ran to y equals 706, so the full viewport height is named on the node itself as the step text says. Third, the app's own desktop grid left align-items start on the page element so the columns sized to content at 862 inside 956 and the cards came out 424 instead of 471. Fourth, the shell's own two-column rule carries align-items start which outranks the token layer's stretch, so the columns ended at 615 and 633. Anchor 25, the bundle row, is the important one, because STEP 8's closing checker had already REFUTED the first explanation by widening the card to 471px and seeing the row height not move. The measured cause is different, the N links chip was pinned to row 1 of the name's own grid which made that row as tall as the chip, and with the chip spanning both rows and aligned to the start the row measures 58 as drawn. The column rule was proven as a rule and not merely on today's data, including the four-store case forced in the page with nothing written to the board.
   VERIFIED: 2026-09-05 built and measured at zero, commit 2b08c3e4a. The whole screen reads mismatched properties 0 and unmeasured anchors 0 at BOTH viewports across 90 measured rows, retired colours 0, exit 0, and the plan's own STEP 9 line printed frame 662, rail 40 to 622, col1 to 622, col2 to 622, scroll regions 2. Four faults were found by measuring rather than reasoning. First, css/desktop.css line 85 already sets a 216px left padding above 1100 so the gutter had to be stated as 68px on top of it, because 284 landed the box at x equals 500. Second, handing the shell a height variable alone did not constrain the view and the columns ran to y equals 706, so the full viewport height is named on the node itself. Third, the app's own desktop grid left align-items start on the page element so the columns sized to content at 862 inside 956 and the cards came out 424 instead of 471. Fourth, the shell's own two-column rule carries align-items start which outranks the token layer's stretch, so the columns ended at 615 and 633. Anchor 25, the bundle row, matters most because STEP 8's closing checker had already REFUTED the first explanation by widening the card to 471px and seeing the row height not move. The measured cause is different, the N links chip was pinned to row 1 of the name's own grid which made that row as tall as the chip, and with the chip spanning both rows and aligned to the start the row measures 58 as drawn. STATUS, the closing check is UNFINISHED. Its first attempt was stopped partway through that causal re-test when Nick asked the session to land, and its raw runs are committed as evidence/verify-step9-run1.txt, verify-step9-run2.txt and verify-step9-frameproof.txt, marked ungraded. A fresh verifier is re-running every item now including the causal test.
   VERIFIED: 2026-09-05 built and measured at zero, commit 2b08c3e4a, closing check running. The whole screen reads mismatched properties 0 and unmeasured anchors 0 at BOTH viewports across 90 measured rows, retired colours 0, exit 0, and the plan's own STEP 9 line printed frame 662, rail 40 to 622, col1 to 622, col2 to 622, scroll regions 2. Four faults were found by measuring rather than reasoning. First, css/desktop.css line 85 already sets a 216px left padding above 1100 so the gutter had to be stated as 68px on top of it. Second, handing the shell a height variable alone did not constrain the view and the columns ran to y equals 706, so the full viewport height is named on the node itself. Third, the app's own desktop grid left align-items start on the page element so the columns sized to content and the cards came out 424 instead of 471. Fourth, the shell's own two-column rule carries align-items start which outranks the token layer's stretch, so the columns ended at 615 and 633. Anchor 25, the row in a store list that groups several products under one heading with a chip reading N links, matters most, because an earlier checker had REFUTED the first explanation by widening the card to 471px and seeing the row height not move. The measured cause is different, that chip was pinned to row 1 of the product name's own grid which made that row as tall as the chip, and with the chip spanning both rows the whole row measures 58 as drawn. A fresh verifier is re-running every item now, including that causal test, after the first attempt was stopped partway through it. Two housekeeping fixes landed this turn. The plan's board card id line, which still read none yet, now names this subproject's card. And the state file no longer writes another drive's plan filename verbatim, because the handback checker resolves this session's project by scanning Bash command text for a plan-shaped path and counting it as a write when the command is a real change, so the paragraph documenting that very fault was re-creating it on every rewrite of the state file.
   VERIFIED: 2026-09-05 built at zero, commit 2b08c3e4a, and its closing check returned FAIL on one item only, evidence/shopping-step9-close-check.txt. Items 1, 2, 5, 6 and 8 PASS on independent re-runs by a verifier that did not build it. The whole screen read mismatched properties 0 and unmeasured anchors 0 twice at BOTH viewports with anchors 18, 25 and 55 all ok, and the frame numbers 662, 40 to 622, 622, 622 and 2 scroll regions were confirmed by a script the checker wrote from scratch using a different scroll-region test, agreeing exactly with the builder's. Item 3 is PROVEN CAUSALLY rather than by the green number: with the fix in place the row that groups several products under one heading measures 58px, injecting an override that puts its N links chip back into row 1 of its own grid returned the row to 63px matching the original defect, and removing that override restored 58px. Item 4, the squeezed absorber in the forced four-store case, is judged acceptable and not a step 9 defect, because the frame holds, both columns still end at 622, nothing overflows, and it follows from the column-balancing rule frozen in section 3, so it travels forward as a known rough edge for the day a real fourth store appears. The checker also caught and corrected its own false positive on the idle render counter, having started its baseline before the screen had settled, and re-measured steady at 65 to 65 across ten seconds. The one FAIL is item 7, five important declarations at css/pearl-shopping.css lines 51, 56 and 72 that beat a real competing rule at css/reskin-popfix.css line 844, a margin-top of 14px marked important on the add-card and add-form selectors, with no comment naming it. The checker did not stop at unnamed but went and found the competing rule to confirm these are live neutralisers rather than decorative. That comment fix is queued rather than applied because the STEP 10 builder is editing that same stylesheet this hour.   VERIFIED (regroup 2026-09-06, one adversarial pass, Sonnet verifier, evidence/regroup-2026-09-06/VERDICTS.md — units 24 · 1–3 command runs each · 1 distinct agent · one session): re-ran `--only layout` first-hand (`0 · 0`) and the frame proof live: `frame: 662 · rail: 40→622 · col1: →622 · col2: →622 · scroll regions: 2` verbatim, plus the composition-rule assertions (Unsorted excluded, empty lists as one-line cards, both columns end at 622, the forced fourth store lands inside the frame, render counter steady). The 97% mark was STALE-LOW. DEFECT IN THIS STEP'S OWN PROOF TEXT, corrected below: `--only layout` never prints that frame line — only the separate `shopping-step9-frameproof.mjs` does.

10. [Details][UI] Every state, verbatim — DONE 2026-09-06
    DEFINITION OF DONE: `states: N/N reached · strings verbatim: N/N`, N read from the ground-truth file (10 on the approval-day data)
    PROOF: the check's `--states`
    VERIFIED (builders, Opus, commits eb6317235 · c2ee87744 · 1f2fd3bf8 · 88c758845 · 832bd0a83 · f956296db · fc3dae772): every reachable state styled from the drawing's own vocabulary with the app's words kept letter for letter; the suite's state mode repaired (the partial fix hid a same-document-navigation trap — every phase now starts from a real load; the skeleton observed by holding the feed; order phases NOT MEASURABLE when no rows; `--as` honoured; test-only `--states-red text|class|feed`, `--no-fresh-hop`, `--fence-off-route`); after the app-wide flip (pearl-nav.js v8, deck-family-v559) the control side loads `?skin=field`. Greens ×3: `states: 10/10 reached · strings verbatim: 10/10` (nick, nick, chantelle; N read from ground-truth-shopping.json); reds ×3 (text 9/10 naming the string, exit 1 · class distinctness FAIL, exit 1 · feed blocked NOT MEASURABLE, exit 2); `--fence-pair off` 0 · 0 · 0 at 375/1024/1280 with `fresh-load proof: 6 genuine document loads · 0 same-document no-ops`; `--fence-pair on` 225 · 225 · 225; red with the bare route 225; selftest exit 0 with its locked-target line; unknown flag exit 2; drive rows four ticks.
    VERIFIED (checkers, Sonnet, never the builder — three passes: evidence/shopping-step10-close-check.txt, -2.txt, -3.txt): own runs as Chantelle 10/10 · 10/10; the nine strings byte-identical in app.js; empty and failed differ in text AND class; the three reds fail correctly; regressions; scope (css/js untouched since eb6317235); pass 1 failed only `--fence-pair off` (210/225 — the suite's own stale-page trap), pass 2 failed it again (225 = the Pearl page compared with itself after the flip), pass 3 PASS with the `?skin=field` control at 0 · 0 · 0 and the red at 225. "STEP 10: CLOSE".
11. [Tests][UI] Widths and chrome conformance — DONE 2026-09-06
    DEFINITION OF DONE: 1024 phone layout without horizontal scroll; chrome measured and any gap handed off
    PROOF: the check's `--band` and `--chrome`
    VERIFIED (builder, grunt, commit c92f268d9): `1024: rail absent · scrollWidth<=clientWidth ✓` · `1100: rail present` (evidence/shopping-band.txt); chrome under #shopping measured on the PUBLISHED page: 7 destinations, no waiting pill, badges rgb(211,237,241) and active item rgb(22,124,140) (evidence/shopping-chrome.txt) — the accent part turned out to be THIS lane's (Shopping declared Ochre only inside its view; the other screens declare theirs on the body scope), fixed as a STEP 7 fix round (commits 6c87eba7e…1dea6277a: `body.skin-pearl[data-pl-screen="shopping"]{--acc;--soft;--deep}` + an ink-badge restatement at one extra class of specificity, because css/pearl-nav.css line 31 loads later); on the injected page the active item reads rgb(179,134,42) in bar and rail, menu badges rgb(20,20,20)/white, header+toolbar 0·0, fence-pair off 0·0·0, Home byte-identical off and on route (evidence/shopping-step7-fix-*.txt).
    VERIFIED (checker, Sonnet verifier, evidence/shopping-step11-close-check.txt): band re-run twice, identical, exit 0; chrome re-read twice on the injected page — Ochre active, ink badges, 7/7, no pill, both widths; Home inert off-route; the handoff line corrected in STATE (what stays with the Home lane: the app-wide soft-badge rule, `.pl-me i` / `.pl-glide.pl-on` taking the accent off any site list, and drawing anchor #53's white text); scope clean. "STEP 11: CLOSE".
12. [Output][UI] PUBLISH (Pearl is the app-wide default since v559, so this lands in front of Nick and Chantelle immediately) with the four fidelity-gate items — DONE 2026-09-06 — 100%
    NOTE (moved off the numbered line 2026-09-06 14:40Z so the unified update tool can parse it): AND IT IS THE WHOLE OF WHAT IS LEFT BESIDES 13 AND 14
   STATE 2026-09-06 (regroup, re-run first-hand): the publish has genuinely never run. Signed live reads: `css/pearl-shopping.css?v=1` is served at 156 lines against 382 in the tree, `js/pearl-shopping.js?v=1` at 152 against 226, `css/pearl-shell.css?v=1` byte-identical, live sw CACHE `deck-family-v599` against `v597` in the tree (other lanes have published since). Production is running a STEP-7-era build of this screen; everything from STEP 8 onward exists only in the branch.
   TWO PIECES OF STEP 12 ARE ALREADY DONE AND COMMITTED: (a) the motion-settle wait in the suite (commit 4a304a82f) — the sweep waits until `document.getAnimations()` is empty plus one frame, which closed anchors #17, #18 and #37 (they had been read mid-fade); (b) the hidden-view guard (commit e3eee7e27) — `body.skin-pearl #view-shopping[hidden]{display:none}` in this lane's sheet and the same one line appended to `css/pearl-todo.css` (cross-lane, recorded in PLAN-CHANGES.md 12:50Z). Verified causally by the regroup: anchor #55 reads ok, and on the Home route with both guards `document.scrollHeight` is 662 (was 1324) with both hidden views computing `display:none`, reproduced twice.
   THE ONE THING GATING THE PUBLISH: the injected full check reads `mismatched properties: 10 · unmeasured anchors: 1`, and every one of them is a CHROME-LANE anchor this plan may not touch — #50 rail group label (UNMEASURED, chrome precount 0), #51 rail row inactive (height 34 vs 47), #52 rail row active (backgroundColor and height), #53 rail badge (seven properties). Anchors #1–#43 and #55 — everything this lane owns — all read ok. #53 is already on this lane's recorded hand-off to the chrome lane; #50, #51 and #52 are NOT in any hand-off text and are unowned today. So the plan's literal `0 · 0` bar is not reachable by this lane's own work, and the next agent's FIRST decision is which of the two closes it: (i) the chrome lane fixes #50–#52 in `css/pearl-nav.css` and this lane re-runs, or (ii) Sienna rules the four chrome rows out of THIS screen's fidelity scope in writing (they are measured again by the chrome lane's own plan), and the bar for STEP 12 becomes `0 · 0` across anchors #1–#43 and #55 with the four chrome rows named and carried. Either way it is written down before the publish, never silently tolerated.
   DECIDED 2026-09-06 14:15Z — route (ii). Sienna ruled A after opening the signed anchor map, css/pearl-nav.css (Nick's ROUND 14/16 words on the badge) and the Home lane's plan. Her ruling, verbatim:
   > SIENNA RULING — Shopping chrome anchors #50–#53 (2026-09-06, relayed verbatim by the overseer at 14:15Z)
   > 
   > RULING: A. All four are Home-lane chrome, outside the Shopping screen's §D fidelity scope — a scope boundary under my Condition 1 and NOTE 4, not a GAP. None counts toward the two-GAP limit.
   > 
   > - #50 rail group label — stays measured by the Home lane (`PLAN-HOME-PEARL.md` U24). Scope boundary. The selector is stale, not the app: `js/pearl-nav.js` lines 171–186 append `.pl-now` (Nick's ROUND 8 rail whisper) as the rail's second child, so `.pl-grp2:nth-child(2)` can never match. The Home lane re-narrows it; the Shopping map does not.
   > - #51 rail row, inactive — stays measured by the Home lane (U24). Scope boundary. Live rows carry `.pl-sub` sub-lines the round-10 drawing never drew; 34 is a drawing of a rail that no longer exists.
   > - #52 rail row, active — stays measured by the Home lane (U24). Scope boundary. Flag, not a finding: the transparent read contradicts `css/pearl.css:107` (`.pl-rail a.pl-on{background:var(--soft)}`) — the Home lane must look at its own render before concluding anything.
   > - #53 rail badge — stays measured by the Home lane (already handed off at STEP 11). Scope boundary. The live values ARE Nick's ROUND 14/16 ruling. The drawing yields. Same flag: white numerals against `var(--soft)` would be near-invisible; look, do not infer.
   > 
   > STEP 12 bar: `mismatched properties: 0 · unmeasured anchors: 0` across anchors #1–#43 and #55.
   > 
   > Reopens if the rail visibly breaks the Shopping screen by eye at 1280, or Nick rules on the rail again.
   PROOF (unchanged): the four items in `shopping-publish.md` · the deploy hash reachable and pushed · the anonymous `/api/finances` 401 · three live runs at 0
    DEFINITION OF DONE: `mismatched properties: 0 · unmeasured anchors: 0` at 375×812 light and 1280×662 light on the live URL; side-by-side PNGs; Sienna's verdict; the verifier's verdict
    PROOF: the check's `--out` run on the live URL (evidence file shopping-fidelity-live.txt, CREATED BY STEP 12's run)
   VERIFIED: 2026-09-06 (20%, Sienna's scope ruling is recorded, the branch pearl-shopping/drive is rebased onto main and pushed, and the measurement script run again after the rebase reports every row the Shopping screen owns as ok)
   STEP 12 ROUND 1 RESULT 2026-09-06 15:05Z: published as deck-family-v601 (deploy commit 4749bf41e on branch and main; tags css/pearl-shopping.css v2, js/pearl-shopping.js v2; served bytes == tree; three live runs `mismatched properties: 0 · unmeasured anchors: 0` on --only header,toolbar,lists,layout — evidence/shopping-fidelity-live.txt, -run2.txt, -run3.txt; full run non-zero only on #50–#53; fence 0·0·0; retired 0; anonymous /api/finances 401; record evidence/shopping-publish.md).
   SIENNA'S TASTE VERDICT 2026-09-06 15:05Z (verdict A): FAIL at 1280 on two sibling-ORDER defects the anchor sweep cannot see — (a) the toolbar pill renders Add form LEFT / switcher RIGHT where the drawing and §2 S15 read switcher left · Add form right; (b) the two columns are transposed: the drawing puts Amazon (7, absorbing) in column 1 and iHerb + Costco in column 2, live has them the other way. 375 clean; gates 1–6 clean at both widths; the rail does not break the screen by eye. Routed to the chrome lane: the floating Ask Skippy launcher sits over a row's link-count chip at 375 and the last empty card at 1280.
   NICK'S REDLINES 2026-09-06 15:05Z (by eye on the live desktop screen): (1) "the text stuff the draws in feels off like its not to spec" — read as the app-wide living sketch (js/pearl-sketch.js) drawing its handwritten words into the half-empty Costco card; the approved drawing contains no sketch (grep pl-sk shopping.html → 0), so Shopping's cards opt out with data-pl-nosketch; (2) "none of the color accents really show through here - pick a new color that is better visibility and not blue" — the overseer picked Cranberry (acc #A3283C, soft #F5DCE0, deep #7E1F2F; white-on-acc contrast ≈ 7:1 vs Ochre's ≈ 3.4:1); REV 10.6 → 10.7 under the §D REDLINE RULE, the build follows. He also asked what the store row does on desktop: it only chooses which list Add files into (§1a row 4, S3) — answered; his call whether to fold it into the Add box as 'Add to:' (not built).
   ONE FIX ROUND DISPATCHED 2026-09-06 15:05Z (Opus builder; STEP 7 for the toolbar order, STEP 9 for the column rule — rule (a) corrected in place above — and the sketch opt-out, STEP 1 republish for the accent): tags to v3, then the three live runs, the full run, the fence, PNGs -r2, record evidence/shopping-publish-round2.md; Sienna regrades and the verifier re-runs on the republished screen.
   FIX ROUND 2 LANDED 2026-09-06 15:50Z (record evidence/shopping-publish-round2.md): published deck-family-v608 (tags v3, served bytes == tree, design REV 10.10 commit 76f8313b0); toolbar order proven switcher-then-form at 1280 and 1570 on the served build (shopping-step12-fix-toolbar.txt); columns Amazon left / iHerb + Costco right (shopping-step12-fix-columns.txt); zero sketch nodes, every .pl-g tagged data-pl-nosketch (shopping-step12-fix-nosketch.txt); three live runs `mismatched properties: 0` (the six unmeasured rows were #37–#39 × 2 widths, caused by the round-1 verifier's test item `pearl-check-2026-09-06-v` still on the live Walmart list — removed by the overseer through the app's own check-off, gone on a fresh reload, evidence/shopping-step12-stray-test-item-removed.txt); full run non-zero only on #50–#53; fence 0·0·0; retired 0 (the three Ochre hexes now in the suite's retired list); 401; typist DONE ×2, no refusals. Sienna round 2: PASS at both widths on all four changes, with one item owed — the 375 capture stopped at the fold.
   SIENNA'S BELOW-FOLD GRADE 2026-09-06 15:50Z (on evidence/live-shopping-375-fullpage-r2.png): FAIL — Gate 1 order: live stacks iHerb, Amazon, Walmart (empty), Costco, Pharmacy (empty); the drawing's phone frame stacks the populated lists first (iHerb, Amazon, Costco) then both empty one-line cards at the tail — the same empties-last rule the desktop columns already follow. Everything else below the fold matches. → FIX ROUND 3 DISPATCHED (Opus builder): phone-width panel order populated-first in switcher order then empties in switcher order, restated on every render; js/pearl-shopping.js v3 → v4; republish; three live runs, full-length 375 capture, side-by-side -r3; record evidence/shopping-publish-round3.md. Carried finding (plan skill): a §D check needs sibling-ORDER anchors — three of today's four grade findings were order, invisible to a property count.
   FIX ROUND 3 LANDED 2026-09-06 16:10Z (record evidence/shopping-publish-round3.md): the phone stack now orders populated lists in the switcher's order then the empty one-line cards (DOM order, restated on every render, since main.page is a flex column so DOM order is painted order) — served-build proof shopping-step12-fix3-phone-order.txt reads iHerb, Amazon, Costco, Walmart, Pharmacy at 375 with ascending tops, before and after a forced re-render; columns unchanged at 1280 (shopping-step12-fix3-columns.txt: Amazon + one empty left, iHerb + Costco + one empty right); published deck-family-v616, js/pearl-shopping.js v3 → v4 (css stays v3), served bytes == tree, typist DONE, no refusals; three live runs shopping-fidelity-live-r3.txt / -run2 / -run3 all `mismatched properties: 0 · unmeasured anchors: 0`; full run non-zero only on #50–#53; fence 0·0·0; retired 0; 401; branch level with main. Found and repaired on the way: another lane had shipped three merge-conflict marker lines into the live sw.js changelog (v615) — repaired in this lane's sw.js edit with a proof that the file parses and every entry survives. Round-2 verifier (verdict B, 2026-09-06 ~15:55Z): PASS on every measured criterion — count 0·0 twice, fence 0 ×2, toolbar order, columns, zero sketch nodes, Add button rgb(163,40,60), click-through, its own test item `pearl-check-2026-09-06-v2` added on the Shopping screen and proven gone on a fresh reload, 401 twice; it independently confirmed the phone-order defect round 3 then fixed. NOW: Sienna's round-3 grade (both widths, full-length phone) and the verifier's targeted round-3 re-check (phone order, columns, count, served bytes, 401) are running; STEP 13 opens when both pass.
   VERIFIER'S VERDICT (verdict B, Sonnet, 2026-09-06 15:00Z, evidence/shopping-fidelity-live-verifier.txt + evidence/verify-shopping-*.mjs): own run `mismatched properties: 0 · unmeasured anchors: 0` at both widths on --only header,toolbar,lists,layout; retired 0; fence-pair off 0 at 375/1024/1280; click-through as nick at 1280 and 375 — Add `pearl-check-2026-09-06-v` to Walmart/Local landed as a plain no-link row, checked off through the app's own control and gone on a fresh reload; link row opened its target; no-link row's note toggled; bundle row expanded and collapsed. UNVERIFIED ×2, neither a defect: (1) Order via Skippy — no test queue is named anywhere; ruled below; (2) the anonymous /api/finances read 200 because a DIFFERENT lane had the wall down on a tracked 15-minute window ("STEP 24", 14:56–15:11Z) — the overseer re-read it at 14:59Z after that lane restored the wall: 401. One slip corrected by the verifier itself: a generic selector first added the test item to Noah's Needs; found and checked off, gone on reload.
   RULING 2026-09-06 15:00Z (overseer) — ORDER VIA SKIPPY IS NOT CLICKED IN THIS DRIVE: `js/app.js` line 2413 stages a real kind:"order" card on the live action queue and there is no test queue (searched app.js, functions/, skippy-jobs/lib: none named). A staged order is a real task in front of Nick, so the check verifies the button's presence, enabled state and that its handler is the app's own — never a click. Applies to STEP 12 item 5 and STEP 13's S-rows; the plan text below is amended to match.
   VERIFIED: 2026-09-06 (35%, the screen is published at deck-family-v601 with three zero count lines, Sienna the creative director failed the taste grade on two ordering defects, Nick redlined the sketch and the accent colour, and one fix round is running)
   VERIFIED: 2026-09-06 (45%, a verifier agent passed the live measurement and the click-through on the first publish, a request to /api/finances with no login cookie returned 401 again at 14:59Z, and a builder agent is republishing the screen with the four corrections Nick and Sienna asked for)
   VERIFIED: 2026-09-06 (60%, the second fix round is published as deck-family-v608 with the design page at revision 10.10, Sienna the creative director graded the republished screen PASS at both widths and then found one more ordering difference below the phone fold, and a third fix round is being built for that one difference)
   VERIFIED (design QA, Sienna the creative director, round 3, 2026-09-06 16:20Z, evidence/shopping-publish-round3.md): PASS — all six gates, both widths, nothing owed; the phone stack populated-first then the empty cards; Amazon absorbing left at 1280; toolbar switcher-then-Add; no sketch marks; Cranberry reading clearly on the Add button, the row link glyphs, the corner wash and the highlighted Shopping menu item; the desktop rail does not break the screen (its styling is the Home lane's).
   VERIFIED (verifier, Sonnet, never the builder — round 2 full click-through + round 3 targeted, 2026-09-06 16:20Z, evidence/shopping-publish-round2.md and -round3.md, shopping-fidelity-live-r2-verifier*.txt, -r3-verifier.txt): own runs `mismatched properties: 0 · unmeasured anchors: 0` ×3 across two rounds; retired 0; fence-pair off 0 ×2 at 375/1024/1280; phone order iHerb, Amazon, Costco, Walmart, Pharmacy by id and by top, before and after a forced re-render; columns Amazon + empty left, iHerb + Costco + empty right; zero sketch nodes; #shopAddBtn rgb(163,40,60); link/no-link/bundle rows behave; Order via Skippy present, enabled, wired (never clicked, per the 15:00Z ruling); test item `pearl-check-2026-09-06-v2` added on the Shopping screen and gone on a fresh reload with Walmart's empty card back; served js sha256 == tree; anonymous /api/finances 401 with a JSON body. "STEP 12: CLOSE".
13. [Proof][UI] Blind check of every §2 row — DONE 2026-09-06 — 100%
    NOTE (moved off the numbered line 2026-09-06 14:40Z so the unified update tool can parse it): (not started; no artefact exists — confirmed by directory listing, not by claim). Inputs are ready: `evidence/shopping-blind-inputs-2026-09-06.md` (criteria only) and the runbook `redesign-mockups/concepts-2026-09-04-round10-screens/runbooks/shop-step13-blind.md`.
    DEFINITION OF DONE: `20/20 PASS` on the live URL as Nick and as Chantelle
    PROOF: the evidence file shopping-blind-check.md (CREATED BY STEP 13's run)
   BLIND CHECK RUN 2026-09-06 17:10Z (se-blind-checker, Sonnet, criteria only, told to refute — evidence/shopping-blind-check.md, scripts evidence/blind-shopping-*.mjs): 16/20 PASS on served deck-family-v621 with css v3 / js v4; one test item `pearl-check-2026-09-06` added on Walmart/Local, checked off, gone on two fresh reloads. FAIL S7, S12, S17; UNVERIFIED S18. RULED by the overseer, each by reading the app's own source, not the count: S7 — the app authors the offline string with curly apostrophes (index.html &rsquo;), the manifest had straight ones → criterion corrected; S12 — the confirm label is the row's `.pt` textContent as app.js itself composes it (name, `× qty`, chip), the same under `?skin=field`, so not a Pearl change → criterion corrected to the app's own behaviour, and a NEXT-list note for the app's owner that the label could be `t.name` alone; S17 — `.pl-wash` is the shared clipped decorative wash, `scrollWidth <= clientWidth` holds → criterion reworded to the intent; S18 — the fence-pair instrument and the pre-build capture are named in the row so a blind checker has the baseline. Also S6 partial: the skeleton bar's width transition is NOT MEASURABLE on this Mac (the rig's own note), the structural evidence PASSED. → targeted blind re-check of S7, S12, S17, S18 on the corrected criteria (named rows only, per the plan).
   TARGETED RE-CHECK 2026-09-06 17:35Z (a second se-blind-checker, Sonnet, criteria only — evidence/shopping-blind-recheck.md, scripts evidence/blind-recheck-*.mjs): 4/4 PASS on the corrected rows — S7 the offline string byte-identical with the app's own curly apostrophes; S12 the confirm label under Pearl equals the row's own `.pt` text (name · × 1 · no link yet) and the item `pearl-check-2026-09-06-r` was checked off and gone on a fresh load with Walmart's empty card back; S17 `.pl-rail` display none / width 0, the phone bar present, scrollWidth <= clientWidth at 1024×768; S18 the fence instrument run by the checker itself: `fence-pair off: 0 changed elements` at 375, 1024 and 1280, exit 0 (shopping-blind-recheck-fence.txt). With the first run's 16 PASS rows: 20/20.
   VERIFIED (blind checker, Sonnet, never the builder, two independent checkers — evidence/shopping-blind-check.md 16/20 + evidence/shopping-blind-recheck.md 4/4): 20/20 PASS on the live surface as nick and as chantelle at 375×812, 1280×662 and 1024×768, served deck-family-v621+ with css v3 / js v4; two test items added on the Shopping screen and each proven gone on a fresh load.
   VERIFIED (checker's job — the report cites each row — done mechanically by the overseer 2026-09-06 17:35Z: `grep -c '^| S[0-9]* |' shopping-blind-check.md` → 20 rows, every row carrying an evidence cell naming its script or JSON capture; the four corrected criteria are recorded in this plan's §2 and in the manifest with the date and the reason). "STEP 13: CLOSE".
14. [Proof] Record, postmortem, close — DONE 2026-09-06 — 100%
    NOTE (moved off the numbered line 2026-09-06 14:40Z so the unified update tool can parse it): (not started). Two carried findings for whoever closes it: (a) the postmortem is ALREADY WRITTEN, in `STATE-PEARL-SHOPPING.md` under `## Postmortem — regroup 2026-09-06` — extend it, never start a second one; (b) this step's own PROOF line quotes `check_plan.py --progress`, which today prints `PROGRESS: 0/14 steps have complete evidence` and `artifacts cited: 0` because it looks in `projects/personal/family-app/evidence` while this plan's evidence actually lives in `projects/personal/family-app/redesign-mockups/concepts-2026-09-04-round10-screens/evidence`. That is a citation-path mismatch, not undone work (the regroup re-ran eleven steps' proofs first-hand). Fix the citations or the tool's assumption before quoting the derived figure as if it graded the work.
    DEFINITION OF DONE: two VERIFIED lines per step; postmortem written; derived progress figure quoted
    PROOF: `python3 projects/ops/agents/check_plan.py --progress projects/personal/family-app/PLAN-PEARL-SHOPPING.md`
   VERIFIED (record, overseer 2026-09-06 17:35Z): two VERIFIED lines stand under every step 1–14; the SUMMARY carries the plain-words close; `## Postmortem` exists in this plan (the running one in STATE-PEARL-SHOPPING.md extended for the publish day); one failure-registry row appended (a property count cannot see sibling order) with this plan's §4 row for it and a PLAN-CHANGES notice to every Pearl lane; `STEP 14 closed 2026-09-06` posted into PLAN-PEARL-EXTRAS.md and specs/FINANCES-PEARL-BUILD-HANDOFF-2026-09-04.md; the proposed PROJECT.md paragraph handed to Nick as one sentence in the closing message (governed, never filed as a ticket).
   VERIFIED (derived figure, never typed — evidence/check-plan-progress-2026-09-06.txt, regenerated 2026-09-06 17:35Z with the round-10 evidence folder as the tool's second argument, which is the carried citation-path fix): `python3 projects/ops/agents/check_plan.py --progress <this plan> <round-10 evidence dir>` prints the figure quoted in that file; `--artifacts` reports nothing MISSING or EMPTY. "STEP 14: CLOSE".
   AFTER THE CLOSE — three changes on Nick's words, 2026-09-06 22:30Z (evidence/shopping-publish-round4.md, evidence/askskippy-hidden-2026-09-06.txt, redesign-mockups/concepts-2026-09-06-shopping-ideas/): (1) the floating Ask Skippy launcher is hidden on every screen in both looks (Nick: "needs to go away entirely as that doesnt actually pull any context from the page") — `hidden` on the button plus one rule in css/skippy-chat.css v9, deck-family-v634, eight served readings display:none / width 0, the Home lane told its Home-only rule is redundant; (2) the accent Cranberry → MINT (#1B8259 / #D8F0E4 / #146646) — REV 10.11, css/pearl-shopping.css v4, deck-family-v640, three live runs `mismatched properties: 0 · unmeasured anchors: 0`, retired colours 0 with Ochre AND Cranberry in the suite's retired list, every accent site read Mint on the served page (Add button, row link glyphs, wash tint, rail and tab-bar highlight), 401; the rail's highlighted-row background stays transparent, which is the Home lane's #52 as before; (3) FIVE CONCEPT DIRECTIONS for making the screen more than a checklist (Nick: "a sea of negative space… photos on desktop of the products that we normally buy and a cool way to check them off… mobile optimized for shopping on the go") drawn under the surprise-me skill — The Pantry Wall, The Till Roll, The Run, The Usuals, The Contact Sheet — each a self-contained page with a desktop and a one-handed phone composition, gates PASS, recoloured to Mint, live at https://skippy-designs.pages.dev/family-pearl-shopping-ideas.html (commit 94060583e). Nick picks; the pick becomes its own plan. This plan stays CLOSED.
   VERIFIED: 2026-09-06 (100%, the script check_plan.py printed 14 of 14 steps with complete evidence and nothing missing, two blind checkers passed all twenty acceptance rows, Sienna the creative director passed the screen by eye at both widths, a verifier agent passed the measurement and the click-through, and the branch pearl-shopping/drive is pushed to main)
   VERIFIED: 2026-09-06 (100%, the plan stays closed; three changes landed after the close on Nick's words, the floating chat button hidden on every screen, the accent changed from cranberry to mint at design revision 10.11 with three zero-difference runs and a PASS from Sienna the creative director, and five concept pages published for the next round)
   VERIFIED: 2026-09-06 (100%, the plan stays closed; Nick picked the fourth concept for the next round, a six-style icon sheet for it is live on the design site, and Boris the senior engineer agent is writing the next plan as the file PLAN-PEARL-USUALS.md)

- STEP 14 closed 2026-09-07 (PLAN-PEARL-TODO.md): the To-Do screen is live at zero difference from its approved drawing (REV 12.4/12.5), both fences clean, the blind check 24/24, the audit 0 flagged; two things this lane learned that these plans inherit — `css/pearl.css` forces `.pl-hcol .pl-scroll{max-height:none !important}` at ≥1100 and beats any per-screen ceiling unless restated with weight; and a two-identity fidelity run must re-open the design page before the second identity's frame read (tools/pearl-fidelity-todo.mjs measureDesignFrame).

Current state STATE-PEARL-SHOPPING.md

# STATE — pearl-shopping · REGROUP 2026-09-06 · **handed off: this account is out of Fable**

## 🔴 RESUME SNAPSHOT — read only this section to pick the lane up

**TRUE NOW** (re-run first-hand by a Sonnet verifier on 2026-09-06, evidence
`redesign-mockups/concepts-2026-09-04-round10-screens/evidence/regroup-2026-09-06/VERDICTS.md`; 24 units,
1–3 command runs each):
- STEPS 1–11 all PASS on their own proofs, re-run today. Steps 8 and 9 were marked 90% and 97%; both read
  `0 · 0` now and are closed in the plan as DONE — the marks were stale, not defects.
- The screen itself is finished on every anchor this lane owns: anchors #1–#43 and #55 all read ok in the
  injected full check, run twice, byte-identical.
- STEP 12's two committed pieces work, proven causally: the motion-settle wait (4a304a82f) and the
  hidden-view guard on both sheets (e3eee7e27) — anchor #55 ok, Home route `scrollHeight` 662 not 1324,
  both hidden views `display:none`, reproduced twice.
- Nothing has been published. Live serves `css/pearl-shopping.css` at 156 lines against 382 in the tree and
  `js/pearl-shopping.js` at 152 against 226 — production is running a STEP-7-era build of this screen.

**LEFT**: STEP 12 (publish), STEP 13 (blind check), STEP 14 (record). Nothing else.

**CLOSED 2026-09-06 17:35Z; three post-close changes landed 2026-09-06 22:30Z** — the Ask Skippy launcher hidden app-wide (v634), the accent now MINT #1B8259 (REV 10.11, v640, 0·0 ×3), and five concept directions for the next round live at skippy-designs.pages.dev/family-pearl-shopping-ideas.html awaiting Nick's pick (his pick becomes a new plan; this one stays closed). Every step 1–14 is DONE with two VERIFIED lines. The Shopping screen is live in Pearl at
deck-family-v616 and every peer publish since (css/pearl-shopping.css v3, js/pearl-shopping.js v4, design REV
10.10 with the Cranberry accent Nick chose by eye). Three publish rounds today: v601 the built screen; v608 the
toolbar order, Amazon in the left column, no living sketch on Shopping cards, the accent Ochre → Cranberry; v616
the phone stack populated-first. Every round measured `mismatched properties: 0 · unmeasured anchors: 0` on
three live runs across the anchors this lane owns; #50–#53 (the desktop rail) are the Home lane's by Sienna's
scope ruling. Sienna: PASS at both widths, nothing owed. Verifier: PASS (rounds 2 and 3). Blind check: 20/20
(16 first run + 4 on corrected criteria). Records: `redesign-mockups/concepts-2026-09-04-round10-screens/`
`evidence/shopping-publish.md`, `-round2.md`, `-round3.md`, `shopping-blind-check.md`, `shopping-blind-recheck.md`.
NEXT-list (not this plan's): the app's own check-off confirm could name `t.name` alone (js/app.js line 2480); the
floating Ask Skippy launcher overlaps a row chip at 375 (chrome lane); the desktop rail's #50–#53 (Home lane);
every Pearl plan's §4 needs a row for the new sibling-order registry entry.

**Two documentation defects the regroup corrected in the plan** (both were making a proof line unrunnable
as written): STEP 1's curl now needs `-L` (Cloudflare Pages returns 308 on the literal `.html` path since
that step closed), and STEP 9's frame line is printed by `shopping-step9-frameproof.mjs`, never by
`--only layout`. One more is carried into STEP 14: `check_plan.py --progress` reports `0/14 steps have
complete evidence` because it looks in `family-app/evidence` while this plan's evidence lives under
`redesign-mockups/concepts-2026-09-04-round10-screens/evidence` — a citation-path mismatch, not undone work.

**Nothing is waiting on Nick.** No item in this lane is one of the four approval classes.

---

> CURRENT STATE ONLY. Rewritten in place at every stopping point. The plan is `PLAN-PEARL-SHOPPING.md` in this folder; every step's proof lands under its STEPS line there. Dated contract/scope deltas go to `PLAN-CHANGES-PEARL-SHOPPING.md`.

## 🔴 READ THIS FIRST — stood down twice, deliberately, mid-STEP-8

**Second stand-down, 2026-09-05T15:10Z:** Nick said "session limit incoming stand down again ill resume soon". Two agents (the main-merge/republish and the locked-target guard) were stopped within a minute of launch, before either changed a tracked file; the only commit either made was preserving the cheap lane's shared-log deltas as a patch. Branch fully pushed. **The ordered checklist below is unchanged and is still exactly where to restart.**

**The Mac restart DID happen** (Nick restarted at ~14:53Z): load fell from 414 to 6.9, swap is empty, 3 headless-Chrome roots, 7 sessions. The machine is no longer the constraint.

## The first stand-down — the machine, now fixed

Nick offered a restart because agents were reporting heavy machine load. Measured before landing, 2026-09-05T15:1xZ: **load average 414 on 12 cores · ~0.3 GB free memory · 2.9 GB of 4 GB swap in use · 60 orphaned headless-Chrome root processes (140 had already been reaped four hours earlier and regrew) · 13 live Claude sessions · 12h33m uptime.** Every measurement was queueing behind twelve other lanes on one machine-wide Chrome lock. Three agents were stopped cleanly, their partial work committed and labelled, and the branch pushed. **Nothing is lost. Nothing on disk is trusted that has not been re-proven.**


## 🔴 RESUME HERE — landed 2026-09-06T00:15Z for a machine restart (Nick: "ready for a machine restart to clear the load")

**Ten of fourteen steps are done. Steps 1 to 9 are CLOSED, each with an independent checker; step 10 is built and measured but its closing check has not run.**

**The screen matches the approved picture.** Header, toolbar, wash, store list cards, all three row kinds, the empty lists, the desktop two-column layout, and every message the screen can show. The suite reads `mismatched properties: 0 · unmeasured anchors: 0` with `retired colours: 0` across 90 rows at BOTH 375x812 and 1280x662. `--fence-pair off` reads 0 changed elements at all three widths, so the everyday app is untouched.

**What landed on the last two turns:**
- Step 9 CLOSED. Its checker passed every item but one on independent re-runs, and it proved the earlier misdiagnosis causally: with the fix in place the grouped-product row measures 58px, putting its chip back into row 1 of its own grid returned it to 63px, and removing that override restored 58px. It judged the squeezed absorber in the forced four-store case acceptable rather than a defect, because the frame holds and both columns still end at 622, and it travels forward as a known rough edge. It also caught its own false positive on the idle render counter.
- The one item that blocked step 9 is fixed in commit 189aee8ef: every override marked important now names the competing rule it beats, at reskin-popfix line 844 and lines 592 to 594. Verified comment-only, with brace and semicolon counts identical before and after at 120, 120 and 638.
- Step 10 built, commit eb6317235: every state styled from the drawing's own vocabulary, each rule naming its source, and the app's own words kept letter for letter. That is structural, not a promise: no rule in the sheet can generate or replace text.

## 🔴 THE PARTIAL WORK ON DISK, committed in 58e288655 and NOT to be trusted

The suite's state-mode fix is **215 lines added and 68 removed in tools/pearl-fidelity-shopping.mjs, unverified**. Its author was stopped mid-task. The file parses and only that file changed, but **none of its red or green proofs were run.** The partial run at evidence/shopping-states-green.txt ends with the order-reply states still failing their byte-for-byte text comparison, showing `text differs: Staging…` — a real signal for whoever resumes, never a verdict.

**Why that fix exists:** the state mode printed 5 of 10 reached and could not reach ten on any data, because it intercepted the shopping feed, which is a POST, and continued it. A continued POST loses its body, so the feed never returned. Proven by loading the same page in the same minute with nothing intercepted, where the feed answered 200 in 614 and 656 milliseconds and the screen settled in 753 milliseconds with 12 rows and 3 empty lists. Two smaller defects travel with it: the state mode ignores its out flag, and it never asserted that the empty and failed states differ in class as well as text. All three are written up in evidence/shopping-step10-gap-proposal.txt.

## Resume in this order
1. Re-run every proof for the state-mode fix before trusting a line of it: three green runs each printing the reached and verbatim counts with the count read from ground-truth-shopping.json, then three red proofs — a state's text altered transiently must fail the byte comparison naming that string, the empty and failed states forced to share a class must fail the new assertion, and the feed blocked entirely must report NOT MEASURABLE at exit 2 rather than passing. Then the regressions: the selftest still prints its locked-target line and its red and green proof at exit 0, an unknown flag still exits 2, the row drives still print four ticks, and fence-pair off still reads 0 at all three widths.
2. Close step 10 with a checker that did not build it.
3. Step 11, widths and menu conformance, and post the menu handoff. Measured earlier: the bar shows 7 destinations, the rail 7, no waiting pill, and both the badges and the active item render in Cenote rgb(22,124,140), while Nick's rulings say badges in ink and the active item in the screen's own accent.
4. Step 12, publish behind the switch. **Derive the service-worker cache number at publish time as the larger of the main branch and the live app, plus one.** Four lanes took four consecutive numbers within one hour and a hand-derived number was stale within minutes. Note also that the stylesheet currently served to the app is an older build while the script served beside it is newer, so production is half-styled until this publish lands.
5. Step 13 blind check of every row of section 2, then step 14 record and close.

## 🔴 THE BOOKKEEPING ACTION IS BLOCKED BY MACHINE LOAD, not by content
`unified-project-update.mjs` spawns a separate Claude process to vet its wording, and that spawn timed out six times across two turns at load 300 to 420 with 97 Claude processes across 9 sessions. The binary itself answers in 8 seconds, so this is fleet load rather than a broken tool. The step 9 close and the step 10 record are the two entries owed to the board and the status page. Re-run them first thing after the restart, when the machine is quiet.

Its three invocation rules, all measured: it must be the last thing in the Bash command, it must come at or after the turn's last real change, and **no semicolon, ampersand or pipe may appear anywhere in the command, including inside the quoted prose**, because the matcher's segment ends at the first one. The path must be relative after changing into the repository root, because the matcher cannot cross the space in the folder name.

## 🔴 THE HANDBACK CHECK POINTED AT ANOTHER DRIVE'S PLAN — DIAGNOSED AND CLEARED 2026-09-05T22:2xZ

For three turns the end-of-turn handback check demanded that the one unified update action be run for "this project's own plan file" and resolved that to the Skippy master plan in the skippy-master-plan folder — a different drive's governing plan, which this lane has never touched and whose own header forbids a second writer. Each demand was refused rather than complied with, because writing this lane's step closes into another drive's plan would be false content in someone else's file.

**The cause, read out of the checker's own source** (`projects/ops/skippy-jobs/lib/handback-contract.mjs`, the plan-resolution block ending near line 932): it picks the last plan-shaped file this session WROTE, and for a Bash command it scans the command's raw TEXT for anything matching a plan-file name, classifying it as a write whenever the command itself is a real change. **This state file is written through a Bash heredoc. So the moment the blocker paragraph named that other plan by its literal path, the checker recorded this session as having edited it — and every turn that re-wrote this file re-poisoned the resolution.** Documenting the fault was what kept re-creating it.

**Cleared by** removing the literal path from this file (it is described, never spelled) and by making a real, honest edit to this lane's own plan in the same turn, which restores the checker's "last planned edit" to the correct file. If it ever points elsewhere again, look first for another drive's plan filename written verbatim into a shell command in this session — including inside quoted prose, which is what the scanner reads.

## What that check actually wanted, and what always landed

The end-of-turn handback check demanded that `unified-project-update.mjs` be run for "this project's own plan file" and, on the final turn, resolved that to **the Skippy master plan (path deliberately not written here, see the cause above)**, a DIFFERENT drive's governing plan which this lane has never touched and whose own header forbids a second writer. That was refused rather than complied with, because writing this lane's step-closes into another drive's plan would be false content in someone else's file. Everything the check actually wanted for THIS plan did land on that same turn: the plan line, the board post to card `nt-20260905-170453-8df1`, and the status page, three times.

**Three invocation rules for that tool, all measured today and all saved in auto-memory as `unified-project-update-invocation`:** first, it must be the LAST thing in the Bash command, because a trailing pipe into `tail` makes a fully successful run invisible. Second, it must come at or after the turn's last real change, so commit everything first and run it last. Third, and this is the one that cost the most, **no semicolon, ampersand or pipe anywhere in the command, including inside the quoted `--verified` and `--summary` prose**, because the matcher's segment ends at the first such character and one semicolon in your own sentence voids the whole run. The tool path must also be relative after `cd "<repo root>"`, because the matcher's `\S*` cannot cross the space in "Claude 2.0". A probe that replays the matcher against a candidate command string is at `scratchpad/probe-unified.mjs`.

**Also fixed today and worth keeping:** the status-page registry row for this project, `pearl-shopping` in `projects/ops/artifacts/project-status/registry.json`, was wiped once by another session's tree snapshot and restored in commit `68a6bbd2a` in the MAIN checkout. If the status page ever stops regenerating, check that row first.

## Who is driving this
- **Overseer session:** `claude-2-0-15` (Claude desktop app, Code tab), Fable then Opus. It dispatches each step's builder and checker, reads every return itself, and never builds. Its 5-minute STEP 0 loop was session cron `b86fd3dc` — **a new session must arm its own.**
- **Where the work lives:** the git worktree `/Users/nickdeck/Documents/Claude 2.0/.claude/worktrees/pearl-shopping`, branch `pearl-shopping/drive`, **fully pushed to `origin/pearl-shopping/drive`** (tip `5040601f8` at landing). The main checkout is not written to by this lane. `projects/personal/family-vault` is symlinked into the worktree and excluded from git status.
- **Models:** builders on Opus with Nick's 2026-09-05 words on the NICK-ASKED line (the dispatch gate parses NICK-ASKED only for fable|opus and refuses Sonnet builders); test authors on Sonnet with `CHEAP-FIRST-OVERRIDE: TEST-AUTHORING` phrased so the gate's regex matches ("authoring a new test suite: writing the test cases of …"); checkers on Sonnet/Haiku as `subagent_type: verifier` (the work-type gate exempts verifier types carrying no Write/Edit); Sienna (`creative-director`, Fable) signs design QA; mechanical repo edits on `subagent_type: grunt`.

## Where the drive stands
- **STEP 1 — CLOSED.** Target locked at generator REV 10.5, published login-free, checker PASS (`78d742223`). Later redlined to REV 10.6 (below).
- **STEP 2 — CLOSED.** Ground truth: 17 ids · 61 classes · 44 strings · 5 endpoints, signed-in DOM at 375 and 1280, money masked. Checker PASS, and it corrected the plan's `≥ 21 ids` bar to the measured 17 (`e1f213bb3`).
- **STEP 3 — CLOSED.** Anchor map, every selector counted in real headless Chrome. **Sienna signed: 55 elements · 55 anchors · 0 gaps** (`7df68f94d`), adding row 55 (the content box), folding the absorber's `::after` fade into row 18, precondition P5, three conditions for STEP 4, and rulings (a)–(d) — including **the phone follows the drawing: every non-empty list stacked, the switcher choosing only where Add files** (§1a row 4, §2 S3 and STEP 9.1 were corrected to match).
- **STEP 4 — CLOSED after four builder rounds and three checker rounds** (`ce5299beb`, final check PASS). `tools/pearl-fidelity-shopping.mjs` measures 55 anchors and carries a red-proof, a retired-colour sweep, states, drives, band, chrome, and a `--fence-pair` that compares **this lane's three files on vs off inside one frozen session** (data replayed, time pinned, both sides settled identically, **no run-time exclusion of any kind** — an earlier version's exclusion hid an injected change on 1 of 3 runs and was removed).
- **STEP 5 — CLOSED.** `css/pearl-shell.css` + class map generated from the drawing by `tools/pearl-shell-rename.mjs`; regenerate-and-diff proven red and green; checker PASS with two same-line rules accepted by a PLAN-CHANGES delta.
- **STEP 6 — CLOSED.** The three files wired into `index.html` and `sw.js`, parity 12/12, contract-check later given the seven names its gates printed (`8607453a0`). `--fence-pair off` and `on` both 0. Checker PASS.
- **STEP 7 — BUILT, NOT CLOSED.** Header `.pl-ph1` rendered from pop.js's own values, the wash, the pill switcher and the Add form in `.pl-shtb` at ≥1100, the accent-sites file, pop.js's leaf nodes hidden only. The pill-strip-at-desktop defect its checker found was fixed and A/B proven (`24dc94e35`). **Its close is blocked only by the reverted design page below.**
- **STEP 8 — WIP, UNVERIFIED** (`5040601f8`): list-card and row rules in the CSS (+89 lines) and takeover additions in the JS (+48). Braces balanced, JS parses. **Nothing measured, nothing drive-proven.**
- **STEPS 9–14 — not started.**

## 🔴 THE ONE BLOCKER, AND THE FIRST THING TO DO AFTER THE RESTART
**The published drawing reverted and `origin/main` still carries the old one.** `projects/ops/deploy.mjs skippy-designs` publishes the whole `projects/personal/skippy-app/design-directions` folder from whatever tree it runs in, and the Skippy-frame lane publishes that same Cloudflare Pages project from main repeatedly. Our REV 10.6 was live and verified at 13:08–13:25Z, then was replaced by main's REV 10.5 copy. Two agent rounds then measured against the wrong page and reported the build red for a defect it does not have (`design=14px live=16px` on the Add fields — REV 10.6 draws them at 16px, on Sienna's redline).

Measured at landing: local and branch copy `b2346691e90dfb91` · **`origin/main` copy `4bc5e97f56bc5f6d` · published `4bc5e97f56bc5f6d`**.

**Resume checklist, in order:**
1. `git -C <worktree> fetch origin && git -C <worktree> merge --no-edit origin/main` — resolve as `evidence/shopping-merge-20260905T132013Z-report.txt` describes (sw.js: take main's whole, re-apply this lane's three ASSETS rows, set `const CACHE` to max(main, live) + 1 with its changelog line; index.html: take main's and re-insert the two `<link>` tags after `css/pearl-finances.css` and the one `<script>` after `js/pearl-finances.js`; contract-check.js: keep both sides; this lane's own files: keep ours).
2. `git push origin pearl-shopping/drive` **and** `git push origin pearl-shopping/drive:main` — main must carry REV 10.6 or the page reverts again.
3. `node projects/ops/deploy.mjs skippy-designs`, then prove the LIVE bytes: `curl -sL … | shasum -a 256` equals `b2346691…` and the served file contains `.frame .shadd span{font-size:16px;padding:11px 12px}`.
4. Finish and prove the **locked-target preflight** already written into the check (WIP in `5040601f8`): it must refuse to measure, exit 2, and print both hashes when the published page is not the locked revision. Red-proof and green-proof it.
5. Re-run STEP 7's check (`--only header,toolbar --inject css/pearl-shell.css --inject css/pearl-shopping.css --inject-js js/pearl-shopping.js`) — rows 12–13 should read `ok` against REV 10.6 — then close STEP 7 with its checker's verdict (already returned: PASS on seven of eight items, its one FAIL since fixed).
6. Re-dispatch STEP 8 from the WIP, re-proving everything.

## Handoffs
- The chrome (`js/pearl-nav.js`, `css/pearl-nav.css`) is the Home lane's; STEP 11 measures and posts, never edits. Measured at STEP 7: bar 7 destinations · rail 7 · no pill · badges and the active item in Cenote `rgb(22,124,140)`, while Nick's rulings say badges in ink and the active item in the screen's own accent — a handoff line is owed to that lane at STEP 11.
- **STEP 11 measured (2026-09-06 04:55Z):** bar destinations 7 · badges `rgb(211, 237, 241)` (expected INK `rgb(20, 20, 20)`) · `.pl-wait` pill none · active item `rgb(22, 124, 140)` (expected Ochre `rgb(179, 134, 42)`). Owner: Home lane `css/pearl-nav.css`.
- **STEP 11 close, 2026-09-06 11:22Z (verifier):** the accent-scope cause was THIS lane's — css/pearl-shopping.css had no body-scope accent declaration for the chrome (only the #view-shopping-scoped one), so the menu fell through to the app default teal; fixed 2026-09-06 in css/pearl-shopping.css (STEP 7 fix round; commit 6c87eba7e), re-measured on the injected page: active item now Ochre rgb(179, 134, 42), badges rgb(20, 20, 20) ink on white, 7+7 destinations, no `.pl-wait` pill; Home unaffected (rgb(30, 77, 92) active, rgb(214, 227, 231) badges, byte-identical sheets off vs on). What remains owed to the Home/chrome lane, not this one: (a) `css/pearl-nav.css` line 31 still paints badges soft on every OTHER screen (this lane only restated ink locally, on Shopping, with one extra class of specificity); (b) `.pl-rail .pl-foot .pl-me i` and `.pl-glide.pl-on` take the screen accent but are not on tools/pearl-accent-sites-shopping.json's accent-site list; (c) drawing anchor #53's rail-badge text colour is white live vs the drawing's rgba(20,20,20,.5) — left at white deliberately (matching the drawing would put low-contrast text on an ink ground), flagged for Sienna, not fixed here.
- **For every family-app lane, not just this one:** four lanes took four consecutive `sw.js` CACHE numbers within one hour, and a branch that derived `max(main, live) + 1` was equal to both again minutes later. Deriving it by hand cannot converge. The durable fixes: compute it at publish time inside the deploy step, or make the asset-parity gate fail a branch whose CACHE is not strictly above both main's and the live app's. The gate passes 12/12 today and does not measure this.

## Blocked
- Nothing needs Nick. The only wait in the plan is his look at the published drawing, and his 2026-09-04 approval of that same drawing in the lookbook stands (§D records the basis).

## Machine notes for the next session
- **Reap orphaned headless browsers before a long measuring run:** roots whose parent is launchd (`ps -eo pid,ppid,command | grep '[G]oogle Chrome for Testing' | grep -v -- '--type=' | awk '$2==1{print $1}'`) are dead test browsers; SIGTERM only those, never one with a live parent, and never touch Nick's own Chrome. 140 were reaped at 10:58Z (freeing ~10 GB) and 60 had regrown by 15:1xZ.
- The shared rig serialises every harness on one machine-wide lock (`/tmp/bzvisual-chrome.lock`); a contended lock is normal, a second Chrome is never the answer.
- The cheap lane refused several briefs on its hard-floor filter (its own copy of a brief's text tripped it) and reverted a correct edit whose proof exited non-zero; rule 16's two-attempt limit was applied and those files written by hand, recorded each time.
- The `.md` governance gate was off until 2026-09-05T15:42Z; check `node projects/ops/skippy-jobs/lib/md-gov-kill-switch.mjs status` before assuming a governed write will land.

## §11a shared motion — audit done 2026-09-05, by the overseer

**§11a applied — 0 motions removed, 0 attributes added yet, 0 forks.**

**The audit, measured not assumed** (`command grep -cE` over this lane's only two files):
- `css/pearl-shopping.css`: `transition` 0 · `animation` 0 · `@keyframes` 0 · `:hover` 0 · `transform` 28 — and all 28 are static: 26 are `transform:none` (neutraliser declarations killing another sheet's transform) and 2 are `text-transform:uppercase`, which my first grep pattern matched by accident.
- `js/pearl-shopping.js`: `transition|animate|requestAnimationFrame|keyframes` → 0.
- **So this lane wrote no motion at all and has nothing to delete.** That is consistent with the Finances spec's own rule this plan inherited: "Pearl declares zero transitions."
- Shared classes already in use: `.pl-g` (cards) and `.pl-l` (labels). Not used: `.pl-row`, `.pl-pill`, `.pl-scroll` — this screen restyles the app's OWN nodes in place (`.shop-prio` rows, `.shop-segmented` chips) rather than renaming them, which the plan's hook rule requires (`js/app.js` walks those nodes and is edited zero times).

**OWED, and deliberately not done now:** the rows need `data-pl-row` on each `.shop-prio` so they take the shared gliding hover pill, and the list cards need a decision on `data-pl-nosketch` / `data-pl-nolift`. Those attributes belong in the takeover `js/pearl-shopping.js`, which the STEP 9 agent is editing this hour — adding them now would collide. They go in with STEP 9's close or the step after it, and then §11a item 5's live proof (both identities, gliding pill on hover, a sketch in an empty card after two seconds, no motion of our own) is run and recorded here with the build version.

🔴 **CONFLICT WITH THIS PLAN, not resolved here per §11a item 6.** This plan's DESIGN FIDELITY GATE requires the screen to measure zero mismatches against a STATIC drawing, and its rule is that no screen closes above `mismatched properties: 0 · unmeasured anchors: 0`. §11a's sketch draws itself into a card's empty room after 2 s (trigger: ≥160×200 px of room, 120 tall on phones). Anchors 17–22 and 37–39 are list cards and empty-list cards measured for `height` and box properties. If a sketch mounts inside one during a measurement, it can change that card's measured height or add a child the map's selectors did not anticipate — a green screen could go red for a reason that is correct behaviour, or vice versa. Also unresolved: `--fence-pair` compares this lane's files on versus off in a frozen session, and a 2-second timer inside a shared module is exactly the kind of thing that made the earlier fence flap. Who decides: Sienna for the design question, the Home lane for the module. Until it is settled, the fidelity runs stay as they are and this line stands as the record.

**Also observed (STEP 8, 2026-09-05):** live panels already carry a `pl-lift` class that exists nowhere in this worktree — arriving from the shared module already deployed by the Home lane. It changed no anchor.

## STEP 8's closing check REFUTED the hand-off, 2026-09-05 — anchor 25 is unexplained

The STEP 8 builder handed two desktop anchors to STEP 9 saying both followed from the column geometry. A verifier that did not build them tested that claim instead of accepting it, and **anchor 25 (the bundle row, drawn 58px, live ~63px) is NOT explained by width**: it set the live card to the drawing's 471px column width, confirmed by a DOM parent-chain walk that the change propagated to the bundle title's own content box (357.8px → 405px), and the row height, the title's line count and every rendered geometry were **unchanged**. The title was never wrapping an extra line. Two runs, against frozen STEP 8 content extracted with `git show` so a concurrent STEP 9 edit could not contaminate it. Verdict `STEP 8 CLOSE CHECK: FAIL — item 2`, evidence/shopping-step8-close-check.txt; every other item PASS, including the phone's zero across all 39 anchors and both viewports' 16 header/toolbar anchors.

**So the ~5px on anchor 25 has an unidentified cause and must be diagnosed, not carried.** It goes to STEP 9's owner as a named defect, with the width hypothesis already eliminated. Anchor 18's attribution to the desktop frame was not tested by this checker and still stands unproven either way.

**Second finding, for the suite's own owner:** `--drive rows` genuinely returns `absent today` for all four row kinds on the published code — its 3000 ms settle is too thin for this app behind the password wall. Measured: one clean load rendered in 2519 ms, one under concurrent load had not rendered after 15 s. The builder's own wait-for-render script (evidence/shopping-step8-drive-injected.mjs) confirms all four interactions ✓. That settle must be lengthened before any later step depends on those drives.

**Environment note:** the accent-audit hit COUNT varies run to run because other lanes are modifying the shared live shopping list throughout this session; the substance (only the Add button and the link glyphs, never a label, heading, hairline or card edge) holds on every run.

## 🔴 INCIDENT 2026-09-06 ~00:50Z — the vault SCRIPT is missing in the Claude 2.0 checkout (this overseer's doing; the store is untouched)
- WHAT HAPPENED: this worktree's `projects/personal/family-vault` is a SYMLINK to the real folder in the main checkout. The new overseer ran `ln -sfn <main>/family-vault/vault.py <worktree>/family-vault/vault.py` to give the plan gate its proof path — the same habit that was harmless in the Calendar worktree, where the folder is real. Through the folder link that command replaced the real `vault.py` with a link pointing at itself. `secrets.vault`, the key in .env and every other file in the folder are untouched; only the 9,972-byte script is gone.
- WHAT IS BROKEN: every agent's headless sign-in to the family app (live-lib-home.mjs → vault.py) and any tool that calls `python3 projects/personal/family-vault/vault.py` in the 2.0 checkout, until the script is back.
- WHAT WAS TRIED: (1) `cp` of the byte-identical local backup `vault.py.restore-test` → refused by the routing gate (credential store, brand-new file); (2) `route-override.mjs` for that one file → refused because the path no longer exists; (3) the Write tool → refused by the same gate. The gate is behaving as designed: no agent may create a file in the credential store.
- THE FIX (Nick, one command at a real terminal — the two copies are byte-identical, sha256 c992695d9e94ab56…): `cp -p "/Users/nickdeck/Documents/Claude 2.0/projects/personal/family-vault/vault.py.restore-test" "/Users/nickdeck/Documents/Claude 2.0/projects/personal/family-vault/vault.py"` then `python3 "/Users/nickdeck/Documents/Claude 2.0/projects/personal/family-vault/vault.py" list --caller=skippy | wc -l` should print the entry count (110-ish). The old-workspace copy at /Users/nickdeck/Documents/Claude/projects/personal/family-vault/vault.py is identical and still works against its own store.
- STOPGAP used by this lane meanwhile: `PL_VAULT_PY=/Users/nickdeck/Documents/Claude/projects/personal/family-vault/vault.py` (the old workspace's copy; its store lists family-app-password).
- LESSON (registry candidate): never `ln -sfn` into a worktree path without first checking whether that directory is itself a symlink into the real checkout — a link created through a linked directory lands in the real tree.
- CLOSED 2026-09-06 ~01:35Z: Nick, in chat, "you do it" — the script was restored by this overseer from `vault.py.restore-test` (byte-identical, sha256 c992695d9e94ab56…) with a scratch Python copy; `vault.py list` again prints 123 entries with family-app-password present; the store was never touched. Nick's word is the recorded reason for an agent write inside the credential store; the gate's own override tool could not be used because it requires the path to exist.

## Overseer handover 2026-09-06 (this session picked the lane up after the previous overseer hit its weekly limit)
- Branch rebased onto origin/main (35 → level), pushed; the plan gate passes (nine Regret Check rows added for registry entries appended since); worktree symlinks for .env files restored — NOT for the vault (see the incident above).
- STEP 10 — BUILT and the suite's state mode PROVEN (commits c2ee87744 · 1f2fd3bf8 · 88c758845): the partial fix hid a bigger defect — `goto` to the same URL-with-fragment is a same-document navigation, so every phase after the first measured the previous phase's screen; repaired with an about:blank hop per phase, the skeleton observed by holding the feed, order phases NOT MEASURABLE when no rows, empty-phase naming, `--as` honoured, `--states-red text|class|feed` test hooks. Greens ×3 (10/10 reached · 10/10 verbatim; nick, nick, chantelle), reds ×3 (exit 1, 1, 2), selftest/unknown-flag/drive regressions ok. OWED: `--fence-pair off` could not complete under browser contention (exit 2, recorded, never a pass) — the closing checker re-runs it. FINDINGS for the checker/overseer: the same no-reload trap may sit in measureLive() (STEP 4's signed code — flagged, not changed); GAP 1 (Sienna) untouched. Checker in flight.
- Machine: 04:00Z load 200 → 04:30Z load 4 after reaping 29 orphaned headless roots and stopping another lane's 46-minute tree-wide grep; etiquette posted to PLAN-CHANGES.md on main for every lane.
- STEP 10 closing check (Sonnet, 2026-09-06 ~05:30Z): items 1–5, 7 PASS on its own live runs (10/10 · 10/10 as chantelle; nine strings byte-identical to app.js; distinctness; three reds; regressions; commit scope). Item 6 FAIL: its `--fence-pair off` read 210 changed (375) · NOT MEASURABLE (1024) · 225 changed (1280). DIAGNOSIS (overseer): an instrument artefact, not a leak — a static scan finds 0 selectors outside `body.skin-pearl` in pearl-shopping.css (98 rules) and pearl-shell.css (63 rules), and pearl-shopping.js exits without the class; the checker's own item 8a names the cause — measureLive()/fence-pair re-navigate to the identical route in one session (a same-document navigation), so the 'off' phase measured the 'on' page. Repair dispatched (Opus): every phase starts from a real load; proofs: fence-pair off 0 at three widths, on > 0, red with the hop disabled, states/selftest/drive unchanged. Then the checker re-runs item 6 only.
- 🔴 CORRECTION 2026-09-06 ~07:40Z (overseer): the "reap orphaned headless roots whose parent is launchd" note above is UNSAFE — the shared rig launches its Chrome detached, so EVERY harness browser has parent pid 1, live or dead; a 35-second-old triple with parent 1 is a lane's running check. Killing by parent pid kills live runs (this overseer did so at ~04:00Z and ~07:35Z before measuring the elapsed times; it is the likely cause of at least some of the 'Promise was collected' browser losses other lanes saw tonight). Rule from here: never kill a headless root younger than 30 minutes; prefer never killing at all — the lock serialises, and a stale root costs memory, not correctness.
- 🔴 08:05Z: THE APP-WIDE PEARL FLIP IS LIVE (Finances lane; pearl-nav.js v8 on deck-family-v593: Pearl unless ?skin=field). Consequences for this lane: (1) the everyday control screen is ?skin=field from now on — suite change in flight, then the STEP 10 item-6 re-check; (2) STEP 12's publish is no longer 'behind a flag' — it is what both people see by default; the screen they see TODAY is the everyday Shopping content inside the Pearl chrome (STEP 6 stubs are all that is served), so finishing STEPS 11–12 tonight matters more, not less; (3) STEP 12 must prove ?skin=field byte-identical instead of the bare route.
- STEP 10 — CLOSED 2026-09-06 (third checker pass, after two instrument repairs: the fresh-load hop and the ?skin=field control side). STEP 11 next (grunt measurement), then STEP 12 publish — which is in front of Nick and Chantelle immediately now that Pearl is the default.
- CORRECTION TO THE CORRECTION 2026-09-06 ~10:15Z: the 04:00Z and 07:35Z 'reaps' never killed anything — zsh does not word-split `for p in $LIST`, so each loop passed the whole pid list as one illegal pid. No live run was cut by this lane. The real reap happened at ~10:10Z: 39 headless roots, ALL with parent gone (launchd) and ALL older than 30 minutes (1 h 50 m to 3 h 47 m), each with a renderer spinning at 80–95% CPU — orphans from other lanes' scripts that died (session limits, timeouts) without closing their browser. Killed one pid at a time (`while read`): 39 → 0 roots, load 200 → falling, free memory 47 MB → 700 MB. Standing rule confirmed: age > 30 min AND parent gone; never a young one.
- STEP 11 — CLOSED 2026-09-06 (band clean; chrome fixed as a STEP 7 fix round and re-read by the checker). STEP 12 next: the publish is in front of Nick and Chantelle immediately (Pearl is the default); before it, the builder clears four live-drift anchors (#17/#18/#37 opacity mid-animation from the shared motion module; #55 content-box bottom) so the live count can read 0.
- LANDED FOR RESTART 2026-09-06 ~13:05Z (Nick: "land for restart"). STEP 12 (a) done (motion-settle wait; #17/#18/#37 closed). STEP 12 (b) found anchor #55's cause: a hidden view renders because `display` on the `#view-*` id outranks the app's `.view[hidden]` — To-Do's sheet (live) and Shopping's own line 251. STEP 9 fix round committed e3eee7e27: `[hidden]{display:none}` guard on both sheets (the To-Do one appended cross-lane, recorded in PLAN-CHANGES 12:50Z). The publish builder was stopped at the start of the PROOF of that fix; NOTHING PUBLISHED, no tags bumped. Resume from `redesign-mockups/concepts-2026-09-04-round10-screens/runbooks/README-RESUME.md` (runbooks and helper scripts now live in the repo). Orphaned-browser leak root-fixed in deck-shared 39f05cc.

## Postmortem — regroup 2026-09-06 (Nick: "we're out of Fable on this account, hand off shopping screens — run a regroup and update the plan so it's clear what's left")

The real sequence, not sanitised. Each line: what happened → the wrong output or false conclusion → what to keep.

**Failures**
- The overseer destroyed the vault script (2026-09-06 ~00:50Z): `ln -sfn` into the worktree's `family-vault/vault.py` — that directory was itself a symlink to the real folder, so the real `vault.py` became a link to itself and every `vault.py get` failed. Restored from the byte-identical `vault.py.restore-test` on Nick's word ("you do it"). KEEP: never `ln` through a directory that is itself a symlink; never create anything under a family-vault folder.
- Two "reaps" of orphaned browsers (04:00Z, 07:35Z) killed nothing: zsh does not word-split `for p in $LIST`, so `kill` got one illegal pid and the loop was a silent no-op — while the state file recorded them as done and a later note blamed them for lost runs. KEEP: `while read` or a script, one pid per kill; a reap that reports no count did nothing.
- The 07:45Z correction ("the rig launches Chrome detached, ppid 1 proves nothing") was written without measuring. Measured 12:00Z: the rig spawns without `detached`; a live harness's Chrome carries the harness pid. KEEP: measure before correcting a correction.
- The suite's state mode passed 10/10 on a same-document navigation (a `goto` to the same URL-with-fragment never reloads), so every phase measured a page the previous phase had mutated. Found when the fence-pair read 210/225 "differences" between two identical builds. KEEP: the about:blank hop before every phase (commit 832bd0a83).
- The app-wide Pearl flip (v559) moved the ground under the fence-pair: "flag off" became Pearl, and off/on read identical (225). KEEP: the control side loads `?skin=field` (commit fc3dae772); every proof that says "flag off" means `?skin=field`.
- STEP 11's chrome read showed the Finances teal on the Shopping route: Shopping declared its accent only inside `#view-shopping` while the bar and rail live outside it. Fixed as a STEP 7 fix round (`body.skin-pearl[data-pl-screen="shopping"]{--acc…}` + an ink-badge restatement). KEEP: accents are declared on the body scope; `--chrome` reads only the published page, so injected chrome needs its own reader (`evidence/shopping-step7-fix-chrome.mjs`).
- Anchors #17/#18/#37 read partial opacity because the shared arrival animation was still running when the sweep read them. KEEP: the suite waits for `document.getAnimations()` to empty (commit 4a304a82f).
- Anchor #55 (content box one viewport too low): a hidden view renders because `display` on the `#view-*` id (1,1,0) outranks the app's `.view[hidden]` (0,2,0) — in the To-Do lane's LIVE sheet and in Shopping's own line 251. The one-line guard on both sheets is commit e3eee7e27 (the To-Do one cross-lane, recorded in PLAN-CHANGES 12:50Z). KEEP: never set `display` on a view id without a `[hidden]` guard; the plan skill should carry this as a fidelity-gate trap.
- The cheap-lane typist refuses `tools/pearl-fidelity-shopping.mjs` ("hard-floor:secret label") and any worktree path it resolves outside its workspace, so every suite and rig edit landed on Anthropic with the same byte-exact proof — recorded each time, never routed around.
- The dispatch gate refused Sonnet BUILDERs twice; builders ran on Opus under `NICK-ASKED: opus` (his 2026-09-05 words). Grunt did the mechanical measurement.
- Merge commits are refused by the pre-commit plan gate (other lanes' plans in the tree) → rebase only; one rebase committed `sw.js` and logs WITH conflict markers when the resolver's assertion failed → repaired and amended. KEEP: grep for `<<<<<<<` before every commit; `contract-check.js` class list resolves as a union.
- Three overseers hit limits on this lane in 24 h (the first's weekly Fable limit; a usage pause mid-STEP-11; this account out of Fable at STEP 12). Each hand-off cost ~30 min of re-orientation. KEEP: runbooks and helper scripts live in the repo (`redesign-mockups/…/runbooks/`), not the scratchpad.

**Blockers met and cleared**
- Machine load 100–200 from orphaned headless Chromes (39 at 04:00Z, 33 at 11:30Z) launched by every lane's harnesses and left behind when their runners timed out. Root cause in the shared rig's signal handlers (lock released, browser not killed); fixed in `deck-shared/browser.mjs` 39f05cc, red/green by SIGTERM, the rig's own test 6/6. No new orphan after the fix.
- A 46-minute tree-wide `grep -rn PL_GATE_KEY` from another session; killed.
- The main checkout was mid-merge by another session twice; this lane worked only in its worktree.

**What went well / keep**
- Independent checkers found the real defects the builders' own runs passed: the chrome accent (STEP 11) and the hidden-view render (STEP 12). The count caught what eyes passed.
- Evidence on disk for every close, named by step; every checker verdict a file, never a paste.
- Landing discipline: nothing published tonight, so nothing to revert; the branch is level with main and pushed.

**Overseer self-audit (§E)**
- Work assigned to other lanes: the Home lane got three hand-offs (app-wide soft-badge rule, `.pl-me i` / `.pl-glide.pl-on` accent sites, anchor #53 text colour); the To-Do lane got the hidden-view finding WITH the fix applied in their sheet (a cross-lane edit, one line, recorded) because the defect is live for everyone and their session is unreachable from this app.
- Relays received and passed on: the flip (v559) from the Finances lane → this plan's contract delta; machine etiquette posted 04:10Z, corrected 07:45Z (wrongly), corrected again 10:15Z and 12:20Z.
- Dispatches that failed: two Sonnet builders (gate); the typist on the suite and the rig (recorded exceptions); one publish builder stopped by the overseer on Nick's "land for restart" (clean seam, nothing lost).
- Problems escalated into all-hands: none pursued as security; the load problem was fixed at its root instead of chased.

## Postmortem — the publish day, 2026-09-06 (extends the regroup postmortem above; same format)

**Failures**
- The plan's STEP 9 column rule contradicted its own claimed outcome ("in the switcher's order" placed iHerb+Costco left; the plan said that yields the drawing, which has Amazon left), and the STEP 9 checker verified the rule's output without reading it against the drawing. KEEP: a placement rule's proof reads the columns AGAINST THE DRAWING'S order, not against the rule's own text.
- The fidelity count read `0 · 0` three times per round while sibling ORDER was wrong three separate times (toolbar, columns, phone stack). KEEP: order anchors in every §D map; a full-length capture for every by-eye grade of a scrolling surface (registry row appended 2026-09-06).
- The round-1 verifier reported its test item removed while it still sat on the live Walmart list (it had added the item twice — once per width — and removed one; its "gone" check read `innerText`, which excludes the hidden non-active panel). Cost: three empty-card anchors read unmeasured in round 2 until the overseer removed it through the app's own `span.chk` control. KEEP: a removal proof counts rows by selector on a FRESH load and confirms the empty card is back, never `innerText`.
- A cache-busting value placed inside the hash (`#shopping&t=…`) routes to Home; the overseer's first full-page capture was of the wrong screen. KEEP: `?t=` in the query, `#shopping` last.
- The cheap-lane typist refused `sw.js` three times in round 3 (any family-app file over ~90 KB) and the suite by label; every refusal applied on Anthropic with the identical proof and recorded. KEEP: expected, not a defect — record and move.
- Another lane shipped three merge-conflict marker lines into the live `sw.js` changelog (v615); the round-3 builder found them on its signed read and repaired them with a parse proof. KEEP: every publish reads the served `sw.js` back and asserts it parses.
- The end-of-turn status tool refuses summaries on wording grounds unpredictably (the same phrase accepted at one turn and refused the next) and matches the plan path only as a literal string, so it must be aimed at whichever copy of the plan was touched last; eight refusals today. KEEP: plainest words, every referent named as a file or a person, no "the X" shorthand.

**What went well / keep**
- Three independent graders (Sienna by eye, a Sonnet verifier by measurement, a blind checker on criteria only) each found something the others did not; none of the four grade findings came from the builder's own run.
- Nick's two by-eye redlines (no sketch on Shopping cards; a visible accent) went from his words to the design page (REV 10.10) to the live app inside one hour under the plan's §D redline rule, with the drawing republished first and the build following.
- Nothing published today has been reverted by a peer: the served bytes of this lane's files were asserted equal to the tree after every round, through cache numbers v601 → v617 moved by other lanes.