Files
atomic-design-poc/docs/project/readable-codebase/README.md
T
ehoandClaude Opus 5 84cbf3f7d8 docs: use git grep for acceptance checks, and fix RD-09's ledger row
Four acceptance commands in this backlog could not pass as written. The
fourth, in RD-09, reached this backlog's own ticket files and 22
gitignored worktrees, so satisfying it literally would have rewritten the
history of completed tickets.

The general fix is `git grep` instead of `grep -r`: it searches tracked
files only, so untracked and gitignored paths cannot pollute the result.
Measured here, `grep -r` finds 132 hits under .claude/ where `git grep`
finds none. RD-17, RD-18 and RD-19 are repo-wide sweeps and depend on
this.

Also correct RD-09's Order row. It claimed the ticket covered a generator
and a skill file; neither teaches the deleted idiom, as recorded in
PLAN.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 17:28:37 +02:00

206 lines
12 KiB
Markdown

# Readable codebase — apply the dashboard pattern to the rest of the app
The dashboard refactor cut `dashboard.page.ts` from 340 lines to 42. It was built as a
**reference implementation**: prove the pattern on one screen, then hold the rest of the app
to the budget it establishes.
This arc applies that result. The full design record, with every measurement and every
rejected alternative, is [`PLAN.md`](PLAN.md). Each ticket below is one commit.
Two findings shape the work:
1. **Pages are already thin** (17 pages, median 88 lines). The remaining bulk sits one layer
down, in organisms.
2. **The worst problem is not size.** `runIfSubmitting` is copy-pasted into 5 components and
must be called by hand after `dispatch`. Forgetting it fails silently. RD-06 fixes two
user-facing bugs that follow from it.
## Session protocol
- **One ticket per session.** Read `CLAUDE.md`, this README, the ticket file, and the
ticket's "Read first" list — then execute. Do not start the next ticket in the same
session.
- The **Decisions** block in each ticket is pre-made. Do not relitigate it. `PLAN.md` records
why, including the alternatives that were rejected and the reasons.
- **Ticket files are written just in time, not all 35 up front.** Writing one means choosing
which decisions and which traps from `PLAN.md` belong in it, so the **supervisor** writes
the ticket file (an Opus-shaped job) immediately before delegating it. The file lands in
that ticket's own commit. Generating all 35 in advance would be speculative — later tickets
are better written once the earlier ones have taught us something.
- **Match the model to the step** (see CLAUDE.md, "Model routing for agent delegation").
Executing a ticket is written for the `developer` agent (Sonnet). Read-only checks go to
`task-runner` (Haiku). Escalate to `planner` (Opus) only if a Decisions block turns out to
be wrong — in which case stop, and fix `PLAN.md` first.
- A ticket ends **GREEN**, with its acceptance criteria checked, its `Status:` set to `done`,
**and its README row updated — all in the same commit as the code.** Never in a follow-up
commit. This is what makes a restart safe: whatever is committed is done, and whatever is
not is not.
- **`Status: done` carries no commit hash**, because a commit cannot contain its own hash. The
commit is recoverable when you need it:
`git log --oneline --diff-filter=A -- docs/project/readable-codebase/RD-NN-*.md`.
- No ticket leaves a check disabled without an inline reason **and** a reference to the
ticket that removes it.
## GREEN (global definition of done)
```bash
npm run ci
```
For any ticket whose "`--full`?" column says yes — it touches a story, an `.mdx`, or
`libs/shared/src/ui/**` — additionally:
```bash
npm run ci --full
```
`npm run ci` does **not** build Storybook. Only `--full` does, and a broken `.mdx` story
import is invisible until it runs. RD-27 in particular must not be pushed without it.
## Recovery after a restart
A fresh session with no context needs three commands:
```bash
git log --oneline -8
grep -rn '^Status:' docs/project/readable-codebase/RD-*.md | grep -v done # next work
npm run ci # is HEAD green?
```
Then read `PLAN.md` for the design record, and the first `todo` ticket for the work.
## The agent loop
One supervisor session drives it; one `developer` agent executes each ticket:
1. Read the Order table. Pick the first `todo` whose dependencies are all `done`.
2. Spawn **one** `developer` agent: _"Read `CLAUDE.md`, then
`docs/project/readable-codebase/README.md`, then `RD-NN.md` and its Read-first list.
Execute it. End GREEN. Update the ticket Status and the README row in the same commit as
the code. Do not start another ticket."_
3. Verify with `task-runner`: `npm run ci`, `git log -1 --stat`, and that `Status:` now reads
`done`. Never mark a ticket done on an agent's report alone — the check is the exit code.
4. Green: next iteration. Red: stop and surface it.
**Run tickets sequentially.** Three properties make concurrent writes to one branch hostile:
`behaviour-spec.mdx` and `snippets.generated.ts` are regenerated and drift-checked, so two
agents regenerating conflict by construction; every ticket writes this README's Order table;
and the file sets overlap (the three wizards appear in RD-06, RD-07, RD-22 and RD-23).
Parallel work pays only for genuinely disjoint tickets, in separate git worktrees, merged
deliberately — RD-18/RD-19 (the ticket sweep) and the Phase 5 doc tickets qualify. Cap at
two. Note that RD-15 exists because 22 abandoned agent worktrees are still on disk.
## Order
| ID | Ticket | Deps | `--full`? | Status |
| ----- | ---------------------------------------------------------------------------- | ---------- | --------- | ------ |
| RD-01 | Scaffold this backlog: README, PLAN, ticket template | — | | done |
| RD-02 | `max-lines` rule + `reportUnusedDisableDirectives` + 7 disables | 01 | | done |
| RD-03 | `overzicht` context: page + 2 nav sections, boundary edge, admin-links token | 02 | yes | done |
| RD-04 | Story titles to `Domein/<Context>/<Name>`; add the missing stories | 03 | yes | todo |
| RD-05 | `createStore` gains the effect map + specs | 02 | | done |
| RD-06 | **Bug fix:** 2 single-step forms to the effect map + retry affordance | 05 | yes | done |
| RD-07 | Add `Primary` to the 3 wizard machines + specs | 05 | | done |
| RD-08 | Migrate the 3 wizards to the effect map + `Primary` | 07 | yes | done |
| RD-09 | Teach the effect map: ARCHITECTURE §2d + fp-tea (2 docs, no generator) | 08 | | done |
| RD-10 | `WizardStatus` to a payload-carrying `WizardPhase` | 08 | yes | todo |
| RD-11 | Fold the lifecycle projection into `remote-data.ts`; PascalCase 3 machines | 01 | | todo |
| RD-12 | `ActionState` becomes `action` on `BriefState.Loaded` | 11 | | todo |
| RD-13 | Same for org-template, folding `pendingPublish` in | 12 | | todo |
| RD-14 | Move `SaveState` to `debounced-save.ts`; delete `action-state.ts` | 13 | | todo |
| RD-15 | Remove 22 abandoned agent worktrees (4.7 GB) | 01 | | todo |
| RD-16 | `parseDashboardView` returns `BigProfile`; delete `DashboardView` | 01 | | todo |
| RD-17 | `successOf`/`successOr` sweep — 10 sites, 8 files | 01 | | todo |
| RD-18 | Ticket-reference sweep, frontend — 181 refs, 100 files | 01 | | todo |
| RD-19 | Ticket-reference sweep, backend — 370 refs, 86 files | 01 | | todo |
| RD-20 | `wizard-errors.ts` + spec, adopted by all 3 wizards | 02 | | todo |
| RD-21 | `rich-text-dom.ts` helpers + spec cases | 02 | yes | todo |
| RD-22 | `intake-wizard` to 3 step components | 08, 20 | yes | todo |
| RD-23 | `registratie-wizard` to 3 steps + the upload-controller move | 08, 20 | yes | todo |
| RD-24 | `concepts.page` to 6 sections + `concept-card` + globals + code tokens | 02 | yes | todo |
| RD-25 | `org-template-editor` to `sample-letter.ts` + labels + 2 children | 02 | yes | todo |
| RD-26 | `letter-canvas`: inline the labels + `letter-line`; keep one disable | 02 | yes | todo |
| RD-27 | **The layer move:** 33 `git mv` + 28 specifiers + 8 MDX imports | 21 | yes | todo |
| RD-28 | Layer-tag fixes + the `libs/beheer` title rule | 27 | | todo |
| RD-29 | The 3 atomic-ladder rules in dependency-cruiser | 27 | | todo |
| RD-30 | Archive the finished backlogs (16,300 lines) + an archive README | 01 | | todo |
| RD-31 | `ARCHITECTURE.md` section 6a: symbols not lines, 2 dead paths, new names | 03, 08, 16 | | todo |
| RD-32 | `fp-tea-atomic-design.md`: 11 broken paths + the broken anchor | 27 | | todo |
| RD-33 | CLAUDE.md + `atomic-design.mdx` + the `ui-component` skill | 03, 27, 29 | yes | todo |
| RD-34 | _(optional)_ `NO_SUBORGS`/`NO_TABLES` become `RemoteData.Empty` | 11 | | todo |
| RD-35 | _(optional, last, alone)_ upload `type:` discriminant to `tag:` | 27 | | todo |
The ID order already respects every dependency, so it is the recommended running order.
**Independent tickets.** RD-15 through RD-19 depend only on RD-01. Pull them forward to fill
a short session. Take RD-15 early: it makes every later repository search faster.
**Two ordering traps the table encodes.** RD-01 must precede RD-30, because RD-01 copies its
ticket template out of the directory that RD-30 archives. And four tickets edit the same two
documents in different sections — RD-09 rewrites the submit-idiom teaching, while RD-31 and
RD-32 fix section 6a and the stale paths. Sequential is fine. Never put those pairs in
parallel worktrees.
## Ticket template
```markdown
# RD-NN — Title
Status: todo | in-progress | done
Source: PLAN.md section <n>
## Why
## Read first
## Decisions (pre-made, don't relitigate)
## Files
## Steps
## Acceptance criteria
## Verification
## Out of scope
## Risks
```
Three rules when you write a ticket file, because the agent reads its ticket and not
`PLAN.md`:
1. **Copy the decision, never a pointer to it.** The verdict goes in the Decisions block,
verbatim.
2. **Inline the traps that apply to that ticket.** A trap recorded only in `PLAN.md`'s global
Risks section is a trap that fires.
3. **State acceptance as a command, not a sentence.** "Lands about 230 lines" is a design
estimate and nothing can check it. `npm run lint` has an exit code.
4. **Run every acceptance command against the tree before you hand the ticket over.** A
command that cannot pass is worse than no command: the agent either wastes a cycle or,
worse, "fixes" correct code to satisfy it. Four real misses so far, all in tickets written
by the supervisor:
- RD-06 grepped only `runIfSubmitting`, missing that one wizard spells it `runIfIndienen`.
- RD-08 grepped bare `onPrimary\|onRetry`, which can never return nothing — an unrelated
`uploadCtl.onRetry` exists in `upload-controller.ts`.
- RD-08 said "no machine changes" while also requiring a repo-wide grep to come back
clean, which forced comment edits in three machines. The two instructions contradicted
each other.
- RD-09 grepped `docs/ apps/ libs/ .claude/`, which also matched this backlog's own ticket
files (they name the deleted method as the history of `done` work) and 22 gitignored
abandoned worktrees. Satisfying it literally would have corrupted completed-ticket
history.
Three habits that prevent all four:
- **Use `git grep`, not `grep -r`.** It searches tracked files only, so untracked and
gitignored paths never pollute the result. Measured on this repo: `grep -r` finds 132
hits under `.claude/`, `git grep` finds 0. This matters most for RD-17, RD-18 and RD-19,
which are repo-wide sweeps.
- **Anchor on a declaration** (`^ onRetry\(\)`), not on a name that may legitimately
appear elsewhere.
- **Keep the Files list consistent with the Acceptance commands.** If a command reaches a
file the ticket says not to touch, one of the two is wrong.