uidex scan

CLI reference for uidex scan, --check, --audit, --json, scaffold, configuration, and the JSDoc migration path.

uidex scan is the entry point to the static analyser. It walks your sources, extracts data-uidex* attributes and export const uidex = {...} declarations, and emits a typed registry (src/uidex.gen.ts by default) that the runtime SDK consumes.

The gen file is auto-generated and committed — consumers import types from it directly.

Commands

Run uidex scan after any data-uidex* attribute change, export const uidex edit, or new flow.

--check

Compares the generated registry to the committed uidex.gen.ts byte-for-byte. Exits non-zero on drift. Intended for CI.

--audit

Runs --check plus the lint rules. Exits non-zero on errors. Warnings are printed but do not fail.

--json

Emits diagnostics as a JSON array on stdout for tooling consumption.

scaffold

Emits a Playwright spec with exactly one test() per declared acceptance criterion. The describe carries { tag: "@uidex:flow" }. Each test has a // TODO body for you to fill in.

  • Deterministic file path: the scaffold writes to <flows-glob-root>/flow-<id>.spec.ts.
  • Idempotent: re-running will refuse to overwrite an existing file unless --force is passed.
  • Without --force, exits with a clear message naming the existing path.

Audit coverage

uidex scan --audit emits one diagnostic per uncovered acceptance criterion. The diagnostic identifies the entity, the criterion text, and the source location, and includes a hint:

Disable per-section with audit.acceptance: false in .uidex.json.

Bundler plugins

Optional opt-in watch integrations that regenerate uidex.gen.ts on file save:

  • @uidex/vite-pluginimport { uidex } from "@uidex/vite-plugin"; add to plugins: [uidex()]. Dev server integrates with Vite's HMR error overlay; production builds run scan --check once and fail on stale gen.
  • @uidex/webpack-pluginnew UidexPlugin() in webpack.config.js. Hooks watchRun / done.
  • @uidex/next-pluginwithUidex(config) wrapper for next.config.*. Installs the webpack plugin on the server compiler. Webpack transport only today; Turbopack support is a follow-up because the Turbopack plugin API is not yet stable.

All three wrap the shared @uidex/plugin-core watcher so behaviour, debounce window (≥100ms coalescing), and diagnostic shapes are identical across bundlers.

Projects without a plugin fall back to uidex scan on demand.

Configuration

.uidex.json is flat. Example:

typeMode: "strict" (default) emits literal id unions — tsc rejects unknown ids at the satisfies Uidex.X site. typeMode: "loose" emits string — a temporary migration knob for repos mid-migration from legacy JSDoc.

Multi-source (monorepo) example:

Migration from legacy JSDoc

Previous versions of uidex recognised JSDoc tags (@uidex page|feature|widget, @acceptance, @uidex:not-flow) as the module-scoped annotation surface. Those tags have been removed. The scanner no longer registers anything from them; they only trigger a lint warning.

The legacy-jsdoc lint rule

uidex scan --lint (and --audit) emits a warning for every legacy tag it finds, pointing at the replacement:

Detected tags:

  • @uidex page <id>export const uidex = { page: "<id>", ... } as const satisfies Uidex.Page
  • @uidex feature <id>export const uidex = { feature: "<id>", ... } as const satisfies Uidex.Feature
  • @uidex widget <id>export const uidex = { widget: "<id>", ... } as const satisfies Uidex.Widget (keep the data-uidex-widget attribute on the DOM root)
  • @acceptance <text> → one entry in the acceptance: string[] array on the enclosing uidex export
  • @uidex:not-flowexport const uidex = { notFlow: true } as const satisfies Uidex.NotFlow at the top of the spec file

Step-by-step

  1. Run uidex scan --lint to surface every legacy JSDoc location.
  2. For each page / feature / widget / primitive / flow, add an export const uidex = { ... } as const satisfies Uidex.<Kind> at the top of the module. Move @acceptance items into an acceptance: string[] field. Remove the JSDoc block.
  3. Run uidex scan to regenerate uidex.gen.ts.
  4. Commit the regenerated file.

The typeMode: "loose" migration knob

.uidex.json accepts a typeMode: "strict" | "loose" field (default "strict").

  • "strict"FeatureId = "billing" | "auth" is a literal union. tsc rejects unknown ids at the satisfies Uidex.X site. This is the end state.
  • "loose"FeatureId = string. No compile-time id validation, minimal gen-file diff. This is a temporary migration knob.

Why it exists: during a migration, half the repo may have landed on the new export const uidex surface while the other half still carries legacy JSDoc. In strict mode, tsc floods with "not assignable to FeatureId" errors on the migrated half, because the emitted FeatureId union is missing the ids still expressed in JSDoc (which the scanner no longer registers). Setting "typeMode": "loose" silences those errors without silencing tsc globally.

Flip back to "strict" and regenerate as soon as every JSDoc tag is gone. typeMode: "loose" is not promoted for long-term use.