Repo: motir-core. One PR. Surfaced while fixing MOTIR-2301 (the ChildList row's rounded-md px-2 py-2), which deliberately scoped the sweep out. Logged as its own card rather than absorbed (notes.html #27 / the drive-by-fix rule).
⚠️ RE-SCOPED to the RADIUS axis by THE REPLAN ACTION, 2026-08-07. This card originally also owned the own-box PADDING / HEIGHT axis and sized both from a one-directory sample. Measured on
origin/main, that half is 447 literals in 190 files and — more decisively — has nowhere to go:--spacing-card-paddingis 24px and 140 of 145 card surfaces pad at something else, so the instruction "route it to the tokenCLAUDE.mdmaps its surface to" is unsatisfiable until the token set grows a density scale. That axis now lives in MOTIR-2335, whose first subtask is that decision; the story isblocked_bythis card because it extends the guard this card creates. Full evidence:notes.html#232 ("a sweep card whose routing instruction pointed at a destination that could not hold the values" — locate by title, the numbering shifts on append collisions), planning bug MOTIR-2337.
CLAUDE.md's shape rule: a surface's own radius MUST name an element-semantic shape token — --radius-{btn,card,input,modal,badge,control,kbd} — because only those are overridden by a [data-style] block. A raw rounded-md, a bare rounded, or a generic rounded-(--radius-sm) does not participate in the style swap.
Measured on packages/design-system/theme.css: of the ten bare [data-style] token blocks, nine also override the Tier-0 --radius-xs/sm/md/lg/xl scale. So an escaped surface still moves when the style changes — just to the wrong value (the container radius instead of the control radius, differing under most styles). Only soft-playful leaves the generic scale alone, which is the one style where the escape is plainly visible. A visual spot-check does not find this class; the wrong-but-moving value looks fine. That is what makes a mechanical guard the deliverable, not a one-off sweep.
origin/main @ 7ffa2ba4 — a sample of the shape, not the assertion)62 offenders in 34 files. By token: rounded-(--radius-sm) ×18 · rounded-md ×15 · bare rounded ×15 · rounded-(--radius-xs) ×9 · rounded-none ×1 · rounded-lg ×1 · rounded-xl ×1 · rounded-[calc(var(--radius-btn)-2px)] ×2. Concentrations: the app shell's menus and switchers (ProjectSwitcher, WorkspaceSwitcher, UserMenu, SidebarHeader, ThemeToggle, CommandPaletteTrigger, CreateIssueButton — rows and <kbd> chips on --radius-sm/-xs), the work-item detail panels (AddLinkControl, ContentSectionCard, RelationshipsPanel, RemoveLinkButton, FieldCard, ParentBreadcrumb), the skeletons (IssueTreeSkeleton, BoardSkeleton, BacklogSkeleton — bare rounded), WorkflowEditor, LinkAddForm, CreateIssueLinksField, MarkdownEditor, TreeTable.
Do not treat that list as the specification — it goes stale on every merge. The guard IS the specification: run it, and fix what it names (notes.html's site-count-goes-stale lesson).
Three known allowlist candidates, each to be judged on its merits, not inherited:
app/tokens/page.tsx — the design-system specimen route, whose job is to render the Tier-0 scale as labelled swatches. The same exemption swapLayerLint grants it for --color-*.packages/design-system/src/components/ui/Segmented.tsx and .../theme/AppearancePickers.tsx — rounded-[calc(var(--radius-btn)-2px)], a value DERIVED from a semantic token so the inner pill fits its shell. It swaps correctly; it is not an escape.app/(authed)/items/[key]/_components/AttachmentPreview.tsx — rounded-none, if the intent is a deliberately square full-bleed image. Verify before allowlisting; a wrong reason is worse than a fixed site.tests/theme/shapeSwapLint.test.ts exists, mirroring tests/theme/swapLayerLint.test.ts's exact shape: the same git ls-files tracked-source set, comments stripped before matching, and the offender list asserted EQUAL to an enumerated allowlist — so a fixed exception must be deleted from the test and a new violation cannot hide behind an old one.swapLayerLint's SOURCES.length > 1000 + named-file checks), so it can never silently pass by scanning nothing (notes.html #195).rounded-none|sm|md|lg|xl|2xl|3xl), a bare rounded, the generic rounded-(--radius-xs|sm|md|lg|xl), and an arbitrary rounded-[…] that does not derive from a --radius-* token. rounded-full and rounded-(--radius-pill) pass — a genuinely circular thing is not style-dependent.CLAUDE.md's radius-by-surface table maps its surface to, or carries an allowlist entry stating why it is legitimate. The allowlist entries are the ONLY exceptions; there is no third disposition.pnpm test tests/theme/shapeSwapLint.test.ts is green, and reverting any one migrated site turns it red (state the negative control you ran in the PR body).motir-core/CLAUDE.md — "Shape (radius + spacing + sizing) flows through element-semantic shape tokens", with the radius-by-surface table this sweep routes to.tests/theme/swapLayerLint.test.ts — the colour-axis guard this mirrors: the renderedSources() git ls-files set, stripComments, the exact-allowlist discipline, and the reason each is written that way.packages/design-system/theme.css:159-174 — the Tier-0 radius scale and the seven element-semantic roles; :360+ — the ten [data-style] blocks, nine of which override the generic scale.