A specification for measuring Rudiment UI adoption with the rudi namespace: why it’s the measurement foundation, what rudi check emits, and how to roll it out through GitHub Actions.
Why the namespace is the measurement instrument
In React codebases, a component’s identity lives in import statements, which get aliased, wrapped, re-exported, and renamed. A scanner has to trace module graphs and guess whether <Button> is a design system button, wrapper, or local recreation. Mews found import-based measurement inaccurate for this reason.
Custom elements dissolve that problem. The platform requires every custom element name to contain a hyphen, and registration is global, so <rudi-button> means the same thing in every file, template language, and framework: no alias, no rename, no ambiguity.
- Identity is stable at the usage site. A scanner matches the tag name. It doesn’t trace imports, resolve barrels, or guess about wrappers.
- The namespace survives every layer. The same
rudi-string appears in source templates, JSX, Vue and Svelte templates, Lit template literals, server-rendered HTML, and the live DOM. You can measure at build time with static analysis or at runtime withquerySelectorAll, and the numbers reconcile. - Recreation detection becomes a set difference. Every hyphenated tag is a component by definition. Partition all custom element tags into
rudi-*and everything else. The “everything else” set is your recreated-component report, with names attached. - Tokens inherit the same property.
--rudi-color-*custom properties are collision-free and greppable. Anyvar(--rudi-*)reference is adoption. Any hardcoded color value in product CSS is measurable drift. - The namespace works with zero tooling. GitHub code search for
rudi-across the organization gives you a rough adoption census before you build anything. The CLI below makes that structured, repeatable, and trackable. - Framework-agnostic measurement matches a framework-agnostic library. Rudiment’s rewrite to web components means consumers may be in React, Vue, Astro, or plain HTML. A tag-based scanner covers all of them with one strategy. React-centric tools like react-scanner and Radius Tracker can’t offer that.
One limitation: static scanning misses tags composed at runtime from dynamic strings. This is rare, and an optional later phase, runtime DOM sampling, closes the gap.
The rudi check CLI
rudi check is a read-only scanner. Run it at the root of any repository. It parses source files, counts namespace usage and drift signals, and emits one JSON report.
What it scans
| Source | What it extracts |
|---|---|
.html, .astro, .vue, .svelte, .jsx, .tsx, .js, .ts | Hyphenated element tags, split into rudi-* and foreign; bare interactive HTML elements (button, dialog, input, select, details); attributes passed per rudi-* element |
Tagged template literals (Lit html, css) | Same tag and attribute extraction inside the literal |
.css, and <style> blocks | var(--rudi-*) references; non-token color values (hex, rgb(), hsl(), oklch()); --rudi-* overrides |
package.json and lockfile | Declared and resolved versions of @rudi/* packages |
Counting rule: count source-level references, not rendered instances. A rudi-button inside a shared header counts once, no matter how many pages render it. Repeat that in every report, or the numbers get misread.
Output schema
{
"schemaVersion": 1,
"generatedAt": "2026-07-21T06:00:00Z",
"repo": "acme/checkout",
"commit": "9f2c1ab",
"scanner": { "name": "@rudi/check", "version": "0.1.0" },
"versions": {
"declared": { "@rudi/core": "^2.3.0" },
"resolved": { "@rudi/core": "2.3.1" }
},
"elements": {
"rudi-button": {
"count": 42,
"files": 17,
"attributes": { "variant": 40, "disabled": 12, "class": 31 }
},
"rudi-dialog": { "count": 6, "files": 4, "attributes": { "open": 6 } }
},
"tokens": {
"references": { "--rudi-color-surface": 12, "--rudi-space-3": 44 },
"overrides": { "--rudi-color-accent": 2 }
},
"foreign": {
"customElements": { "acme-card": 9, "acme-toggle": 3 },
"bareInteractive": { "button": 4, "dialog": 1 },
"hardcodedColors": { "count": 23, "files": 9 }
},
"coverage": {
"elementCoverage": 0.87,
"tokenCoverage": 0.79
}
}
Field notes:
schemaVersionlets you evolve the format without breaking the dashboard. Bump it on any breaking change.elements[*].attributesis the web components equivalent of prop tracking: a highclassorstylecount signals that an element’s defaults don’t fit, and a never-used attribute is a removal candidate for the next major version.foreign.customElementsis the recreation report. Names are included on purpose: “the checkout team builtacme-toggle, used 3 times” starts a specific conversation, and doubles as roadmap demand data.elementCoverage= rudi element references ÷ (rudi + foreign custom elements + bare interactive elements).tokenCoverage= token references ÷ (token references + hardcoded color values).- Version data rides along in every report, so version-lag tracking needs no separate pipeline.
Commands
rudi check # human-readable summary to stdout
rudi check --report out.json # machine-readable report
rudi check --ignore "**/legacy/**"
Ship it as a report-only tool first. Don’t fail builds on adoption thresholds. Measurement tooling that blocks merges gets uninstalled; enforcement, if you want it, belongs in a later phase after teams trust the numbers.
GitHub Actions rollout
Use a central scanner rather than asking every team to install a workflow. Distributed per-repo workflows are a later phase, once teams want the report in their own PRs.
- Publish
@rudi/checkto your registry. - Create a
rudiment-metricsrepository to hold reports and the dashboard. - Add a scheduled workflow (weekly is enough) that lists organization repositories through the GitHub API, shallow-clones each, runs
rudi check --report, and commits the output todata/<repo>/<date>.json. - Build the dashboard as a static page that reads the JSON history. Astro fits, and it can live in the Field Guide, next to the docs that improve it.
- Later, offer an opt-in reusable workflow so teams can surface their own report as a PR comment.
Minimal workflow sketch:
name: rudiment-adoption-scan
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install -g @rudi/check
- run: node scripts/scan-org.js # clones repos, runs rudi check, writes data/
env:
GH_TOKEN: ${{ secrets.ORG_READ_TOKEN }}
- run: |
git config user.name "rudi-bot"
git config user.email "rudi-bot@users.noreply.github.com"
git add data && git commit -m "chore: weekly adoption scan" && git push
Questions the data answers
- Version lag: resolved
@rudi/coreversions across repositories, and how long each lags behind. - Component health: usage per component, attribute histograms, and override rates, ranked for improve-or-deprecate calls.
- Recreation rate: foreign custom elements per repository, trending down (ideally) as gaps get filled.
- Token drift: hardcoded color counts per repository, an early sign the theme system doesn’t meet a need.
- Coverage trend: element and token coverage per repository, week over week.
Right-sizing
This spec scales down as well as up. At current Rudiment scale, phase one is a single script plus GitHub code search. The JSON schema is worth getting right early, since history only accumulates from the day you start recording it. The enterprise patterns (central scanning, dashboards, deprecation codemods) layer on without rework, since the namespace contract never changes.