Days
142
From first commit to a live store.
Case study · Community Food Co-op · Jun–Nov 2026
Community Food Co-op’s custom cake orders arrived in a shared inbox and were copied by hand onto carbon tickets. I replaced that with a WooCommerce plugin: a pickup-first cake builder, staff dashboards, printed tickets, emails and a Thanksgiving deli layer, live since 1 November 2026.
Days
142
From first commit to a live store.
Commits
1,503
1,482 of them with a Claude co-author trailer.
Assertions
6,089
In a full test run, 1 known red.
Tickets
69
61 closed, median 9 days to close.
DesktopThe landing page at 1440 px, from the desktop pass.
PhoneThe same landing at 375 px, the width every screen was designed at first.
01 · The problem
The co-op sold custom cakes through Catapult/WebCart, its POS vendor’s storefront. Each order landed in a shared inbox and was re-keyed onto a three-colour carbon ticket: white for the store, yellow for the deli, pink for the bakery. Cancellations were phone calls.
In October the deli joined the project. Until then the deli lead had run Thanksgiving pre-orders alone: every order arrived as an email that couldn’t be searched, and each one was typed three times, into a season master, a pickup-day sheet and the store’s guide.
The deli’s 25-sheet workbook showed why the numbers drifted. No formula on any sheet referenced another, so anything that appeared twice had been typed twice. The 2025 season had 42 orders; as the spec put it, “its cost is the retyping, not the volume.”
“The amount of hours that I put in, putting this in here, is ridiculous.”
Four groups use the system:
02 · Research and requirements
Before any feature, I ran “grills”: the agent puts each open decision to me, one at a time, with a recommended answer. The grill of 6 July locked 12 product decisions and 8 build phases. Every later ticket opened the same way.
Two of the twelve shaped everything after: size is the only variation axis, with every other choice stored as structured data, “never a text blob”; and the browser “is fast but never trusted”.
Six hard rules open the agents’ manual. One says the plugin must still render correctly with the theme switched off.
Requirements argue from real examples. The deli’s 2025 order board was read cell by cell, and each finding became a rule:
| What the 2025 workbook showed | The rule it became |
|---|---|
| A 2 lb side was counted, not doubled: the master showed 9 where the weight was 18 lb | Cooking totals are computed from order lines and a side-amounts table, never typed |
| 26 of 42 orders were picked up on the last day | Per-day, per-store sheets with slot numbers |
| Only 20 of 44 guide rows carried the website’s order code | The order number and slot print on every sheet |
| 11 of 32 bundles took no pie, and 11 took no protein | Protein & Dessert is an optional step with counters |
| Cancellations were deleted cells with “Cancelled” typed in | Cancel is a status plus a full refund, and the order is kept |
The rule that kept the agents honest is one sentence: “An undocumented assumption is a bug.” Any session that assumes something the docs don’t decide adds a numbered row before it commits, and I sign it off, supersede it or leave it pending.
| # | Assumption (trimmed) | Decided |
|---|---|---|
| A3 | 72-hour lead time means “today + 3 calendar days”, not a rolling clock; the hours are editable in Settings | Signed off 2026-07-07 |
| A349 | Check 18 fails on six seasonal products that carry the Seasonal category but no season: “a finding, not fixed here” | Recorded; the data is mine to fix |
| A960 | “Nothing prints on its own; a Print click goes on the site’s print list and a runner next to the Epson prints it” | José, 2026-10-20 |
| A1306 | An Undo doesn’t ask the holiday calendar; the checkout gate does | “José’s call, as built first”, 2026-10-26 |
| A2032 | Each holiday side’s allergens sit below its ingredients; a blank one reads “None” | José, 2026-11-04 |
The register holds 1,594 rows, numbered up to A2121 because each ticket prompt reserves a block of numbers.
Figure 1 · Test suite and register over time
Left, assertion lines in a full test run. Right, the highest row number in the assumptions register. Same dates, separate scales.
03 · Insights
The research produced 17 findings that changed the build. These five matter most to the people using it.
The bakery had no daily limit to give me, so the cap launched switched off while a counter ran from day one, derived from real orders with no stored total to drift. A real cap can come later, from data.
includes/class-capacity.php:2-17 · docs/build-roadmap.md:94-96
Lead time moved from 72 hours to two days with a 12:00 pm cut-off: an order placed before noon counts from today, and from noon it counts from tomorrow. A customer who opens the calendar at 11:55 and submits at 12:05 is refused by the server’s recheck, “as designed, no client timer”.
includes/helpers.php:907-911 · docs/assumptions-register.md:1291-1292 (A1504–A1505)
Step 1 is the pickup: a store, then a calendar with exactly three day states, and the step can’t advance without a date the server marks open. A customer never designs a cake for a day the bakery can’t make.
docs/figma/figma-final-wireframe-plan.md:37-39 · tests/cli/checks/06-pickup-first.php:3-6
The plan said auto-print, but the web host can’t reach a printer on the store network, and on 20 October I ruled it out: “i dont want tickets to automatically print when an order gets confirmed.” Now a Print click puts the job on a list, and a small runner beside the receipt printer prints it.
docs/assumptions-register.md:913 (A960) · includes/class-print-agent.php:2-33
The storefront CSS has exactly one media query, at 782 px; there’s no tablet layout. A check hashes every byte outside that block, so the 375 px layout I approved on my phone can’t move by accident.
docs/parity/28/ledger.md:42-43 · tests/cli/checks/33-desktop-pass.php:3-19
04 · Design
The flow was designed at 375 px in the co-op’s design-system file, from library tokens and components only, with each backend rule annotated on its frame in Dev Mode.
An overnight agent run on 5–6 August built 30 components, a 97-row copy map and 13 screens with 35 annotations. Its audit read 0 raw fills, 0 raw strokes, 0 raw font sizes and 0 raw radii.
I approved the structure and sent back the styling. The diff became a register of 30 deviations, 16 global and 14 per screen, plus seven calls left for me that would be “chipped, not silently decided”. An earlier pass had felt “too big, too bold-y”; the audit traced it to desktop text styles on 375 px frames.
The copywriters edited a duplicate of the journey. A read-only diff compared 691 text layers, found 91 copy edits and 54 comment threads, and changed nothing until I approved: “No node was mutated.”
The token rule is one line: “Never hand-author a brand value.” The plugin reads 200 --cfc-bakery-* names, and one file, tokens.php, is the only place they meet the theme’s tokens.
In code, every screen was compared with its frame at 375 px in 96 screenshots, until all 13 read “self-review passed”. The last gate was my own phone: “everything passed on my iPhone.”
05 · Build
The plugin doesn’t rebuild commerce. WooCommerce handles payment, cart and checkout; the plugin adds the builder, capacity, order statuses, tickets, emails and the staff screens.
There are 64 PHP modules and 50,116 lines, in ten groups:
Theme tokens only exist on front-end pages, so each surface resolves them its own way:
--cfc-bakery-color-inkone of 200 names · includes/tokens.php
Live
Storefront pages
var(--wp--preset--*, fallback): the theme’s live value
var(--wp--preset--color--primary, #284734)
Snapshot
Staff dashboard and print ticket
a :root snapshot written into every render
:root { --cfc-bakery-color-ink: #284734 }
Inline
literal values inlined on each element, zero var()
style="color:
The fallbacks are WordPress’s own computed clamp() strings. theme.json’s size field is the clamp’s maximum, and a fallback built from it would have rendered every email and ticket “too big”.
Figure 2 · Lines by area
Lines in the plugin repo by area, 7 November 2026.
06 · The AI workflow
Every commit is authored under my GitHub login, and 1,482 of 1,503 carry a Claude co-author trailer. My part is on either side of the code: the decisions and rules before it, and the verdict after.
One orchestrator session holds the plan and the shared test database. Builders work in their own git worktrees and may not push staging or main, merge, or edit the status file. On 25 October I handed the deli build to an orchestrator:
“you are the orchestrator … starting as many sessions as you need”
“I will leave this plugged in until you are done”
By the next morning all 18 deli tickets were merged and on staging.
Figure 3 · When commits happened
Commits by hour of day (Pacific) and by weekday, all branches.
Each deli ticket got its prompt from a separate session. The one for #49, the mixed cart, runs 243 lines under these headings:
# #49 (draft ticket 12) — Mixed cart: holiday is the third kind, and the department is decided at checkout## 0. The goal, in José’s words## 1. Blocked by, and what you may assume has landed on `holiday`## 2. What you build (the ticket body on #49 is the contract; this is the order — seams first, screen last)## 3. The `--only` list## 4. Check 47’s `47.12.*` sections (engine seam — library / stored data in → verdict out)## 5. The browser pass on Local (own orders, every tick by own order id)## 6. The review lane — PILLAR## 7. The Local lock, the stop rules, the 40% rule, the hand-back
When a builder passes about 40% of its context window, it stops at a clean commit and writes a hand-off for a fresh session. Hand-backs read like lab notes:
Review R1: 8/10, FIX. Two must-fixes […] Fix round 1 (12577ab).Review R2: 9/10, PASS. Browser pass on 12577ab: PASS […] Fix round 2 (ce2e87d).Review R3: 9/10, PASS. […]Full suite on the final tree: 6088 passed / 1 failed theme on, 6083 / 1 theme off (f38fd9e).
The review gate for risky changes is one rule. A new reviewer each round scores the change out of 10; 9 passes and anything lower goes back. The reviewer reruns the tests itself:
“It does not trust the builder’s word.”
I raised the cap from three rounds to five after one ticket needed five (7, 8, 8, 7, 9) and every failed round had found a real bug.
Figure 4 · Review rounds to a pass
Mentions of “9/10 in N rounds” across the status file and the agent records. A ticket can be mentioned more than once, so read the shape, not a count of tickets.
The test harness asserts at one seam, data in and verdict out, never through a browser, with the theme on and off. After two overlapping runs lost 16 settings on 20 October, every run takes a lock. The one standing red is a data problem of mine, and every report names it.
Figure 5 · GitHub issues, cumulative
Issues opened and closed on the plugin repo, 6 August to 7 November 2026. The shaded gap is open work.
A bug found outside a ticket gets a row and a question, never a quiet fix: “No fix without José’s word — not even a one-liner.” The status file uses the phrase “record-and-ask” 99 times.
Figure 6 · Co-author trailers by model
Co-Authored-By trailers by model, all branches.
Figure 7 · Commits by type
Conventional-commit prefixes across 1,503 commits.
07 · Outcome
The roadmap’s target was 15 August. The store went live on 1 November, 78 days later, after the deli layer joined the scope in October.
Figure 8 · Commits per day, all branches
Daily commits from 12 June to 5 November 2026, with milestones. October alone holds 1,174 of 1,503.
“all passed. push to main”
| Date | Launch week |
|---|---|
| 1 Nov, evening | Live on production. A round of launch fixes is built overnight in two lanes, both reviewed 9/10 |
| 2 Nov | Site-wide checkout built, reviewed and browser-tested; my five staging notes fixed; on production that evening |
| 3 Nov | Size price push fails my staging walk, is rebuilt, and reaches production the same day. The holiday walk passes and the 18 deli tickets close |
| 4 Nov | Thanksgiving ordering opens. Nine deli features that skipped scored review get read-only reviews from nine fresh sessions: all 9/10 |
| 5 Nov | A Health tab for staff reaches staging; the suite stands at 6,089 assertions |
I’m not showing order volume. The repo doesn’t record it, and the store’s sales are the co-op’s numbers.
08 · What I’d change
I wrote a retro on 4 November and turned it into a playbook. Three things stay, three change, two go.
“the bakery project wrote 1.09 million words of docs (about 12 novels). STATUS reached 99,000 words and contradicted itself, and 45% of the decision rows were never signed off.”