Skip to content

Releases: dgesteves/onsystem

v0.10.0

Choose a tag to compare

@dgesteves-release dgesteves-release released this 23 Oct 23:18
5d4edfd

onsystem's GitHub Action at 0.10.0. Pin it with uses: dgesteves/onsystem@v0.10.0, or use @v0 to follow each release.

Release notes: https://github.com/dgesteves/onsystem/releases/tag/onsystem%400.10.0

v0.9.1

Choose a tag to compare

@dgesteves-release dgesteves-release released this 23 Oct 21:53
bbf69ed

onsystem's GitHub Action at 0.9.1. Pin it with uses: dgesteves/onsystem@v0.9.1, or use @v0 to follow each release.

Release notes: https://github.com/dgesteves/onsystem/releases/tag/onsystem%400.9.1

onsystem@0.9.1

Choose a tag to compare

@dgesteves-release dgesteves-release released this 23 Oct 21:53
bbf69ed

onsystem 0.9.1 reads tokens the way your stylesheets apply them, and no longer slows down on very long values.

  • shadcn/ui's Tailwind v4 codemod theme (--color-destructive: hsl(var(--destructive))) is read as the token destructive, so text-red-500 is fixed to text-destructive, not text-[hsl(var(--destructive))]. Stylesheets imported one after another apply in CSS order, and when components.json names a stylesheet in another package (as cal.com's does), the app's own stylesheet is read first.
  • A Tailwind v3 config keeps Tailwind's default radius scale: rounded-[12px] is fixed to rounded-xl, not rounded-lg. In a v3 project with no radius tokens of its own, no-hardcoded-radius now runs against that scale, so check can report warnings it did not before.
  • A class with an arbitrary length thousands of digits long (p-[999…9x]) took about 25 s to check at 100,000 characters, in check, check_ui, the ESLint plugin and the Action; it now takes a few milliseconds. A var() whose fallback holds calc() or another var() now has the fallback's value. onsystem export --format dtcg keeps a __proto__ token and no longer adds properties to every object in the process.
  • Thanks to @Aaqibhafeezkhan (get_component explains when a project has no components, #234) and @Golden007-prog (a failed warning cap is explained in every CLI output format, and JSON and SARIF stay parseable, #235).

Patch Changes

  • #234 643bc6f Thanks @Aaqibhafeezkhan! - Explain when a project has no design-system components available.

  • #235 65f91f5 Thanks @Golden007-prog! - Explain a failed warning cap in every CLI output format: a GitHub error annotation, or a message on stderr that leaves JSON and SARIF output parseable.

  • #232 a965061 - A long length or color no longer slows onsystem down. A class with an arbitrary length thousands of digits long, such as p-[999…9x], took a second to check at 20,000 characters and about 25 seconds at 100,000, in check, the MCP check_ui tool, the ESLint plugin and the GitHub Action: the pattern that reads a length tried every way to split the digits. It now takes a few milliseconds. The same goes for token values: a length, a var() with a fallback, or bare HSL or RGB channels in the design system's stylesheets or Tailwind config, and typography, spacing and radius values in onsystem export. A var() whose fallback holds calc() or another var() now has the fallback's value, inside calc() too: calc(2 * var(--gap, var(--space-2))) is read as a length, and calc(var(--gap, var(--space-2)) + 1px) no longer drops the + 1px.

    onsystem export --format dtcg keeps a token whose path has a __proto__ part, such as --color-__proto__, under a "__proto__" key of its group. It was left out, and a name such as --__proto__.polluted added a polluted property to every object in the process that ran the export.

  • #201 ee231a8 - Tokens are read the way the stylesheets and the Tailwind config apply them, in four cases where they were not.

    • An @theme color that wraps a variable of bare channels, as shadcn/ui's Tailwind v4 codemod leaves a v3 theme (--color-destructive: hsl(var(--destructive)) with --destructive: 0 84.2% 60.2%), is the token destructive with its class. The class and the color were on two tokens, so every fix was an arbitrary value: text-red-500 was fixed to text-[hsl(var(--destructive))], and now to text-destructive. rgb(var(--x)) over RGB channels counts too. An @theme value that names another variable takes its color: with --color-brand: var(--color-blue-600), bg-blue-600 suggests bg-brand.
    • Stylesheets imported one after another apply as CSS applies them: a later @import overrides an earlier one, and the importing stylesheet's own rules override both. With @import "./base.css"; @import "./brand.css";, --primary came from base.css, so bg-[[#171717](https://github.com/dgesteves/onsystem/issues/171717)] was fixed to bg-primary, which renders brand.css's color.
    • A Tailwind v3 config that extends borderRadius (as shadcn/ui's does, with lg, md and sm), or sets none, keeps Tailwind's default scale: rounded-[12px] is fixed to rounded-xl, not rounded-lg (8px), and rounded-b-[24px] to rounded-b-3xl, not rounded-b-full. A theme.borderRadius outside extend replaces the scale, unless it spreads defaultTheme.borderRadius into it. No default step is added when the scale cannot be read: a step of the config has no known size, because no stylesheet read sets the variable it uses (var(--radius)); the config takes a preset, a theme or the whole config from an installed package (presets: [require("@acme/preset")], theme: require("@acme/theme"), module.exports = require("@acme/tailwind-config"), { ...base, content }), which may replace the scale; or borderRadius is set from a variable, a function or a spread other than defaultTheme.borderRadius. Tailwind's own modules (tailwindcss/defaultConfig) do not count. A theme that is a module of the project, or spreads one (theme: require("./theme"), theme: { ...require("./theme") }), replaces the scale with its own borderRadius, and presets: [] leaves out Tailwind's default config, so neither keeps the default steps unless a theme.borderRadius spreads them back in. Nor is one added in a Tailwind v4 project that reads the config through @config, which has v4's scale. In a Tailwind v3 project with no radius tokens of its own, such as Dub, no-hardcoded-radius now runs against that scale, so check can report warnings it did not before.
    • When components.json names a stylesheet in another package, as cal.com's names coss/ui's, zero config also reads the app's own stylesheet first (styles/globals.css, app/globals.css and the others it looks for without components.json), and the named one through it when the app's stylesheet imports it. cal.com's own tokens (text-subtle, bg-emphasis, rounded-cal) were not read, and color fixes named coss/ui's tokens instead. When the app's stylesheet imports Tailwind v4, the spacing and radius rules use Tailwind's scale, as in any Tailwind v4 app.

onsystem@0.10.0

Choose a tag to compare

@dgesteves-release dgesteves-release released this 23 Oct 23:18
5d4edfd

onsystem 0.10.0 layers an app's config over the one its design-system package declares, instead of replacing it.

  • An app's own onsystem.config.json now builds on its design-system package's config: its rules apply over the package's, rule by rule, and what it leaves out (components, importPath, docs, tokens) comes from the package, unless it sets components of its own. Before, a config that only turned one rule to "warn" lost the package's components and tokens, and check said No design system found and passed. "extends": false reads a config alone, as before.
  • A monorepo root's config does the same when you check from the root, so its rules and overrides apply to every project without a config of its own.
  • init no longer writes pnpm's version-named paths (node_modules/.pnpm/@acme+ui@1.0.0…) into a config, which broke check after an upgrade. init --force on a package that declares a config writes only $schema, keeps the components of an app config that names its own, and keeps a tokens list that names the app's own stylesheet beside the package's.
  • inspect names both configs it reads, and inspect --explain says where the components come from.
  • onsystem export --format design-md keeps each table row on one line when a comment or value wraps over lines (#237).

This can change what check reports in an app that has a config and a design-system dependency that declares one: it now gets the package's rules where its config leaves them out.


Minor Changes

  • #202 3798fb7 - An app's own config builds on the config its design-system package declares, instead of replacing it. What the app's config leaves out comes from the package's, unless it sets components of its own: components, with the package's importPath, docs, elements, tokens and libraries. A config that sets components reads its tokens as before: without tokens, from the app's own stylesheet (app/globals.css) and the ones it imports. Its rules apply over the package's, rule by rule, either way, and, with the package's components, its elements element by element; the package's exclude patterns are added to the app's; its overrides, tsconfig and includeDesignSystem are its own. Before, an app config that only turned one rule to "warn" lost the package's components and color tokens: in an app that installs the package, check said No design system found and passed. inspect names both configs (config onsystem.config.json, over node_modules/@acme/ui/onsystem.config.json (declared by @acme/ui)). "extends": false in the app's config reads it alone, as before. This can change what check reports in an app with a config of its own whose dependency declares one: it now gets the package's rules where its config leaves them out, and the package's components and tokens when it sets no components of its own. A workspace package whose "onsystem" field cannot be read is a warning in such an app, which then reads its own config alone, and still an error in an app without one.

    A monorepo root's config builds on a project's design-system package config the same way in check and inspect from the root, so its rules and overrides apply to every project without a config of its own. Before, the config a project's design-system package declared took their place, without a word. From the project's folder, the root's config is still not read.

    init --force in an app on such a package, without a config or with one whose components match no file outside the package, writes a config with only $schema, for the app's own rules and overrides, and keeps the package's components, importPath, tokens and rules. Before, it wrote the package's paths into the app's config, or a config that lost the package's components. In an app whose config names components of its own, it writes them again, as before.

    init in an app whose design system zero config reads from an installed package leaves components and tokens to zero config. It wrote paths into the package's folder, which pnpm names by version (node_modules/.pnpm/@acme+ui@1.0.0_react@19.0.0/node_modules/@acme/ui/…), so after an upgrade check failed with Token file not found, and the component files it listed matched nothing.

    In a design-system package whose files leave out its component sources, init says that apps installing it read its components from the type declarations it publishes, when its package.json names some. It said they get no component checks, which holds only without type declarations.

Patch Changes

  • #237 5a10990 - onsystem export --format design-md keeps each table row on one line. A token whose comment or value the stylesheet wraps over several lines, such as a two-part --shadow-lg, broke its row: GitHub showed the rest of it as a code block and every later row in that table as a paragraph. The lines are now joined with spaces.

v0.9.0

Choose a tag to compare

@dgesteves-release dgesteves-release released this 21 Oct 12:44
6eccaea

onsystem 0.9.0 reads design systems published as compiled packages, keeps baselines right when files move, and gives agents clearer MCP answers.

  • An app on a design-system package installed as dist/ plus type declarations now gets every rule: components, props and variants come from its .d.ts files (@dub/ui from npm gives 427 components).
  • The baseline follows a renamed or moved file, and in the GitHub Action a copied baselined line is reported where it was added. The Action runs the onsystem version the project installed, so the lockfile decides; without an install it runs its own version as before.
  • no-hardcoded-color warns when a chart-* token stands in for a status color outside a chart, and init says which status colors the theme has no token for.
  • MCP: search_components says when nothing clearly matches instead of returning a poor best guess, list_components filters and collapses icons, and get_tokens sets chart and sidebar colors apart.

onsystem's GitHub Action at 0.9.0. Pin it with uses: dgesteves/onsystem@v0.9.0, or use @v0 to follow each release.

Release notes: https://github.com/dgesteves/onsystem/releases/tag/onsystem%400.9.0

onsystem@0.9.0

Choose a tag to compare

@dgesteves-release dgesteves-release released this 21 Oct 12:44
6eccaea

onsystem 0.9.0 reads design systems published as compiled packages, keeps baselines right when files move, and gives agents clearer MCP answers.

  • An app on a design-system package installed as dist/ plus type declarations now gets every rule: components, props and variants come from its .d.ts files (@dub/ui from npm gives 427 components).
  • The baseline follows a renamed or moved file, and in the GitHub Action a copied baselined line is reported where it was added. The Action runs the onsystem version the project installed, so the lockfile decides; without an install it runs its own version as before.
  • no-hardcoded-color warns when a chart-* token stands in for a status color outside a chart, and init says which status colors the theme has no token for.
  • MCP: search_components says when nothing clearly matches instead of returning a poor best guess, list_components filters and collapses icons, and get_tokens sets chart and sidebar colors apart.

Minor Changes

  • #192 eeb3af0 - The baseline follows a file that is renamed or moved. Its entries are keyed by path, so after git mv every finding of the file was new to check . and to the GitHub Action with only-changed-lines: false, and nothing was said about the entries left under the old path. Now a file with no entries of its own, new or not, takes over those of a file that no longer exists when at least half of its findings match them, and half of them match its findings, as git counts a rename from 50% similarity. A rename that changes only the case of a name counts too, also on macOS and Windows, where the old spelling still opens the file. check says which file it took for moved, and --prune-baseline records the new path. Each file that no longer exists is taken over by one file at most, so copies of a file are still new, and a finding the move adds is still reported. In the GitHub Action, a file the pull request did not change keeps the entries it took over, so a copy the pull request adds is the new one, even when the rename was merged without --prune-baseline; check alone prefers the file whose findings match the old entries most closely, then a file that kept the old name, then one in the old folder. The entries of deleted files in the folders a run checks now count in the "no longer occurs" hint.

    The GitHub Action tells check which lines a pull request changed, and the baseline accepts the findings on the other lines first. A pull request that copied a baselined line above the original passed before: the baseline accepted the copy, and the original, reported instead, was on a line the pull request did not change. Now the copy is reported, on the line it was added, and fails the check. check without the Action still accepts findings in file order.

    The GitHub Action runs the onsystem the project installed, found in working-directory or the nearest folder above it up to the repository root, so the version the lockfile pins checks the pull request, and the log says when it is not the action's own version. Without an install, it runs npx onsystem@<the action's version> as before. With an install, the action's results now follow the installed version, so this can change the result of a job that passed before: an older install can have fewer rules, and one older than this release accepts baselined findings in file order, whatever lines the pull request changed. A newer one can find more.

  • #193 b8e0853 - no-hardcoded-color warns on a chart-* token used outside a chart, such as text-chart-2 on a green "Connected" label in a theme with no success color. The class is a token, so it was counted as clean, but it changes with the chart palette and does not say what a status color says: in benchmark v2's two tasks that ask for a small green label in such a theme, all 24 runs with onsystem's skill, MCP server and hook together used a chart token for the green, and 0 of 24 without onsystem did. The warning does not fail check or block the hook, and has no fix: it says to add a status token for a status color (--success, as docs/tokens.md#status-colors shows) or to use the token that fits. Chart tokens are left alone in a file that draws a chart (it imports a chart library, renders a tag such as <ChartContainer> or <BarChart>, declares a name such as chartData, or is named for charts), in an element inside an <svg>, a <figure> or a role="img", and everywhere when the design system's own components use them outside a chart. The real-world corpus has no such finding; sidebar tokens, which apps use for their frame (bg-sidebar on a shell or header), are not reported.

    onsystem init says which of success, warning and info the theme has no color for, after the dry run, with a link to the CSS that adds them.

    icon-button-accessible-name takes a button's name from a <label htmlFor> (or the design system's <Label htmlFor>) whose value is the button's id, as for <label htmlFor={id}>Autopilot</label> next to <button id={id} role="switch">. A button is a labelable element, so that label is its accessible name, and the button was reported without one.

  • #194 a5eafef - An app on a design-system package installed from a registry as compiled dist/ and type declarations alone now gets every rule: its components, props, variant values, parts and the elements they render are read from its .d.ts files, as MUI's are. Before, such an app got the token rules and check said No design-system components found: the component rules did not run. This applies to a package that declares its onsystem config (set up with init), whatever its name; to one without a config that is named like a design system in the app's own npm scope (@acme/ui for @acme/web); and, in any app, to the dependency the app's config names as its importPath: { "importPath": "@dub/ui" } in an app on @dub/ui from npm reads its 427 components. A kit from another scope named like one (@sanity/ui, @react-email/components) is not read unless the config names it, and a package whose .tsx sources the app reads is read from them as before; one that publishes src/ beside exports into dist/ is read from its declarations. A package with no entry of its own is read from each subpath it exports (@acme/ui/button). inspect lists it on its library line (Design-system package (@acme/ui 1.0.0): 10 components from its type declarations), inspect --explain says so, or what would make it read, "libraries": false turns it off, and the app's @import of its stylesheets brings its tokens.

    Reading declarations also got more exact for MUI and any package: a capitalised export is a component only when it renders (a function returning a string, or a class without render, is not); variants that tsup inlines with cva's ClassProp are kept, since its class-variance-authority/dist/types import now resolves by path when the app's "moduleResolution": "bundler" does not; a function component's element comes from its DOM attributes when it takes no ref; props that depend on a generic component through a conditional type are left open, so they are not reported as unknown; and Tabs next to Tab, or a lone CheckboxGroup next to Checkbox, is no longer taken for its part. In a bundle's one index.d.ts, parts go by name only in families of two or more, and icons have none: @dub/ui's UserCheck is not a part of User, nor ButtonTooltip of Button.

  • #198 6bd19f5 - The MCP tools answer better where the design system is thin, large, or has no component for what the agent wants.

    • search_components says when no component clearly matches: "a dropdown menu to pick a country" in a design system with no select used to come back as CardAction, best first, because its description mentions a menu. A result now matches clearly when a word of the query is in its name, or when it has more than half of the query's words (itself or through a synonym). When some do, the others are left out; when none does, the answer starts "No component clearly matches", lists the nearest and marks each result weak: true in the structured content.
    • A shadcn/ui component without JSDoc, laid out as npx shadcn add writes it (Badge in components/ui/badge.tsx: the file named for it, in a ui folder), is described by what shadcn/ui's component of that name is for, said as shadcn/ui's (shadcn/ui's Badge: A small label for a status, a count or a category.), in list_components, get_component, resources and the search index, so "status label" finds Badge first in a stock app. A component of the same name elsewhere, such as a design system's src/sheet.tsx, gets no borrowed description. A component with no description is listed without one, not as "No description."
    • list_components takes query, kind ("component" or "icon") and limit. Icons, the components that render an <svg>, are named on one line when there are five or more from one import, or from one folder for an icon set with a module per icon (components/icons/check.tsx, each named with its module), instead of one entry each, in the text and in the structured content, which has them under icons and leaves out empty variants and subcomponents. kind: "icon" lists them one by one.
    • get_component lists the parts before the props, cuts a prop's description lon...
Read more

v0.8.0

Choose a tag to compare

@dgesteves-release dgesteves-release released this 16 Oct 09:57
9603f20

onsystem 0.8.0 reads more of the design system you actually use, reports more of the hardcoded styling agents write, and checks Codex edits. It is a minor release: code that passed can get new findings, so run onsystem check . --update-baseline once after upgrading if you use a baseline.

  • More findings, safer fixes: Tailwind white and black are reported, class strings kept in variables in the same file are followed, and icon-only asChild links are checked. A background is never fixed to a token where the text on it would stop standing out in dark mode.
  • Zero config reads more of your design system: an installed package named like one (@acme/ui) that publishes its .tsx sources, and the folders npx shadcn add copies other registries' code into, read-only next to components/ui. init writes those registry folders into the config.
  • exclude now adds to the defaults (tests, stories, mocks and .d.ts files stay out) and leaves out the same files in check, at a monorepo root and in the ESLint plugin.
  • check and check_ui read only JS and TS files, so check $(git diff --name-only) no longer parses CSS or Markdown as TSX, and --require-design-system fails when nothing was checked.
  • The plugin's hook now checks edits in Codex, which installs the Claude Code plugin and runs its hook on apply_patch.

onsystem's GitHub Action at 0.8.0. Pin it with uses: dgesteves/onsystem@v0.8.0, or use @v0 to follow each release.

Release notes: https://github.com/dgesteves/onsystem/releases/tag/onsystem%400.8.0

onsystem@0.8.0

Choose a tag to compare

@dgesteves-release dgesteves-release released this 16 Oct 09:57
9603f20

onsystem 0.8.0 reads more of the design system you actually use, reports more of the hardcoded styling agents write, and checks Codex edits. It is a minor release: code that passed can get new findings, so run onsystem check . --update-baseline once after upgrading if you use a baseline.

  • More findings, safer fixes: Tailwind white and black are reported, class strings kept in variables in the same file are followed, and icon-only asChild links are checked. A background is never fixed to a token where the text on it would stop standing out in dark mode.
  • Zero config reads more of your design system: an installed package named like one (@acme/ui) that publishes its .tsx sources, and the folders npx shadcn add copies other registries' code into, read-only next to components/ui. init writes those registry folders into the config.
  • exclude now adds to the defaults (tests, stories, mocks and .d.ts files stay out) and leaves out the same files in check, at a monorepo root and in the ESLint plugin.
  • check and check_ui read only JS and TS files, so check $(git diff --name-only) no longer parses CSS or Markdown as TSX, and --require-design-system fails when nothing was checked.
  • The plugin's hook now checks edits in Codex, which installs the Claude Code plugin and runs its hook on apply_patch.

Minor Changes

  • #180 9f40cac - check and check_ui read only JS and TS files. A CSS, Markdown or JSON file named outright or matched by a glob, as check $(git diff --name-only), lint-staged's "*" or check "**/*" passes them, was parsed as TSX and gave hundreds of syntax errors (209 for a shadcn/ui app/globals.css), so the run failed on files with no UI code in them. check now skips such a file and counts it in the summary line (3 files that are not JS or TS skipped). check_ui answers with an error for a path or filename that is not .tsx, .jsx, .ts or .js (or .mts, .cts, .mjs, .cjs): a .vue or .css file was checked as TSX before.

    --require-design-system also fails (exit code 2) when the folders and globs it is given leave no file to check, so CI cannot pass by checking nothing: a JavaScript app whose pages are .js files, a job that runs in a design-system package, whose files are all its own components, or a glob that matches only styles. Files named outright that are not JS or TS still pass with nothing to check. This can fail a job that passed before.

    Folders still stand for their .tsx and .jsx files, and check now names the .js files in them that have JSX (Next.js's JavaScript template writes app/page.js) with the command that checks them, such as check . "**/*.js", in place of "no files match" for a folder of .js pages, and as the first line of the --require-design-system failure. In the GitHub Action, paths: . **/*.js checks them.

    A run with 50 findings or more ends, in the default output, with the count per rule and the ten Tailwind palette classes it found most, each with the token to use. --format json, sarif and github print the findings alone, as before.

  • #183 7159b5b - exclude adds to the defaults instead of replacing them. A config whose exclude lists folders without repeating the defaults now also leaves out tests and stories (*.test.tsx, *.stories.tsx, __tests__, __stories__ and __mocks__ folders) and .d.ts files, which it checked before. To check one of them again, add the default with ! in front ("!**/*.{test,spec,stories}.{ts,tsx,js,jsx}"), or pass --include-tests to check tests and stories for one run; a ! pattern that names no default gets a warning. A design-system package's exclude adds to the defaults for finding its own components too.

  • #176 e7335d8 - Zero config now reads a design-system package installed from a registry when it is named like one (@acme/ui, @acme/design-system) and its exports point at the .tsx or .jsx sources it publishes, directly or through barrels, as it reads a workspace package, with no config in the app or the package. Before, such an app got no components and no tokens: a package that publishes src/, such as one whose exports point at ./src/index.ts, was skipped as compiled. Its exported stylesheets are its tokens, with the ones of the package they @import, and an app stylesheet's @import "@acme/ui/globals.css" is followed into node_modules when the app's config names no tokens. Other installed packages, such as tailwindcss, are not read. Its exports are read through the conditions an app resolves (import, require, module-sync, node, module, browser, style, default), and the first of them decides, for its components and its stylesheets alike, so a package whose source or expo-source condition points at src/ before a default that points at its build, such as @expo/ui or one built with react-native-builder-bob, is not read either. Unlike in a workspace package, exports that point at dist/ are not followed back to src/, and nothing compiled in the package is read (JavaScript, JSX or CSS in dist/, build/, lib/, out/, esm/ or cjs/, or an export that points at JavaScript anywhere, such as ./index.js or ./es/index.mjs, whose re-exports are not followed), so a component kit named like a design system that publishes src/ next to dist/ for its source maps is not read as a second design system. With pnpm the package is read in node_modules/.pnpm, so the props its components take from their own dependencies resolve. A package whose exports point at build output is still not read, and inspect --explain now says why: its exports point at build output (node_modules/@acme/ui/dist/index.mjs), not at component sources, instead of calling every installed package compiled. An app whose own config leaves components unset now picks up such a package too.

  • #183 83a40a0 - Zero config reads the folders npx shadcn add copies other registries' code into as read-only design systems, next to components/ui: for each registry in the registries of components.json, the folder of that name under the components alias (@signoff-ui → components/signoff-ui, @ai-elements → components/ai-elements). Before, that code was checked as the app's own: in VoltAgent's chatbot starter, which the corpus now checks, 11 of the 14 findings of check . were in components/ai-elements, and the next shadcn add could bring findings in registry files nobody edited, which failed check and the GitHub Action. Now check, the Claude Code hook, the ESLint plugin and the Action skip those files, as they skip components/ui; search_components, get_component and list_components find their components, and get_component says which registry one was installed from and that changes to it belong upstream; and the app's code is checked against them, so <DiffReview tone="danger"> or <Message.Header> is a finding, and so is an icon-only registry button without a name. None of them is suggested in place of a native element, and the helpers in a folder's lib/ (signoff-ui's Img and icons) are left out of list_components and search_components. inspect names the folders on its detected line, and inspect --explain says why each was read or not. A folder is not read when the app's components come from a workspace package imported through its exports, and code a registry copies loose into components/ is still checked as the app's. init names those folders on its "Found the design system" line instead of a note of its own.

    A config that lists such a folder in components, as init writes it, reads it the same way: its components are no longer suggested for native elements (signoff-ui's Img for an <img>); map one in elements to have it suggested. A folder that components.json names in no registries entry is read as before.

  • #174 005d204 - check, check_ui, the hook and the ESLint plugin report more of the hardcoded styling agents write, so code that passed can get new findings:

    • no-hardcoded-color reports Tailwind's white and black (bg-white, text-black, border-white), which passed while bg-[white], style={{ color: "white" }} and fill="white" were reported, so bg-white dark:bg-zinc-900 had only its dark half fixed. They are fixed only to a token of exactly that color (bg-white → bg-background in a stock shadcn/ui theme), and suggested without a fix otherwise. A theme that defines --color-white or --color-black makes them its tokens, which are not reported. White or black text, fill or stroke is a warning without a fix, since its token depends on the surface under it. A hardcoded background (bg-white, bg-zinc-900, bg-[[#111](https://github.com/dgesteves/onsystem/issues/111)]) under hardcoded text that keeps no fix (white or black, text-black/80, or a color such as text-neutral-800 held back as another shade), on it, inside it or inherited from around it, is not fixed to a token unless the text still stands out from it in every mode, by 0.25 of lightness or 3:1 contrast as it did on the hardcoded color: bg-black text-white would become bg-primary text-white, white on near white in dark mode, an...
Read more

onsystem@0.7.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 04:01
1d54d1e

onsystem 0.7.0 reads more of your theme, makes its fixes safer and closes a file-read hole. It is a minor release: read the upgrade note under Minor Changes before you update.

  • Security: component, token and docs files are read only when their real path is in the project, its workspace or git repository, or an installed package. A cloned repository can no longer make get_component return a file elsewhere on the machine through a committed link or a ../ path.
  • Zero config now finds color tokens kept in a shared Tailwind config package, as Dub keeps them, so such projects get color findings for the first time. Run onsystem check . --update-baseline once after upgrading, and pin the GitHub Action to a commit SHA if you need a stable check: @v0 now runs 0.7.0.
  • A color whose hue has no token in your theme is now a warning that names two ways out, not an error that pushed agents to a gray or a chart token.
  • Fixes no longer change what code means: eslint --fix leaves files that compile, color fixes keep hover states, opacity and status colors, guessed props and variant values are suggested, not applied, icon-button labels are fixed only when the icon says what the button does, and 1px hairlines are left alone.
  • check --prune-baseline drops fixed findings without accepting new ones, and check, check_ui and inspect now say when a component library is not read yet or the dependencies are not installed. The Claude Code hook holds an Edit to the lines it changed and uses a monorepo root's config and baseline. Thanks to @CRYPTONIKAV for the SVG mask fix (#110) and the hairline spacing fix (#153).

Minor Changes

  • #107 9ecb46d Thanks @dgesteves! - Zero config now finds color tokens kept in a shared Tailwind config package, as Dub keeps them in packages/tailwind-config: it follows the @imports of the stylesheets it finds, by a path or into a workspace package (@import "@acme/theme" in Tailwind v4), reads presets that a package exposes only through exports, takes the stylesheets of a shared preset's package that set the colors its config maps, and reads bare RGB channels (--content-error: 220 38 38) as colors where the config reads them with rgb().

    Upgrading from 0.6.0: a project that keeps its color tokens in such a package got no color findings from 0.6.0, which said "No color tokens found: no-hardcoded-color did not run". Now the rule runs and reports the colors it skipped: in Dub's apps/web, 10,024 errors and 164 warnings that a baseline written with 0.6.0 does not hold, so check exits 1 there. Run onsystem check . --update-baseline once after upgrading to accept them; in Dub the 1,052 findings 0.6.0 recorded still match, and check exits 0 again. The warnings are colors whose hue has no token in the theme, and they do not fail check unless you set --max-warnings (or fail-on: warning in the Action). The GitHub Action at @v0 moves to each release when it is published, so pin it to a commit SHA if you need a stable check, and move the pin when you update the baseline.

Patch Changes

  • #170 57aef3b Thanks @dgesteves! - check --prune-baseline drops the baseline entries that no longer occur and accepts nothing new, and it is the command check now prints when baselined findings get fixed. The command it printed before, --update-baseline, also recorded every new finding of the run, so following it next to a new error accepted the error. The printed command keeps --baseline, --root, --config, --components, --tokens, --docs, --include-design-system and --include-tests, so it rewrites the same file from the same findings; before, with --baseline <file> it wrote a second baseline at the root. --update-baseline creates the baseline's folder, and with --format json, sarif or github prints its summary on stderr, so stdout stays in the format.

    The baseline now records the onsystem version that wrote it and the rules that ran. When a rule that did not run then reports findings, as when an upgrade finds tokens an older version missed, check says they are new to the baseline rather than to the code, and how many other new findings --update-baseline would accept with them. A baseline without these fields is read as before, and older versions of onsystem read the new ones without complaint.

  • #147 a19115d Thanks @dgesteves! - no-hardcoded-color no longer offers two kinds of fix that changed what a color means, which eslint --fix and applyFixes applied unasked. A class under a condition (hover:, focus-visible:, md:) is not fixed to the token the same class list has or gets without it, nor the other way round, unless the two already have exactly the same color: bg-[#2563eb] hover:bg-[#1d4ed8] with a blue primary became bg-primary hover:bg-primary, and the button lost its hover. A slight change counts too: bg-[#fff] hover:bg-[#fafafa] keeps its hover. The base class is still fixed, and the message suggests keeping the two apart, such as with hover:bg-primary/90; dark: classes still get the same token, and so do two hardcoded classes of exactly the same color (border-red-500 focus:border-red-500), whose condition changed nothing before either, and classes for a pseudo-element or other elements (placeholder:, prose-a:, [&_svg]:). A fix to a token that is not a status color is held back when a status color of the color's hue is close too and at most ΔE 0.03 further: with a rose brand next to a red destructive, bg-[#ef4444] on a payment-failed alert became bg-brand, so the error would follow the next rebrand. The message now offers both (bg-destructive if the color shows a status, otherwise bg-brand). The brand's own color, and reds that destructive matches, are still fixed.

  • #141 a83de7c Thanks @dgesteves! - no-hardcoded-color counts opacity when it matches a color to a token. It used to compare only lightness, chroma and hue, so a translucent color matched its opaque token and the fix made it opaque: bg-[#e7000b33] (the red at 20%) became bg-destructive, rgba(255, 255, 255, 0) became the opaque background, and in shadcn/ui's dark theme dark:border-[#ffffff] became dark:border-border, which is 10% white. Now a token more than 0.02 apart in opacity is never a fix, and the message gives both opacities (destructive is opaque, and this color is 20% opaque.), so the color can keep its opacity with a modifier (bg-destructive/20). Colors and tokens of the same opacity still match (dark:border-[rgba(255,255,255,0.1)] → dark:border-border, and 15% white → dark:border-input), an opacity modifier still stays on the token (bg-[#ffffff]/40 → bg-background/40), and "same value as" lists only tokens of the same opacity.

  • #126 7e11d9b Thanks @dgesteves! - no-hardcoded-color reads tokens whose names start with on- (Material's) as text colors, and those that start with content- (Dub's) too, unless the name goes on to say border or background (content-border, content-background). text-neutral-400 now matches Dub's content-muted, and border-*, ring-* and bg-* classes are no longer fixed to a text color. When the only close token is made for another role, the message names it ("content-subtle has this color, but it is meant for text") instead of saying that no token is close. A Tailwind palette class is now fixed only to a token that matches it (ΔE under 0.02), or to one that is nearest the class's own shade and within ΔE 0.037 of it, the most Tailwind v3 and v4 differ on one shade, so tokens that copy v3's palette (text-red-600 to a token holding v3's red-600) keep their fix. Other close tokens are still suggested, without a fix, so eslint --fix leaves them, and the message says why and what to do: content-default for text-neutral-600 is one shade darker (neutral-700), and a token at neutral-500 for text-slate-500 is closest to neutral-500, not slate-500.

  • #157 efb3888 Thanks @dgesteves! - In an app whose design-system package publishes only compiled dist/, not its component sources, no-unknown-component no longer reports every component the app imports from the package ("Button" is not a design-system component (imported from "@acme/ui")) when the package's config sets importPath, as init writes it for a package apps import by one name. Those errors blocked the Claude Code hook and failed CI on real components, while check said in the same run that the component rules did not run. With no component to check against, imports from the package are now left alone, by the CLI, the MCP server, the ESLint plugin, the hook and the Action. The token rules and icon-button-accessible-name on native elements still run. To check the package's components in those apps, add src/ to files in its package.json.

  • #131 [43b66c9](https://github.com/dgesteves/onsystem/com...

Read more

onsystem@0.6.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 20:31
e801bcd

Minor Changes

  • #92 ebc7dbc Thanks @dgesteves! - onsystem init in a design-system package now declares its config in its package.json ("onsystem": "./onsystem.config.json") and writes ONSYSTEM.md for the teams of the apps that use it, and an app with no config of its own takes on the config a dependency declares, from the workspace or from node_modules, in the CLI, the MCP server and the ESLint plugin alike.

    In a monorepo, an app with no config of its own that depends on such a package reads the package's config rather than the monorepo root's, so the root config's rules no longer apply to it.

  • #87 6c20ad8 Thanks @dgesteves! - Import and export design tokens as DTCG 2025.10 and DESIGN.md. DTCG files now read $root tokens and groups that $extends another, which were left out before. A DESIGN.md's front matter is a token source, which zero config reads when it finds no stylesheet or DTCG file. onsystem export --format dtcg|design-md prints the tokens it found, or writes them with --out: the DTCG file keeps colors as color objects, sizes as { value, unit }, aliases as references, and the token's name, the custom property, the Tailwind theme variable and the dark mode under $extensions["com.github.dgesteves.onsystem"], so reading it back gives the same findings, with the same token names.

  • #86 1d8000e Thanks @dgesteves! - MUI apps get a design system with no config: when a project depends on @mui/material and it is installed, onsystem reads MUI's components from the package's type declarations with the TypeScript checker, for MUI 5 to 9. It reads each component's props (Button's href signature included), the values of its string-literal props as variants (variant, color, size, TextField's generic variant), values the app adds by module augmentation or in its theme (teal: { main }, components.MuiChip.variants), the element it renders and the module that default-exports it. check, check_ui and the hook then report invented MUI components, props and variant values, skip props when component is set, and count a Tooltip with a string title as its child's accessible name. Palette colors, the spacing unit, border radius and font family come from the createTheme options the app writes out, following constants it imports by a relative path. Where the app has components of its own, those win a shared name in what the tools offer and suggest, while code that imports MUI's is checked against MUI's. inspect shows the library on a library line, and a workspace package that uses MUI is a project of its monorepo root. "libraries": false in the config turns this off, and a list (["mui"]) reads only the libraries it names. check --require-design-system, and the GitHub Action, which turns it on by default, count MUI's components as the app's design system: when the theme's colors cannot be read, as with a palette from Sass variables, the check runs and says that no-hardcoded-color did not. Libraries are adapters (src/libraries), so Chakra UI, Mantine, HeroUI and Radix Themes can follow.

Patch Changes

  • #79 ebb0fa8 Thanks @dgesteves! - The GitHub Action's description is shorter than 125 characters, so the release can be published to the GitHub Marketplace.

  • #80 037b19c Thanks @dgesteves! - init writes a config where check and inspect already find the design system without detecting one: an app with components/ui and no components.json. It spells out the default globs that match and the token files found, with --yes, without a terminal, and in one (where it no longer asks where the components are). Before, init --yes and init without a terminal exited 2 with "zero config found no design system".

  • #96 a380586 Thanks @dgesteves! - init names onsystem.baseline.json in its next steps only when it wrote one. Without a terminal, it says that it skipped the baseline question and that onsystem check . --update-baseline records one. inspect --explain lists each docs glob once, where a shadcn/ui app listed components/ui/**/*.{md,mdx} twice. The MCP server's load line counts components as inspect does: loaded 4 components (+6 parts), not loaded 10 components.

  • #84 7042a12 Thanks @dgesteves! - The package's npm page links to the website, onsystem.vercel.app, rather than the README on GitHub.

  • #90 8d62016 Thanks @dgesteves! - A Tailwind v3 borderRadius.DEFAULT token is suggested as rounded, not rounded-DEFAULT.

  • #89 58e6b63 Thanks @CRYPTONIKAV! - Tailwind v3 projects: radius values set in tailwind.config borderRadius, or in a preset it imports, are read, so 4px in a project whose rounded-sm is calc(var(--radius) - 4px) suggests rounded-sm. Thanks @CRYPTONIKAV.

  • #80 8549f8f Thanks @dgesteves! - npx onsystem with no command, typed in a terminal, says on stderr what it is before it starts the MCP server, which otherwise sits waiting on stdin and looks hung: where to add it to an agent, and that inspect or check . is what to run by hand. An MCP client starts it with a pipe, not a terminal, so clients see nothing new.