Files
atomic-design-poc/docs/project/readable-codebase/RD-29-ladder-rules.md
T
ehoandClaude Sonnet 5 b6bd7eea5a refactor: enforce the atomic ladder in dependency-cruiser (RD-29)
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>
2026-09-05 08:32:52 +02:00

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:38anyRoot, and the atomic-layer rules that follow it. The new rules go beside them.
  • .dependency-cruiser.base.js:93-99ui-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.

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:

  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 importgit 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.