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>
206 lines
12 KiB
Markdown
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.
|