diff --git a/.dependency-cruiser.base.js b/.dependency-cruiser.base.js index b6f1e0b..e80da5e 100644 --- a/.dependency-cruiser.base.js +++ b/.dependency-cruiser.base.js @@ -109,6 +109,29 @@ module.exports = function buildConfig(contextAllowed, appName, tsConfigFileName) }, }, + // --- Atomic ladder within libs/shared/src/ui (folder = layer, CLAUDE.md decision 2) --- + { + name: 'atoms-compose-nothing-above', + comment: 'An atom composes nothing above it — no molecule or organism. See CLAUDE.md §2.', + severity: 'error', + from: { path: '^libs/shared/src/ui/atoms/' }, + to: { path: '^libs/shared/src/ui/(molecules|organisms)/' }, + }, + { + name: 'molecules-below-organisms', + comment: 'A molecule composes nothing above it — no organism. See CLAUDE.md §2.', + severity: 'error', + from: { path: '^libs/shared/src/ui/molecules/' }, + to: { path: '^libs/shared/src/ui/organisms/' }, + }, + { + name: 'design-system-not-layout', + comment: 'The design system (ui/) does not depend on layout/ templates. See CLAUDE.md §2.', + severity: 'error', + from: { path: '^libs/shared/src/ui/' }, + to: { path: '^libs/shared/src/layout/' }, + }, + { name: 'no-testing-in-production', comment: diff --git a/docs/project/readable-codebase/RD-29-ladder-rules.md b/docs/project/readable-codebase/RD-29-ladder-rules.md new file mode 100644 index 0000000..c37bde9 --- /dev/null +++ b/docs/project/readable-codebase/RD-29-ladder-rules.md @@ -0,0 +1,143 @@ +# RD-29 — Enforce the atomic ladder in dependency-cruiser + +Status: done +Source: PLAN.md 4c + +## Why + +RD-27 made the ladder **expressible**: `libs/shared/src/ui/` is now `atoms/`, `molecules/`, +`organisms/`. Nothing yet makes it **enforced** — an atom importing an organism compiles, lints, +tests and ships. + +This ticket adds the three rules. It is the payoff for the move, and PLAN calls it "the real +prize". + +## Read first + +- `.dependency-cruiser.base.js:38` — `anyRoot`, and the atomic-layer rules that follow it. The + new rules go beside them. +- `.dependency-cruiser.base.js:93-99` — `ui-not-infrastructure`, the closest existing rule in + shape. +- PLAN.md 4c. + +## Decisions (pre-made, don't relitigate) + +1. **Three rules, in `.dependency-cruiser.base.js`, forbidding upward edges only:** + + | Rule name | from | to | + | ----------------------------- | -------------------------------- | --------------------------------------------- | + | `atoms-compose-nothing-above` | `^libs/shared/src/ui/atoms/` | `^libs/shared/src/ui/(molecules\|organisms)/` | + | `molecules-below-organisms` | `^libs/shared/src/ui/molecules/` | `^libs/shared/src/ui/organisms/` | + | `design-system-not-layout` | `^libs/shared/src/ui/` | `^libs/shared/src/layout/` | + + `severity: 'error'`, each with a `comment` naming CLAUDE.md decision 2, matching the house + style of every other rule in that file. + + Literal `libs/shared/src/...` paths, not `anyRoot`: only `libs/shared` has layer folders. The + rules are evaluated once per app cruise, which is harmless — the same tree, the same answer. + +2. **Never "atoms are leaves". Same-layer edges are legitimate and four exist today:** + + ``` + atoms/masked-value -> atoms/button + molecules/review-section -> molecules/data-block + molecules/task-list -> molecules/choice-list + molecules/task-list -> molecules/choice-link + ``` + + A rule forbidding an atom from importing any atom would fail on the first of these. Forbid + the layers **above**, nothing else. + +3. **No `pathNot` exemption for specs and stories. This corrects PLAN.** + + PLAN says the exemption is needed "because `async.stories.ts` composes `skeleton`". + Measured: `async` is a molecule and `skeleton` is an atom, so that edge points **downward** + and is legal under decision 1. The example does not justify an exemption. + + Measured further: **zero upward edges exist anywhere in `libs/shared/src/ui/`, in production + code, specs and stories alike.** Nothing needs the exemption today. + + So leave it out. An exemption that nothing needs is dead flexibility, and it silently widens + the rule the moment someone does write an upward import in a story. If a story ever earns one + — an atom's story demonstrating it inside a molecule is the plausible case — add the + exemption then, with that real example in the comment. + +4. **All three land green immediately.** This ticket adds enforcement and changes no application + code. If `dep:check` fails after adding them, the rule is written wrongly — do not "fix" the + application to satisfy it without saying so. + +5. **Dependency-cruiser, not ESLint.** It is where every other boundary rule in this repository + lives, and it emits the architecture graph. Do not add an ESLint variant. + +## Files + +- `.dependency-cruiser.base.js` + +Nothing else. No application code changes. + +## Steps + +1. Add the three rules per decision 1, beside the existing atomic-layer rules. +2. Run `npm run dep:check` — it must pass. +3. **Prove each rule bites** (see Verification). This is the point of the ticket. +4. `git add -A`, then run the acceptance commands. +5. Update this ticket's `Status:` to `done` and the README's RD-29 row to `done`. +6. Commit all of it together. + +## Acceptance criteria + +Measured against the tree before handover. + +```bash +git grep -c "atoms-compose-nothing-above" -- .dependency-cruiser.base.js # is 0 -> MUST be 1 +git grep -c "molecules-below-organisms" -- .dependency-cruiser.base.js # is 0 -> MUST be 1 +git grep -c "design-system-not-layout" -- .dependency-cruiser.base.js # is 0 -> MUST be 1 +``` + +The rules pass on the current tree, and no application file changed (decisions 4 and 5): + +```bash +npm run dep:check # exits 0 +git diff --cached --name-only | grep -v '^docs/' # MUST list only .dependency-cruiser.base.js +``` + +```bash +npm run ci # exits 0 +``` + +`--full` is not required: no story, no `.mdx`, and nothing under `libs/shared/src/ui/**` is +edited. The Order table's blank column is correct here. + +## Verification + +**A rule that matches nothing is worse than no rule, because it reads as protection.** Prove +each of the three actually fires, one at a time: + +1. Add a temporary import that violates it — for example, in + `libs/shared/src/ui/atoms/button/button.component.ts`, import + `@shared/ui/molecules/data-row/data-row.component`. +2. Run `npm run dep:check` and confirm it fails, naming that rule. +3. Revert the temporary import. + +Do this for all three. Report which rule name each violation produced. **Do not commit any +temporary import** — `git status` must be clean of them before you commit, and the acceptance +command above checks that only the config file changed. + +## Out of scope + +- Layer folders or ladder rules for `libs/beheer`. It is a bounded context, not a design system + (RD-28 settled this). +- `layout/`'s internal structure. It is sanctioned to hold several layers. +- Any rule about app contexts' own `ui/` folders. They have no layer folders by design — a + context organism does not get its own bucket. +- Changing an existing dependency-cruiser rule. + +## Risks + +- **Do not add the spec/stories exemption out of habit** (decision 3). Two other rules in the + file have one; these three do not need it, and the ticket explains why. +- **Forbid upward, not sideways** (decision 2). Four same-layer edges exist and are correct. +- **`from` must not match the layer it forbids.** `design-system-not-layout` starts at + `^libs/shared/src/ui/`, which covers all three layer folders; that is intended. +- **If `dep:check` goes red, suspect the rule, not the code** (decision 4). Nothing in the tree + violates the ladder today. diff --git a/docs/project/readable-codebase/README.md b/docs/project/readable-codebase/README.md index 87ae2bf..d010a8c 100644 --- a/docs/project/readable-codebase/README.md +++ b/docs/project/readable-codebase/README.md @@ -123,7 +123,7 @@ two. Note that RD-15 exists because 22 abandoned agent worktrees are still on di | RD-26 | `letter-canvas`: inline the labels + `letter-line`; keep one disable | 02 | yes | done | | RD-27 | **The layer move:** 33 `git mv` + 28 specifiers + 8 MDX imports | 21 | yes | done | | RD-28 | Layer-tag fixes + the `libs/beheer` title rule | 27 | yes | done | -| RD-29 | The 3 atomic-ladder rules in dependency-cruiser | 27 | | todo | +| RD-29 | The 3 atomic-ladder rules in dependency-cruiser | 27 | | done | | 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 |