export const uidex

The module-scoped authoring surface — one named export per file, plain AST literals only.

One named export per module, on the file that defines the entity. The RHS must be a plain AST literal — object / array / string / number / boolean literals, optional as const, optional satisfies Uidex.<Kind>. Identifier references in value position, calls, spreads, conditionals, and template literals with expressions are rejected with an error diagnostic.

Kinds

Each kind has a unique discriminator field plus a small set of allowed companion fields.

Setting page: false / feature: false / primitive: false explicitly opts a module out of auto-promotion.

Page

Feature

Widget

The widget id appears in two places: data-uidex-widget="..." on the DOM root (runtime boundary) and widget: "..." in the export (scan-time metadata). The scanner cross-validates; mismatch or one-sided presence is an error.

Primitive

Optional — files under src/components/ui/** auto-register without any export. Add export const uidex = { primitive: ... } only to attach a description or to opt out of convention registration.

primitive: false opts a file under the primitives glob out of auto-registration.

Flow / NotFlow

Flows live in Playwright specs — see Playwright flows. Use notFlow: true at the top of a non-flow spec file to opt out of auto-promotion:

AST-literal rule

The scanner reads export const uidex by AST walk. The RHS must be a plain literal. Allowed:

  • object literals, nested object literals
  • string / number / boolean literals
  • array literals of the above
  • trailing as const
  • trailing satisfies Uidex.<Kind>

Rejected (scanner emits an error diagnostic, skips the module, keeps scanning other files):

  • identifier references in value position (features: SHARED_IDS)
  • function / tagged-template calls
  • spreads (...base)
  • conditionals / ternaries
  • template literals with expressions
  • non-literal computed property keys

The rule keeps extraction deterministic — one AST walk per file, no import resolution, no tsconfig coupling. Shared ids are recovered through satisfies Uidex.X referring to the emitted id unions in uidex.gen.ts.

Typed cross-references

uidex.gen.ts exports per-kind id unions (PageId, FeatureId, WidgetId, PrimitiveId, FlowId, RouteId, RegionId, ElementId) and a Uidex namespace of shape types. satisfies Uidex.Page makes tsc flag unknown ids at the literal site — no uidex scan --audit round-trip required:

Set typeMode: "loose" in .uidex.json to emit FeatureId = string etc. instead of literal unions — see the migration section in uidex scan.

Acceptance criteria

Acceptance criteria are first-class metadata. They attach to widgets, features, and pages; they drive audit coverage, scaffolded Playwright specs, and the widget detail panel.

Declare criteria in the export

Rules:

  • One entry per criterion in the acceptance: string[] array. Source order is preserved.
  • Text is copied verbatim — no JSDoc whitespace trimming involved.
  • Valid on Uidex.Page, Uidex.Feature, and Uidex.Widget shapes. Absent on Uidex.Primitive, Uidex.Flow, and Uidex.NotFlow.

Pages and features declare criteria the same way:

How the scanner uses them

The scanner extracts criteria into entity.meta.acceptance (preserved as a string array) and, for every entity carrying acceptance, computes entity.meta.flows — the list of flow ids whose touches array includes the entity (or, for widgets, any descendant element).

meta.flows is always an array; an entity with no covering flows gets meta.flows: [], never undefined.

The end-to-end loop

See uidex scan for the full audit and scaffold reference.