Verify
The doctor audits what the registry cannot see from inside one install: imports that resolve, theme order, app blocks fighting the skin, and the data-skin stamp.
Run the doctor
The doctor ships with the install. It is read-only: it reads the CSS entry named in components.json, follows every
import it finds, and looks for the data-skin stamp.
node <install dir>/scripts/control-ui-doctor.mjsKeep it runnable as a script, so a later session — or your agent — can check the wiring without this page:
{ "scripts": { "control-ui:doctor": "node components/control-ui/scripts/control-ui-doctor.mjs" }}What a clean install looks like
$ npm run control-ui:doctorcontrol-ui doctor: clean.Anything else is one of the five checks below. Errors exit non-zero and block: styling on top of them is what reads as a broken install afterwards. Warnings exit zero and are deletion offers, named file by file and token by token.
What it reports
../components/… and your entry sits somewhere else. Run node <install dir>/scripts/fix-css-imports.mjs; one wrong prefix silently unstyles everything below it.*.control-ui-theme.css composes over the pack, so source order is what makes it win. The same script repairs the order.@theme block redeclaring Control UI keys. Tailwind merges every @theme in the build and the last declaration wins, so the block detaches those utilities from the skin — a hardcoded font splits the app into two typefaces, a redeclared --radius collapses every corner.:root or .dark block declaring skin token names. The skin out-specifies it, so those declarations are dead; a hybrid block keeps the names the skin never declares painting a second palette beside it.data-skin stamp. Without an id the installed skin declares, every token is undeclared and portalled surfaces render unstyled.Two flags beyond the wiring audit
--contrast resolves the theme contract's required color pairs straight from the installed CSS — no browser, no
network — and prints a per-mode tally with every failure named:
$ node components/control-ui/scripts/control-ui-doctor.mjs --contrastlight: 55/55 passdark: 55/55 passPairs painted by a rendered recipe (popup items, sidebar, tabs, solid buttons, tooltip, focus ring, badges) are audited at /theme-accessibility.It reads the Tailwind palette, the core theme, the installed skin theme and every *.control-ui-theme.css the
entry imports, in that order, so it sees exactly what the browser resolves. A pair a recipe paints is not guessed at;
those live in the rendered audit at /theme-accessibility. A failure names the pair, its
ratio and the threshold, and exits non-zero. A value the engine cannot model, such as color-mix(), is reported
as unverified instead of passing.
--emit-css turns a theme artifact into the one derived stylesheet an app imports:
$ node components/control-ui/scripts/control-ui-doctor.mjs --emit-css midnight.control-ui-theme.jsonIt writes midnight.control-ui-theme.css beside the artifact. Never hand-write those selectors: the emitted scope
doubles the data-skin attribute to match the pack's own weight, which is what hands the win to source order.
The contract
The doctor audits wiring, never taste. It cannot see a token that is ugly, only one that is undeclared, dead, or fighting the skin.
Each deletion it offers is a change to source you own, so it names the file and the token instead of writing them: the sweep stays yours to read as one diff.
Run it after every install, update, and migration. A migration you said yes to ends clean, not hybrid.