feat(design-system): make the design system the default source for all UI - #3
Merged
Merged
Conversation
…l ui Establish @elirobinson/react and @elirobinson/tokens as the primary source of components, tokens, and design patterns for anything built on this template, across every surface an AI agent or human might read. Nothing here hardcodes the design system's contents, so bumping the package version is the only maintenance this template needs: - add `pnpm ds`, a live inventory read from node_modules at run time (components with exports and variants, hooks, typography classes, tokens, CSS classes). Docs point at the command instead of repeating its output. - lead AGENTS.md with the design system rule; drop the hardcoded component inventory that would go stale on every release. - add docs/design-system.md as the deep guide (discovery, token usage, dark mode, escalation path when something is missing). - add a Claude Code skill, a Cursor rule, and Copilot instructions so the same rule applies whichever tool is driving. - block foreign UI libraries and direct Radix imports via ESLint no-restricted-imports; shadcn output in src/components/ui/ is exempt. - alias the Tailwind color layer onto design system tokens in globals.css, replacing an unrelated hardcoded palette. This fixes text-muted-foreground resolving to nothing, and makes dark mode work via [data-theme="dark"] with no extra code. - use design system typography on the home page so the template models correct usage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lower-case subject rule rejected legitimate acronyms and proper nouns (UI, API, TypeScript). Disable subject-case rather than inherit config-conventional's default, which also bans sentence-case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…o 1.1.0 Pull the third design system package into the same treatment tokens and react already get, and move to the react version its contracts describe. @elirobinson/ai-patterns ships the design system's expectations for how UI gets built — uiContracts (touch target, visible focus, WCAG AA), named componentConstraints each with a machine-checkable `check`, working patterns, and reusable prompt templates. It is a devDependency; nothing reaches the browser. - add `pnpm ds contracts`, `pnpm ds patterns`, and `pnpm ds prompts [name]`, and surface the package's version in the inventory header. - upgrade @elirobinson/react 0.4.0 -> 1.1.0. The contracts describe tiered subpath imports (components/<tier>/<Name>), which 0.4.0 predates; 1.1.0 also grows the inventory from 24 flat components to 45 across atoms/molecules/organisms. - make component discovery layout-agnostic: `pnpm ds` walks the package tree rather than assuming a flat directory, so the 0.4 -> 1.1 reorganisation needed no change to the script or to any doc. `pnpm ds props` accepts a bare name or a full subpath and prints the specifier. - extend the ESLint import ban with the `no-barrel-imports` contract: bare @elirobinson/* specifiers are blocked, since no barrel file exists and the import would not resolve. - cover ai-patterns, the tier layout and the contracts across AGENTS.md, docs/design-system.md, README, the Claude Code skill, the Cursor rule, the Copilot instructions and .env.example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d packages Every piece of design system tooling this repo built by hand now ships from EliRobinson/design-system, versioned with the code it describes. Each local copy drifted independently and nothing signalled when it had. - scripts/design-system.mjs (333 lines) -> elirobinson-ds, a bin on @elirobinson/ai-patterns. It reads a generated manifest instead of regex-parsing .d.ts, and is layout-agnostic. - The @theme inline block in globals.css -> @elirobinson/tokens/tailwind.css. - The import bans in eslint.config.mjs -> @elirobinson/eslint-config, which also adds no-hardcoded-design-values. - The four agent-instruction files -> pnpm ds init --agents, with AGENTS.md now carrying a managed block. - Most of docs/design-system.md -> pnpm ds. What is left covers only the four places the design system touches this repo. Adds e2e/design-system.spec.mts, which runs the contracts a linter cannot settle (touch targets, visible focus, WCAG AA contrast) in both themes. It is .mts because the helper is ESM-only and Playwright compiles a plain .ts spec to CJS, where the subpath does not resolve. Moves the next/font variables from <body> to <html>. Dropping the @theme block also dropped --font-sans: var(--font-geist-sans), and the token's literal 'Geist' never matches next/font's generated family name, so the page silently rendered in system-ui. The family tokens are now repointed from an unlayered :root — tokens.css declares them unlayered, so anything in @layer base loses — which carries Geist through tokens.css's own body rule, the .t-* classes, and Tailwind's font-sans. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the design system the primary source of components, tokens, and design patterns for anything built on this template — reinforced across every surface an AI agent or human might read, and enforced by lint and E2E rather than only asked for in prose.
Nothing in this template hardcodes the design system's contents, so bumping the package version is the only maintenance it ever needs.
The mechanism
Docs that list components rot on every design system release. So instead of a list, there's a command —
pnpm ds, which reads the installed packages at run time:Every doc points at this command rather than repeating its output. AGENTS.md says it explicitly: never paste or trust a component inventory — it is wrong as of the next release.
The tooling ships from upstream, not from here
The final commit on this branch deletes the hand-built versions of that tooling in favour of the published packages, which are versioned with the code they describe. Each local copy drifted independently, and nothing signalled when it had.
scripts/design-system.mjs(333 lines)elirobinson-ds, abinon@elirobinson/ai-patterns@theme inlineblock insrc/app/globals.css(~30 aliases)@import '@elirobinson/tokens/tailwind.css'FOREIGN_UI_LIBRARIES/DESIGN_SYSTEM_BARRELS/DIRECT_PRIMITIVESineslint.config.mjs@elirobinson/eslint-configpnpm ds init --agentsdocs/design-system.md(154 → 100 lines)pnpm dsThe upstream CLI reads a generated
manifest.jsoninstead of regex-parsing.d.ts, and is layout-agnostic. The ESLint config addsno-hardcoded-design-values, which catches hex /rgb()/oklch()literals and magic px for radius, shadow and duration inclassNamestrings,cn()calls andstyleobjects.Package versions:
react@1.3.0,tokens@0.3.0,ai-patterns@0.5.0,eslint-config@0.2.0, plusaxe-core.Coverage
AGENTS.md/CLAUDE.mddocs/design-system.md.claude/skills/design-system/SKILL.md.cursor/rules/design-system.mdc.tsx/.css.github/copilot-instructions.mdREADME.mdThe four agent-instruction files are generated by
pnpm ds init --agents, so a version bump refreshes them too.Enforcement, not just instruction
@elirobinson/eslint-configblocks foreign UI libraries, direct Radix imports outsidesrc/components/ui/, bare@elirobinson/*imports, and hardcoded design values.@elirobinson/tokens/tailwind.cssmaps the Tailwind namespaces onto the tokens. This fixed a live bug on the way in:text-muted-foregroundpreviously resolved to nothing, and now compiles tovar(--fg-3). Dark mode works via[data-theme="dark"]with no extra code.e2e/design-system.spec.mtsrunsexpectDesignSystemContracts(touch targets, visible focus, WCAG AA contrast) in both light and dark themes. The existinge2eCI job picks it up with no workflow change.src/app/page.tsx— the template's one page models correct usage (Eyebrow,.t-h1/.t-lead/.t-caption), since agents pattern-match on existing code.Two things worth a reviewer's attention
The contract spec is
.mts, not.ts. The helper is ESM-only, and Playwright compiles a plain.tsspec to CJS, where the subpath fails withERR_PACKAGE_PATH_NOT_EXPORTED. Adding arequirecondition to that export map upstream would let the.tsform work.The
next/fontvariables moved from<body>to<html>. Dropping the@themeblock also dropped--font-sans: var(--font-geist-sans), and the token's literal'Geist'never matches next/font's generated family name — so the page silently rendered in system-ui. The family tokens are now repointed from an unlayered:root(tokens.css declares them unlayered, so anything in@layer baseloses), which requires--font-geist-sansto exist at:root. Geist now carries through tokens.css's ownbodyrule, the.t-*classes, and Tailwind'sfont-sans— previously onlybodyhad it.Verification
pnpm build,pnpm lint,pnpm type-check,pnpm format:check,pnpm testall clean. The new lint rule was probed with a throwaway file: it caught a hex color, arounded-[7px], and a rawboxShadow.pnpm test:e2e --project=chromiumagainstpnpm build && pnpm start: 4/4 passed, including both contract tests.bg-backgroundresolves tovar(--bg), fonts resolve toGeist, "Geist Fallback",data-theme="dark"flips the page, no console errors.🤖 Generated with Claude Code