Skip to content

fix: zero-config examples, and React Aria starters in an app's src/components - #257

Merged
dgesteves merged 4 commits into
mainfrom
fix/os2-57-examples
Oct 29, 2026
Merged

dgesteves merged 4 commits into
mainfrom
fix/os2-57-examples

Conversation

@dgesteves

@dgesteves dgesteves commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Roadmap item: OS2-57 (examples: zero-config demo, Tailwind v3 and React Aria examples, typecheck in CI). Part of #216.

What was wrong

  1. The demo was not zero config. The README calls a shadcn/ui app the zero-config demo, but examples/shadcn-demo had no components.json and shipped an onsystem.config.json. The config only existed because the docs sat in docs/*.md, outside the default docs/components/**. Without it, inspect printed files 5 component files, 1 token file, 0 docs.
  2. React Aria's starter, copied into an app, was not found. React Aria's getting-started page has you download the Tailwind starter and use its components. In an app they land in src/components/ next to App.tsx, and the flat-folder rule only looked at a top-level src/ with no app entry. On main, with the new examples/react-aria: inspect printed files 0 component files, check . checked the components as app code and said "No design-system components found: the component rules did not run", and init --yes stopped with "zero config found no design system".
  3. Example polish. The demo README said danger-zone.tsx gets "one problem per rule"; check reports 11 problems across the 8 rules (no-hardcoded-color 3, no-hardcoded-spacing 2). The components adapted from shadcn/ui did not carry shadcn/ui's MIT notice. The examples' typecheck never ran in CI (the root tsconfig excludes examples). No example had a .gitignore.

What changed

  • examples/shadcn-demo: a new-york components.json, the docs moved to docs/components/, onsystem.config.json removed. inspect now prints config (defaults) and detected components.json (ui: @/components/ui → components/ui) with 5 docs. check "app/**/*.tsx" output is byte-identical to main's (8 errors, 3 warnings). The README table lists components.json, says "check reports 11 problems, at least one for each of onsystem's 8 rules", and links LICENSE-shadcn (shadcn/ui's MIT license, copied as published). Plus a .gitignore.
  • examples/tailwind-v3 (new): shadcn/ui as it was on Tailwind v3: bare HSL channel tokens in app/globals.css, a tailwind.config.ts that maps them to classes, forwardRef components, a components.json that names both. No config: inspect finds it through components.json. check . reports 9 problems on the draft page; for example, text-[#71717a] matches --muted-foreground exactly and the fix is text-muted-foreground, a class from tailwind.config.ts. The README says that no-hardcoded-spacing does not run there, since onsystem takes the spacing scale from a Tailwind v4 stylesheet or spacing tokens (see follow-ups).
  • examples/react-aria (new): an app with React Aria Components in src/components/ next to src/App.tsx and src/main.tsx, adapted from the React Aria Tailwind starter (tailwind-variants, composeRenderProps, the starter's composeTailwindRenderProps and focusRing), restyled with Tailwind v4 tokens in @theme. The README says so and links LICENSE-react-aria, the starter's Apache-2.0 license as react-spectrum publishes it (with its Copyright 2019 Adobe line). inspect prints detected src/components/ (7 components wrapping react-aria-components). check . reports 7 problems on the draft, including <Checkbox> has no prop "checked". Did you mean "isSelected"? and <Switch> has no prop "disabled". Did you mean "isDisabled"?.
  • src/detect.ts: after a flat src/, zero config tries src/components/, components/ and app/components/ with the same rule: a primitives dependency, at least five PascalCase .tsx/.jsx files at the folder's top level, four in five of which import the library. The app-entry check applies only to src/. The default components/ui globs stay in, the app's stylesheets are the theme (the default token globs), and init writes the glob it found. inspect --explain gets a line per folder it looked at, and names the primitives libraries when the app depends on more than one (import a primitives library (react-aria-components, @radix-ui/react-dialog)).
  • Radix's icons are not primitives: @radix-ui/react-icons matched @radix-ui/react-*, so five of an app's own components that each show a Radix icon were taken for a design system and check skipped them. PRIMITIVES now leaves it out, in src/detect.ts and in the hook's copy (plugins/onsystem/hooks/check-ui.mjs), which a test keeps the same. No other icon library the repo knows (the ICON_SOURCE list in src/lint/rules/components.ts: lucide, Heroicons, Tabler, Phosphor and the rest) matches the list. This also applies to the flat src/ rule that main already has.
  • CI: pnpm --filter "./examples/*" typecheck in the "Lint, typecheck, build" job.
  • Docs: one sentence in docs/configuration.md (zero config, rule 4) on the copied-starter layout, and that Radix's icons do not count.
  • Changeset (patch) for the detection change.

The two new examples use the npm release through npx -y onsystem, like the demo. The React Aria example's README says its zero-config detection needs onsystem 0.11.1 or later (0.11.0 is being released without this change). With main's build, inspect on a copy of it prints files 0 component files, 1 token file, 0 docs and check ends with "No design-system components found: the component rules did not run"; with this branch's, detected src/components/ (7 components wrapping react-aria-components) and 7 problems.

Outside the listed files

The plan listed .github/workflows/ci.yml, the three example folders, src/detect.ts and test/example.test.ts. These had to change too:

  • pnpm-lock.yaml: examples/* is a pnpm workspace glob, so the two new examples are workspace packages and pnpm install --frozen-lockfile needs their importers. 38 packages are added, all from tailwindcss@3.4.19 and tailwind-merge@2.6.1 for the Tailwind v3 example; the other dependencies reuse versions already in the lockfile.
  • test/extract.test.ts, test/server.test.ts: the demo's Button docs path is now docs/components/button.md (one line each). test/config.test.ts: the --config test used the demo's config file, now the ACME fixture's. test/setup-steps.test.ts: the test of a project that already has a config writes one into its copy of the demo. The finding named these references.
  • docs/configuration.md: one sentence, as the finding proposed.
  • New test file test/detect-components-folder.test.ts.
  • plugins/onsystem/hooks/check-ui.mjs: the same @radix-ui/react-icons exclusion in its copy of PRIMITIVES (one line).

No open pull request touches these files.

Not in this pull request

  • The two font mappings in the demo's app/globals.css (--font-sans: var(--font-geist-sans), which nothing defines). shadcn/ui's Next.js template has the same lines, where next/font sets the variables at run time, and test/export.test.ts uses them for the unresolved-font case ("Wrote 31 tokens", font-sans as var(--font-geist-sans)). Dropping or defining them changes those export tests; that belongs in a pull request on the export tests.
  • Dependency floors. The demo's newest floors are now 12 to 24 days old (lucide-react 1.59.0 on 2026-10-17, @radix-ui/react-slot 1.5.0 on 2026-10-12, @radix-ui/react-dialog 1.2.0 on 2026-10-05). Dependabot raises the floors on every update (it did on 2026-10-21), so lowering them here would not last: that needs versioning-strategy for the examples in .github/dependabot.yml. The new examples start with floors at least four weeks old.
  • no-hardcoded-spacing on Tailwind v3. It does not run on a v3 project, since the default spacing unit is only added for a stylesheet that imports tailwindcss. This is the documented behaviour in docs/rules.md; changing it is a src/tokens change of its own.
  • A Base UI (base-nova) shadcn app in the corpus. The corpus is frozen after this wave, and adding a repository changes the README's corpus figures.
  • The README's Cursor Directory badge still points at /plugins/design-system-mcp; it needs a check of the listing in a browser.
  • The Claude Code hook's cheap "is the monorepo root one project" check (plugins/onsystem/hooks/check-ui.mjs) gets the icons fix but still looks only at a flat src/, which matters only for a monorepo root that keeps such components in its own src/components/.

How it was tested

New tests, which fail on main:

  • test/example.test.ts now runs the same checks on every folder in examples/ (a test fails when a folder is added without an entry): the README's first steps work in a copy of the folder made outside the repository with its dependencies linked; there is no onsystem config, and inspect in the copy prints config (defaults) and the expected detected line; check exits 1 on the draft page only, and the README's table gives its exact number of problems; a .gitignore lists node_modules; the example has typecheck and CI runs pnpm --filter "./examples/*" typecheck. Per example: the demo covers all 8 rules and reads 5 docs from docs/components/; the shadcn-derived examples carry LICENSE-shadcn with shadcn/ui's copyright line; the Tailwind v3 example matches text-[#71717a] to --muted-foreground through tailwind.config.ts; the React Aria example suggests isSelected and isDisabled from React Aria's types. Run against main's examples/shadcn-demo and ci.yml, the demo's checks fail on the config file, the README's count, the rule-count wording, the missing .gitignore and the missing CI typecheck.
  • test/detect-components-folder.test.ts: React Aria's starter in src/components/ next to main.tsx and App.tsx is detected, with the default globs kept and src/index.css as the theme; inspect --explain says why; check on a new page reports a native <button> for <Button>; init --yes writes src/components/*.{tsx,jsx}; components/ and app/components/ in a Next.js app are detected, Radix wrappers too; a folder of the app's own features, fewer than five components, or no primitives dependency is left alone; five of an app's own components in components/ or a flat src/ that import only @radix-ui/react-icons are left alone, and check reports their hardcoded color; inspect --explain names one primitives dependency, or lists them when there are more; the hook's PRIMITIVES is the same as src/detect.ts's; a flat src/ still comes first. The icons and explain tests fail on this branch before this fix.
  • test/example.test.ts: the React Aria example's README says it is adapted from the starter, links LICENSE-react-aria, and the file has the Apache-2.0 text with Copyright 2019 Adobe.

Gate, on the branch merged with main after #255: pnpm install --frozen-lockfile --prefer-offline, pnpm format:check, pnpm lint, pnpm typecheck, pnpm --filter "./examples/*" typecheck, pnpm test (107 files passed, 1 skipped; 1403 tests passed, 3 skipped), pnpm build and pnpm smoke pass. I also ran CI's two demo steps by hand (check app/settings/members.tsx --format github exits 0; the hook on danger-zone.tsx exits 2 with no-unknown-variant), and pnpm --filter site generate, pnpm --filter site test (22 files, 294 tests) and pnpm --filter site typecheck, since the site loads the demo and renders docs/configuration.md.

Corpus: the detection changes could move counts, so I ran node scripts/corpus.mjs after the build, again after the icons fix. It printed "Counts match corpus/snapshot.json" for the 25 runs, and all 80 labels of the blind sample are still reported. node scripts/corpus.mjs eslint printed "The ESLint plugin reports what check reports in 25 runs." The snapshot is unchanged. I did not run the installed-types corpus (--installed), which CI runs weekly.

…mponents

The shadcn demo shipped an onsystem.config.json only because its docs sat
outside docs/components/, so it did not show the zero config the README
promises. It now has a components.json and its docs in docs/components/;
check finds the same 11 problems.

Add a Tailwind v3 shadcn/ui example (HSL channel tokens, tailwind.config.ts)
and a React Aria Components example whose components sit in src/components/
next to App.tsx, and typecheck every example in CI.

Zero config now reads a flat folder of components that wrap a primitives
library in src/components/, components/ or app/components/, with the same
guard as a flat src/ (five PascalCase files, four in five importing the
library). Before, React Aria's starter copied into an app gave "No
design-system components found".

Also: the demo README's problem count, shadcn/ui's MIT notice next to the
components adapted from it, and a .gitignore in each example.
- PRIMITIVES (src/detect.ts and the hook's copy) leaves out
  @radix-ui/react-icons: five of an app's own components that each show a
  Radix icon were taken for a design system, and check skipped them. A test
  keeps the hook's copy the same as src/detect.ts's.
- inspect --explain names the primitives libraries, not the first one, when
  a package depends on more than one.
- examples/react-aria says its components are adapted from React Aria's
  Tailwind starter, ships the starter's Apache-2.0 license as
  LICENSE-react-aria, and says the zero-config detection needs onsystem
  0.11.1 or later.
Apache-2.0 asks a modified file to carry a notice that it was changed: each file under examples/react-aria/src/components now names the starter and LICENSE-react-aria. inspect --explain also keeps its old wording when no primitives dependency is listed.
@dgesteves
dgesteves merged commit 4e7174d into main Oct 29, 2026
19 checks passed
@dgesteves
dgesteves deleted the fix/os2-57-examples branch October 29, 2026 10:55
@dgesteves-release dgesteves-release Bot mentioned this pull request Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant