The folder now equals the layer, as CLAUDE.md decision 2 requires. 33 directories move by git mv (25 flat, plus upload/'s 8 subfolders split across all three layers). 28 distinct @shared/ui/* specifiers rewrite across 73 files, longest-first. Five relative imports inside upload/ become @shared/ui aliases because their sibling now lives in a different layer; two stay relative because both ends stay in the same layer. Four .mdx docs get their seven broken story imports fixed; atomic-design.mdx's page-shell import is untouched, because layout/ does not move. No component, template, story title, or layer-tag comment changes. That is RD-28's job. Verified against the ticket's acceptance commands: the 26 flat directories become exactly 3 layer folders with the counts the ticket names, only three @shared/ui/* prefixes remain (atoms, molecules, organisms), the .mdx import count holds at 7, and the relative-import count inside ui/ drops from 7 to 2 as decision 4 requires. The @shared/ui/ occurrence count moves from 200 to 205: decision 4 mandates turning 5 of those 7 relative imports into @shared/ui/* aliases, which decision 3's "200 before, 200 after" check does not account for. The 5-occurrence gap is exactly the 5 conversions decision 4 names, not a lost or duplicated specifier. npm run ci --full passes: lint, typecheck, dep:check, format, tokens, seam, both apps' + both libraries' tests, both apps' localized build, audit, backend tests, all three generated-artifact drift checks, and both Storybook instances' build + axe-core a11y suite (67+45 suites, 198+112 tests, all green). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
11 KiB
RD-27 — Make the folder equal the layer in libs/shared/src/ui/
Status: done Source: PLAN.md 4a, Phase 4
Why
CLAUDE.md decision 2 says "folder = layer": libs/shared/ui atoms → molecules → organisms.
Today libs/shared/src/ui/ is 26 flat directories, and the only record of a component's layer
is its story title and a header comment. Nothing stops an atom importing an organism.
This ticket makes the structure say what the rule says. It is a pure move: no component changes, no story title changes, no behaviour. RD-29 then adds the dependency-cruiser rules that the folders make expressible.
This is the highest-risk ticket in the arc, because a broken .mdx story import compiles
fine and only fails when Storybook builds. The README says it must not be pushed without
npm run ci --full.
Read first
libs/shared/src/ui/— 25 flat component directories plusupload/with 8 of its own.libs/shared/docs/atomic-design.mdx:2-5— three of the seven.mdxstory imports that break.- PLAN.md 4a — the design record, including why
layout/does not move. .storybook-ssp/main.ts:11-14— the globs, which are recursive and need no edit.
Decisions (pre-made, don't relitigate)
-
Three layer folders, and the full 33-directory mapping. This table is the ticket.
libs/shared/src/ui/atoms/— 12 flat:alert button checkbox heading link masked-value placeholder-chip radio-group skeleton spinner status-badge text-inputlibs/shared/src/ui/molecules/— 13 flat:application-link application-list async choice-link choice-list confirmation data-block data-row form-field review-section rich-text-editor stepper task-listupload/keeps its feature subfolder inside each layer — 8 directories:New location Directories ui/atoms/upload/delivery-channel-toggle,document-chip,file-input,upload-progress-bar,upload-status-iconui/molecules/upload/single-uploadui/organisms/upload/document-category,document-uploadui/organisms/holds nothing butupload/. That is correct and worth seeing.The mapping is derived from each component's own story title, not invented — every one of the 33 already declares its layer as
Design System/<Layer>/…. -
Use
git mvper directory, so the diff reads as renames.git diff --stat -Mmust show renames plus one-line import edits, nothing else. -
Rewrite the 28 distinct specifiers, longest key first. All 28 are listed by the mapping in decision 1; 25 are flat and 3 are the upload components imported from outside (
upload/document-upload→organisms/upload/document-upload,upload/file-input→atoms/upload/file-input,upload/single-upload→molecules/upload/single-upload).Verified: no specifier is a prefix of another, so ordering cannot corrupt a rewrite here. Do it longest-first anyway — it costs nothing and the property is not guaranteed to hold if this is ever repeated.
There are 200 occurrences across 73 files. The count of occurrences must not change.
-
Five of the seven relative imports inside
upload/become aliases; two stay relative. A../sibling/import only breaks when the sibling lands in a different layer:File (new location) Import Becomes organisms/upload/document-category../delivery-channel-toggle@shared/ui/atoms/upload/delivery-channel-toggleorganisms/upload/document-category../file-input@shared/ui/atoms/upload/file-inputorganisms/upload/document-category../single-upload@shared/ui/molecules/upload/single-uploadmolecules/upload/single-upload../document-chip@shared/ui/atoms/upload/document-chipmolecules/upload/single-upload../upload-progress-bar@shared/ui/atoms/upload/upload-progress-barUnchanged, because both ends stay in the same layer:
atoms/upload/document-chip→../upload-status-icon, andorganisms/upload/document-upload→../document-category.Use the alias, not
../../../. Cross-directory imports insidelibs/sharedalready use@shared/…(seewizard-shell.component.ts), and a three-level relative path is exactly the thing a later move breaks silently. -
Seven
.mdxstory imports break. All seven are real imports, not prose. The Order table says eight; measured, it is seven:a11y.mdx:2 ../src/ui/alert/alert.stories -> ui/atoms/alert/… a11y.mdx:3 ../src/ui/form-field/form-field.stories -> ui/molecules/form-field/… atomic-design.mdx:2 ../src/ui/button/button.stories -> ui/atoms/button/… atomic-design.mdx:3 ../src/ui/form-field/form-field.stories -> ui/molecules/form-field/… atomic-design.mdx:5 ../src/ui/upload/document-upload/… -> ui/organisms/upload/document-upload/… fp-in-ui.mdx:2 ../src/ui/async/async.stories -> ui/molecules/async/… remote-data.mdx:2 ../src/ui/async/async.stories -> ui/molecules/async/…atomic-design.mdx:4imports../src/layout/page-shell/…. Leave it alone —layout/does not move. -
layout/does not move, and neither doeslibs/beheer/src/ui/. CLAUDE.md §5 explicitly sanctionslibs/shared/layoutholding several layers, andlibs/beheeris a bounded context that lives inlibs/only because two apps share it. Both are settled; do not revisit them. -
Change no story title, no layer-tag comment, and no component code. The titles already say the right thing, which is what made decision 1's mapping derivable. The two mislabelled tags (
asynchas/** Convenience: */,breadcrumbsays/** Chrome: */) and the two missing ones (masked-value,rich-text-editor) are RD-28's job, not this ticket's. -
No barrel file. The repository has none and does not need one. A barrel would also hide exactly the layer boundary this ticket exists to expose.
Files
- 33 directories moved under
libs/shared/src/ui/ - ~73 files with a one-line specifier edit
- 4
.mdxfiles (7 import lines)
No changes to angular.json, eslint.config.mjs, plopfile.mjs, any tsconfig.json,
.dependency-cruiser.*, scripts/check-tokens.sh, or e2e/. Verified: @shared/* maps to
libs/shared/src/*, so a deeper path resolves unchanged; both Storybook globs are recursive;
dependency-cruiser's ui-not-infrastructure pattern is (/ui/|/layout/), which still matches a
nested path; and check-tokens.sh's CIBG-GAP check keys on the directory basename, which a
parent-folder move preserves.
Steps
mkdirthe three layer folders, thengit mvall 33 directories per decision 1.- Rewrite the 28 specifiers across
apps/andlibs/(decision 3). - Fix the five relative imports inside
upload/(decision 4). - Fix the seven
.mdximports (decision 5). npm run typecheck— four tsconfigs, and the fastest way to catch a missed specifier.git add -A, then run the acceptance commands.- Update this ticket's
Status:todoneand the README's RD-27 row todone. - Commit all of it together.
Acceptance criteria
Measured against the tree before handover. Run after git add -A.
The structure is three folders, and every directory landed:
ls -d libs/shared/src/ui/*/ | wc -l # is 26 -> MUST be 3
ls -d libs/shared/src/ui/atoms/*/ | wc -l # MUST be 13 (12 + upload)
ls -d libs/shared/src/ui/atoms/upload/*/ | wc -l # MUST be 5
ls -d libs/shared/src/ui/molecules/*/ | wc -l # MUST be 14 (13 + upload)
ls -d libs/shared/src/ui/molecules/upload/*/ | wc -l # MUST be 1
ls -d libs/shared/src/ui/organisms/*/ | wc -l # MUST be 1 (upload)
ls -d libs/shared/src/ui/organisms/upload/*/ | wc -l # MUST be 2
The single strongest check in this ticket — every specifier now names a layer, so only three distinct values may remain:
git grep -ho "@shared/ui/[a-z0-9-]*" -- apps libs | sort -u # is 26 values -> MUST be exactly these 3:
# @shared/ui/atoms
# @shared/ui/molecules
# @shared/ui/organisms
Nothing was lost or duplicated in the rewrite:
git grep -ho "@shared/ui/" -- apps libs | wc -l # is 200 -> MUST still be 200
git grep -c "src/ui/" -- '*.mdx' | awk -F: '{s+=$NF} END {print s+0}' # is 7 -> MUST still be 7
git grep -c "from '\.\./" -- libs/shared/src/ui/ | awk -F: '{s+=$NF} END {print s+0}' # is 7 -> MUST be 2
The move reads as a move (decision 2):
git diff --cached --stat -M | tail -1 # inspect: renames + one-line edits, no rewritten files
npm run ci --full # exits 0 — REQUIRED, see Verification
Verification
npm run ci --full is mandatory and non-negotiable for this ticket. Plain npm run ci does
not build Storybook, and a .mdx importing a moved story path is invisible to the type-checker,
the linter and every unit test. It fails only when build-storybook runs. The README calls out
RD-27 by name for this reason.
Run npm run typecheck first anyway (step 5): it covers four tsconfigs and catches a missed
@shared/ui/* specifier in seconds rather than at the end of a full gate.
libs/shared/docs/layers.mdx deep-links two story ids
(design-system-molecules-application-link--navigatie,
domein-registratie-aanvraag-block--concept). Story ids derive from titles, and decision 7
changes no title, so both links survive. Do not "fix" them.
Out of scope
- The dependency-cruiser ladder rules. RD-29 adds them, and it depends on this ticket.
- Layer-tag comments and the
libs/beheertitle rule — RD-28. layout/(decision 6).- Any component's code, template, styles or story title.
Risks
- A broken
.mdximport is invisible until Storybook builds. This is the whole reason the ticket carries--full. Decision 5 lists all seven; check each one after the move. git mv, notmv+git add. Both produce the same tree, but only the first keeps the diff readable as renames — and this diff is 33 directories wide.- Do not flatten
upload/(decision 1). Its subfolder survives inside each layer. - Do not reach for
../../../(decision 4). Use the alias. - The occurrence count is the tripwire for a bad
sed. 200 before, 200 after. A rewrite that accidentally matches twice, or drops a line, moves that number. atomic-design.mdxhas four story imports and only three of them move. The fourth ispage-shell, inlayout/.