Add three dependency-cruiser rules that forbid upward edges inside libs/shared/src/ui: atoms-compose-nothing-above, molecules-below-organisms, and design-system-not-layout. RD-27 made the atomic ladder expressible by moving components into atoms/molecules/organisms folders; these rules make it enforced, so an upward import now fails dep:check instead of shipping. No pathNot exemption for specs and stories: measured, zero upward edges exist anywhere in libs/shared/src/ui today, in production code, specs, and stories alike, so the exemption PLAN proposed has nothing to protect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.2 KiB
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)
-
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 acommentnaming CLAUDE.md decision 2, matching the house style of every other rule in that file.Literal
libs/shared/src/...paths, notanyRoot: onlylibs/sharedhas layer folders. The rules are evaluated once per app cruise, which is harmless — the same tree, the same answer. -
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-linkA rule forbidding an atom from importing any atom would fail on the first of these. Forbid the layers above, nothing else.
-
No
pathNotexemption for specs and stories. This corrects PLAN.PLAN says the exemption is needed "because
async.stories.tscomposesskeleton". Measured:asyncis a molecule andskeletonis 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.
-
All three land green immediately. This ticket adds enforcement and changes no application code. If
dep:checkfails after adding them, the rule is written wrongly — do not "fix" the application to satisfy it without saying so. -
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
- Add the three rules per decision 1, beside the existing atomic-layer rules.
- Run
npm run dep:check— it must pass. - Prove each rule bites (see Verification). This is the point of the ticket.
git add -A, then run the acceptance commands.- Update this ticket's
Status:todoneand the README's RD-29 row todone. - Commit all of it together.
Acceptance criteria
Measured against the tree before handover.
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):
npm run dep:check # exits 0
git diff --cached --name-only | grep -v '^docs/' # MUST list only .dependency-cruiser.base.js
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:
- 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. - Run
npm run dep:checkand confirm it fails, naming that rule. - 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.
frommust not match the layer it forbids.design-system-not-layoutstarts at^libs/shared/src/ui/, which covers all three layer folders; that is intended.- If
dep:checkgoes red, suspect the rule, not the code (decision 4). Nothing in the tree violates the ladder today.