/writing
Audit Scripts for a Design System
· 3 min read
A design system I work on had a style guide living in a contributor doc. It covered how to name stories, how to structure MDX pages, which imports were allowed, how to keep the public API stable, and how to use design tokens.
Contributors had to remember all of it, and reviewers had to check it by hand. I turned the rules into a set of audit scripts behind one command. They now block any pull request that drifts.
The umbrella
One command runs every audit at once. Each script pays a startup cost, and running them one after another adds all of those up. The runner starts them in parallel, buffers each one’s output, then prints the results in a fixed order. The log reads the same every time, whatever order the checks actually finished in.
Each audit also runs on its own. The command takes a subcommand for a single check, plus flags for the ones that need a deliberate way to regenerate their baseline, like the API surface snapshot.
The categories
Audits group by what they protect, not by what they run.
Story shape. Every component ships its stories file and docs page in the expected shape. Compound components declare their sub-shapes in story meta, so the docs panel renders one props table per sub-shape. No prop spreading on components that count as stable surface.
Test coverage. Every declared prop on an exported props type has at least one positive type test. Every stable component has a property test next to it. Interactive elements get keyboard exercise in their play function.
Public contract. A generated snapshot of the public API diffs against the committed baseline. Every declared prop carries a JSDoc comment, so the docs panel shows descriptions and not just types.
Codebase hygiene. No relative imports inside the design system. No hardcoded colors or pixel values in component CSS. Every stable component ships a Figma Code Connect file so it shows up in Figma’s dev-mode panel.
The categories matter more than the individual scripts. A new audit goes into one of the same four buckets, so the list stays flat instead of growing branches.
What it governs now
Forty-five checks run on every pull request, over sixty-eight components. Sixty of those components also ship a Figma Code Connect file, which is itself one of the checks: a component is not finished until it shows up in the designers’ dev-mode panel.
That last one is the pattern worth copying. The rule does not describe a convention, it defines what finished means, and it applies to a component nobody has written yet.
What I learned
A general-purpose linter knows nothing about story exports, MDX structure, where property tests live, or Figma mappings. Each script is short because the rule it checks is short. Every one has the same shape: walk the design system, find violations, fail with the list. Adding a check is one new file and one new entry.
The value is not in the scripts. It is in what reviewers stopped doing. Story shape, imports, token usage: nobody reads for those anymore. Contributors get feedback from CI in seconds instead of waiting for a review round, and new contributors learn the conventions from what the checks enforce rather than from whatever the doc happens to mention. Review is left for the parts that need judgment.
More writing
Deploying Swagger UI for Bacen's Pix API
A pull request to the Brazilian central bank that was closed without ever being merged, and shipped anyway. Rendering an OpenAPI spec as docs, from CI.
· 2 min read
Cutting Backend CI by 76%
A Django test pipeline went from a median of 18.5 minutes to 4.5. Almost none of it came from making tests faster. The last part was a connection budget.
· 6 min read
Test Layers for a Design System
Type tests, an API surface snapshot, story-level token-cascade guards, and property tests. Layered testing that catches the failure modes a design system actually has.
· 3 min read