uidex 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +263 -263
  2. package/dist/cli/cli.cjs +3243 -0
  3. package/dist/cli/cli.cjs.map +1 -0
  4. package/dist/cloud/index.cjs +149 -0
  5. package/dist/cloud/index.cjs.map +1 -0
  6. package/dist/cloud/index.d.cts +108 -0
  7. package/dist/cloud/index.d.ts +108 -0
  8. package/dist/cloud/index.js +120 -0
  9. package/dist/cloud/index.js.map +1 -0
  10. package/dist/headless/index.cjs +3580 -0
  11. package/dist/headless/index.cjs.map +1 -0
  12. package/dist/headless/index.d.cts +214 -0
  13. package/dist/headless/index.d.ts +214 -0
  14. package/dist/headless/index.js +3562 -0
  15. package/dist/headless/index.js.map +1 -0
  16. package/dist/index.cjs +7977 -3301
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +898 -108
  19. package/dist/index.d.ts +898 -108
  20. package/dist/index.js +7934 -3270
  21. package/dist/index.js.map +1 -1
  22. package/dist/playwright/index.cjs +164 -24
  23. package/dist/playwright/index.cjs.map +1 -1
  24. package/dist/playwright/index.d.cts +32 -55
  25. package/dist/playwright/index.d.ts +32 -55
  26. package/dist/playwright/index.js +148 -21
  27. package/dist/playwright/index.js.map +1 -1
  28. package/dist/playwright/reporter.cjs +62 -28
  29. package/dist/playwright/reporter.cjs.map +1 -1
  30. package/dist/playwright/reporter.d.cts +24 -12
  31. package/dist/playwright/reporter.d.ts +24 -12
  32. package/dist/playwright/reporter.js +62 -28
  33. package/dist/playwright/reporter.js.map +1 -1
  34. package/dist/react/index.cjs +7970 -3267
  35. package/dist/react/index.cjs.map +1 -1
  36. package/dist/react/index.d.cts +670 -108
  37. package/dist/react/index.d.ts +670 -108
  38. package/dist/react/index.js +8016 -3274
  39. package/dist/react/index.js.map +1 -1
  40. package/dist/scan/index.cjs +3281 -0
  41. package/dist/scan/index.cjs.map +1 -0
  42. package/dist/scan/index.d.cts +373 -0
  43. package/dist/scan/index.d.ts +373 -0
  44. package/dist/scan/index.js +3224 -0
  45. package/dist/scan/index.js.map +1 -0
  46. package/package.json +74 -56
  47. package/templates/claude/audit.md +37 -0
  48. package/templates/claude/rules.md +212 -0
  49. package/claude/audit-command.md +0 -16
  50. package/claude/rules.md +0 -88
  51. package/dist/core/index.cjs +0 -3490
  52. package/dist/core/index.cjs.map +0 -1
  53. package/dist/core/index.d.cts +0 -441
  54. package/dist/core/index.d.ts +0 -441
  55. package/dist/core/index.global.js +0 -3469
  56. package/dist/core/index.global.js.map +0 -1
  57. package/dist/core/index.js +0 -3444
  58. package/dist/core/index.js.map +0 -1
  59. package/dist/core/style.css +0 -971
  60. package/dist/scripts/cli.cjs +0 -1168
  61. package/uidex.schema.json +0 -93
package/README.md CHANGED
@@ -1,365 +1,365 @@
1
1
  # uidex
2
2
 
3
- A framework-agnostic library for highlighting and annotating UI elements. Works with React, vanilla JS, or any framework. Includes a build-time scanner, inspect mode, Playwright integration, and Claude Code tooling.
3
+ uidex surfaces structured context about a running UI pages, features, widgets, regions, elements, primitives, flows to humans (devtools overlay, command palette, detail panels), agents (typed `uidex.gen.ts` registry), and test runners (Playwright fixture + reporter).
4
4
 
5
- ## Installation
5
+ This is **uidex 0.1** — a greenfield rewrite. The published surface is a single npm package with framework-neutral core, an optional React adapter, and a built-in cloud client, each reachable through its own subpath export.
6
+
7
+ ## Install
6
8
 
7
9
  ```bash
8
- npm install uidex
10
+ pnpm add uidex
9
11
  ```
10
12
 
11
- ## Quick Start
13
+ ## Quick start (React)
12
14
 
13
15
  ```bash
14
- # 1. Initialize config
15
- npx uidex init
16
-
17
- # 2. Add data-uidex attributes to your elements
18
- # <button data-uidex="submit-btn">Submit</button>
19
-
20
- # 3. Run the scanner
21
- npx uidex scan
16
+ npx uidex init # write .uidex.json and .gitignore entry
17
+ npx uidex scan # generate src/uidex.gen.ts
22
18
  ```
23
19
 
24
- ### React
25
-
26
20
  ```tsx
27
- import './uidex.gen';
28
- import { UidexDevtools } from 'uidex';
21
+ // any React root
22
+ import { UidexProvider, UidexMount } from "uidex/react"
29
23
 
30
- function App() {
24
+ export default function Root({ children }: { children: React.ReactNode }) {
31
25
  return (
32
- <>
33
- <button data-uidex="submit-btn">Submit</button>
34
- <UidexDevtools />
35
- </>
36
- );
26
+ <UidexProvider projectKey="pk_your_project">
27
+ {children}
28
+ {process.env.NODE_ENV !== "production" && <UidexMount />}
29
+ </UidexProvider>
30
+ )
37
31
  }
38
32
  ```
39
33
 
40
- ### Vanilla JS
34
+ That is the full integration. Shadow DOM mounts under the `<UidexMount>` node, the Inspector is always on while the surface is mounted (hover highlights any uidex-annotated element; plain click opens the element menu anchored to its bounds), and Cmd+K opens the palette. Passing `projectKey` auto-wires the built-in cloud client; pass `cloud={null}` to opt out, or `cloud={customAdapter}` to supply your own.
35
+
36
+ ### Dev-only mount
41
37
 
42
- ```js
43
- import './uidex.gen';
44
- import { createUidexUI } from 'uidex/core';
38
+ Because the Inspector intercepts clicks on every uidex-annotated element while mounted, uidex is a **dev-only surface**. Consumers MUST gate the mount on an environment check — the SDK does not enforce this itself.
45
39
 
46
- const ui = createUidexUI();
47
- ui.mount();
40
+ ```tsx
41
+ // React — gate <UidexMount> on NODE_ENV
42
+ {
43
+ process.env.NODE_ENV !== "production" && <UidexMount />
44
+ }
45
+ ```
48
46
 
49
- // Cleanup
50
- ui.destroy();
47
+ ```ts
48
+ // Vanilla — gate createUidex().mount() on NODE_ENV
49
+ if (process.env.NODE_ENV !== "production") {
50
+ createUidex({ cloud: cloud({ projectKey: "pk_..." }) }).mount()
51
+ }
51
52
  ```
52
53
 
53
- ### Script Tag
54
+ ## Quick start (vanilla)
55
+
56
+ ```ts
57
+ import { createUidex } from "uidex"
58
+ import { cloud } from "uidex/cloud"
54
59
 
55
- ```html
56
- <script src="https://unpkg.com/uidex/dist/core/index.global.js"></script>
57
- <script>
58
- const ui = UidexCore.createUidexUI({ components: { ... } });
59
- ui.mount();
60
- </script>
60
+ const uidex = createUidex({
61
+ cloud: cloud({ projectKey: "pk_your_project" }),
62
+ })
63
+ uidex.mount() // defaults to document.body
61
64
  ```
62
65
 
63
- ## Configuration
66
+ ## Package layout
64
67
 
65
- `npx uidex init` creates a `.uidex.json` file:
68
+ | Subpath | Purpose |
69
+ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `uidex` | Vanilla core. `createUidex`, session store (XState v5 surface + highlight + form machines), entity registry, injected surface, panel registry, Zag machines. No React, no network. |
71
+ | `uidex/react` | React boot wrapper. `UidexProvider`, `useUidex`, `UidexMount`, `createReactPanel`. No network. |
72
+ | `uidex/cloud` | Fetch client for the cloud ingest API. `cloud({ projectKey, endpoint })`, `FeedbackPayload`, etc. No React. |
73
+ | `uidex/headless` | Narrower vanilla entry — Registry + Session + Shadow DOM + Overlay + Inspector + MenuBar. No panels. |
74
+ | `uidex/scan` | Scanner pipeline (`discover → walk → extract → resolve → audit → emit`) and CLI. |
75
+ | `uidex/playwright` | Test fixture (`uidex(id)`). Optional peer: `@playwright/test`. |
76
+ | `uidex/playwright/reporter` | Coverage reporter. |
66
77
 
67
- ```json
68
- {
69
- "$schema": "./node_modules/uidex/uidex.schema.json",
70
- "defaults": {
71
- "color": "#3b82f6",
72
- "borderStyle": "solid",
73
- "borderWidth": 2,
74
- "showLabel": true,
75
- "labelPosition": "top-left"
76
- },
77
- "colors": {
78
- "primary": "#3b82f6",
79
- "success": "#10b981",
80
- "warning": "#f59e0b",
81
- "error": "#ef4444",
82
- "info": "#6366f1"
83
- },
84
- "scanner": {
85
- "rootDir": "src",
86
- "include": ["**/*.tsx", "**/*.jsx"],
87
- "exclude": ["**/*.test.*", "**/*.spec.*"],
88
- "outputPath": "src/uidex.gen.ts"
89
- }
78
+ The build enforces subpath isolation: `uidex` contains no React or network code, `uidex/react` contains no network code, `uidex/cloud` contains no React.
79
+
80
+ ## Entity model
81
+
82
+ Eight kinds, one registry, one ref shape:
83
+
84
+ ```ts
85
+ type EntityKind =
86
+ | "route"
87
+ | "page"
88
+ | "feature"
89
+ | "widget"
90
+ | "region"
91
+ | "element"
92
+ | "primitive"
93
+ | "flow"
94
+
95
+ interface EntityRef {
96
+ kind: EntityKind
97
+ id: string
90
98
  }
91
99
  ```
92
100
 
93
- ## Scanner
101
+ - **route** — a URL pattern. Auto-detected from the framework router.
102
+ - **page** — the module that renders a route. `export const uidex = { page: "id", ... } as const satisfies Uidex.Page` overrides the derived id and declares acceptance.
103
+ - **feature** — cross-cutting behaviour under `src/features/*`. `export const uidex = { feature: "id", ... }` overrides.
104
+ - **widget** — composite interactive unit (video player, date picker). Declared with `data-uidex-widget="id"` on the DOM root AND `export const uidex = { widget: "id", acceptance: [...] }` on the component module. The scanner cross-validates the two.
105
+ - **region** — HTML5 landmark or `role="region"`. `data-uidex-region` overrides id.
106
+ - **element** — explicit interactive element. Always annotated via `data-uidex`.
107
+ - **primitive** — reusable presentational component (button, input) under `src/ui/**`.
108
+ - **flow** — top-level `test.describe` in `e2e/**`; `@uidex:flow` tag adds richer metadata. Opt out with `export const uidex = { notFlow: true }`.
94
109
 
95
- The scanner finds `data-uidex` attributes in your source files and generates a typed registry.
110
+ ## Runtime API
96
111
 
97
- ```bash
98
- npx uidex scan # Generate registry
99
- npx uidex scan --check # Validate UIDEX_PAGE.md coverage
100
- ```
112
+ ```ts
113
+ import { createUidex } from "uidex"
101
114
 
102
- Add it to your build:
115
+ const uidex = createUidex({
116
+ cloud: null, // or a CloudAdapter from uidex/cloud
117
+ theme: "auto", // "light" | "dark" | "auto"
118
+ defaultPanels: true, // register bundled detail + palette panels
119
+ panels: [myCustomPanel], // additional panels (last registered wins on conflict)
120
+ })
103
121
 
104
- ```json
105
- {
106
- "scripts": {
107
- "prebuild": "npx uidex scan",
108
- "build": "next build"
109
- }
110
- }
122
+ uidex.mount() // defaults to document.body
123
+ uidex.unmount()
111
124
  ```
112
125
 
113
- ### Annotating Components
126
+ `createUidex` returns:
114
127
 
115
- ```tsx
116
- <nav data-uidex="main-nav">
117
- <a href="/">Home</a>
118
- </nav>
128
+ | Field | Shape |
129
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
130
+ | `registry` | `Registry` — `.add`, `.get(kind, id)`, `.list(kind)`, `.query(fn)`. |
131
+ | `session` | Zustand store projecting the XState surface machine. `hover`, `selection`, `stack`, `inspectorActive`, `theme`, `ingestActive`. `session.send(event)` for direct machine events. |
132
+ | `panels` | `{ register, unregister, list }` — panel registrar. |
133
+ | `cloud` | The configured `CloudAdapter` or `null`. |
134
+ | `ingest` | Console + network capture (auto-enabled when `cloud` is configured). |
135
+ | `shadowRoot` | The Shadow DOM root after `mount()`, else `null`. |
119
136
 
120
- /** @uidex cta-button - Primary call-to-action button */
121
- <button data-uidex="cta-button">Click Me</button>
137
+ ### React wiring
138
+
139
+ ```tsx
140
+ import { UidexProvider, UidexMount, useUidex } from "uidex/react"
122
141
  ```
123
142
 
124
- ### Generated Output
143
+ - `<UidexProvider>` accepts `projectKey`, `cloud`, and `config` props. It creates the core instance on mount and disposes on unmount. If `projectKey` is provided and `cloud` is not explicitly set, it auto-wires `cloud({ projectKey })` from `uidex/cloud`.
144
+ - `useUidex()` returns the core instance and throws `UidexContextError` outside a provider.
145
+ - `<UidexMount>` renders the host DOM node the surface attaches to; the surface detaches on unmount.
125
146
 
126
- The scanner generates `src/uidex.gen.ts` (gitignored) with:
147
+ ### Headless
127
148
 
128
- - `components` — map of id to file locations
129
- - `componentIds` array of all ids
130
- - `ComponentId` — union type of all ids
131
- - `pages` — from `UIDEX_PAGE.md` files (directory-based component association)
132
- - `features` — from `UIDEX_FEATURE.md` files (explicit component association)
133
- - Auto-registration calls
149
+ ```ts
150
+ import { createHeadless } from "uidex/headless"
134
151
 
135
- ### Monorepo Support
152
+ const h = createHeadless({ theme: "auto" })
153
+ h.mount()
154
+ h.overlay.show(el, { label: "target" })
155
+ h.inspector.start()
156
+ ```
136
157
 
137
- Use `sources` for multiple scan roots:
158
+ No panels, no `ViewStack`. Same Shadow DOM, Overlay, MenuBar, CursorTooltip, CommandPalette (empty). Intended for agents, screencasts, and non-panel consumers.
138
159
 
139
- ```json
140
- {
141
- "scanner": {
142
- "sources": [
143
- { "rootDir": "src", "include": ["**/*.tsx"] },
144
- { "rootDir": "../packages/ui/src", "include": ["**/*.tsx"], "prefix": "@myorg/ui" }
145
- ],
146
- "exclude": ["**/*.test.*"],
147
- "outputPath": "src/uidex.gen.ts"
148
- }
160
+ ## Panel system
161
+
162
+ Panels are the pluggable UI surface. Both detail views and palette commands are `Panel`s. Panels are framework-agnostic — `render` is imperative:
163
+
164
+ ```ts
165
+ interface Panel {
166
+ id: string
167
+ matches?: (ref: EntityRef) => boolean // detail panel
168
+ command?: { label: string; group?: string; shortcut?: string } // palette entry
169
+ render: (ctx: PanelContext, root: HTMLElement) => () => void
149
170
  }
150
171
  ```
151
172
 
152
- ## Components
173
+ - `render` receives a mount root and returns a cleanup function.
174
+ - Last registered wins on `matches()` overlap. `id` collision replaces the prior registration.
175
+ - `ctx.navigate(ref)` pushes the matching panel for `ref` onto the view stack; the current view stays mounted underneath and is revealed on pop.
176
+ - `ctx.push({ id, ref? })` and `ctx.pop()` drive the stack directly; `ctx.close()` clears every entry at once.
177
+ - `ctx.cloud` is the configured `CloudAdapter` or `null`; cloud-backed affordances MUST degrade gracefully when it is null.
153
178
 
154
- ### UidexDevtools (React)
155
-
156
- Floating devtools panel with component list, pages, features, and inspect mode.
179
+ Authoring a panel with React:
157
180
 
158
181
  ```tsx
159
- import { UidexDevtools } from 'uidex';
182
+ import { createReactPanel } from "uidex/react"
160
183
 
161
- <UidexDevtools
162
- buttonPosition="bottom-right"
163
- onSelect={(id) => console.log(id)}
164
- />
184
+ export const myPanel = createReactPanel({
185
+ id: "my-panel",
186
+ matches: (ref) => ref.kind === "widget",
187
+ component: ({ ctx }) => <div>Widget: {ctx.ref?.id}</div>,
188
+ })
165
189
  ```
166
190
 
167
- | Prop | Type | Default | Description |
168
- |------|------|---------|-------------|
169
- | `components` | `UidexMap` | registry | Components map |
170
- | `config` | `UidexConfig` | - | Styling configuration |
171
- | `buttonPosition` | `ButtonPosition` | `'bottom-right'` | Initial button position |
172
- | `disabled` | `boolean` | `false` | Disable devtools |
173
- | `onSelect` | `(id: string) => void` | - | Selection callback |
174
- | `inspectShortcut` | `KeyboardShortcut \| false` | `Shift+Cmd+U` | Inspect mode shortcut |
191
+ `createReactPanel` wraps `createRoot` / `unmount` and produces a conforming `Panel`.
175
192
 
176
- ### UidexOverlay (React)
193
+ ### Element menu
177
194
 
178
- Standalone overlay for highlighting a single element.
195
+ Clicking any uidex-annotated element opens a popover menu anchored to the element's bounds. The menu is chrome owned by the Surface (not a `Panel`), composed from the shared `menu` + `popover` Zag machines. All rows close the menu on activation; Escape and outside-click dismiss and return focus to the prior focus holder.
179
196
 
180
- ```tsx
181
- import { UidexOverlay } from 'uidex';
197
+ | Row | When shown | Action |
198
+ | ---------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
199
+ | **Copy path** | Always | `navigator.clipboard.writeText("<kind>:<id>")`. |
200
+ | **Open details** | A registered panel matches the entity | `session.actions.select(ref)` + `session.actions.openPanel(panel.id, ref)` where `panel = panels.findMatch(ref)`. |
201
+ | **Submit feedback** | A registered panel matches the entity | Routes to the matched detail panel (feedback currently lives as a sub-view). Suppressed if there is no detail panel for the entity. |
202
+ | **View acceptance criteria** | `ref.kind === "widget"` and a detail panel matches | Opens the widget detail panel. |
203
+ | **Find usages** | `ref.kind === "primitive"` | Queries the registry for entities composing the primitive and opens the primitive detail panel. |
182
204
 
183
- <UidexOverlay
184
- target={element}
185
- label="Button"
186
- color="#3b82f6"
187
- />
188
- ```
205
+ The ⌘K palette provides the keyboard-driven counterpart: its "On this page" section enumerates every uidex entity present in the current DOM, and selecting a row calls `ctx.navigate(ref)`.
189
206
 
190
- | Prop | Type | Default | Description |
191
- |------|------|---------|-------------|
192
- | `target` | `HTMLElement \| null` | required | Element to highlight |
193
- | `label` | `string` | - | Label text |
194
- | `color` | `string` | - | Border color (hex or named) |
195
- | `borderStyle` | `BorderStyle` | `'solid'` | `solid`, `dashed`, or `dotted` |
196
- | `borderWidth` | `number` | `2` | Border width in px |
197
- | `showLabel` | `boolean` | `true` | Show label |
198
- | `labelPosition` | `LabelPosition` | `'top-left'` | Label position |
199
- | `colors` | `Record<string, string>` | - | Named colors map |
200
-
201
- ### createUidexUI (Vanilla JS)
202
-
203
- ```js
204
- import { createUidexUI } from 'uidex/core';
205
-
206
- const ui = createUidexUI({
207
- components: { ... },
208
- pages: [ ... ],
209
- features: [ ... ],
210
- config: { defaults: { color: '#3b82f6' } },
211
- buttonPosition: 'bottom-right',
212
- inspectShortcut: { key: 'u', shiftKey: true, metaKey: true },
213
- onSelect: (id) => console.log(id),
214
- });
215
-
216
- ui.mount();
217
- ui.destroy();
218
- ```
207
+ ### Bundled panels
219
208
 
220
- ## Inspect Mode
209
+ `createUidex({ defaultPanels: true })` (the default) registers:
221
210
 
222
- Press **Shift+Cmd+U** (Shift+Ctrl+U on Windows/Linux) to enter inspect mode. Hover over elements to highlight them, click to select. Press **Escape** to exit.
211
+ `commandPalettePanel`, `componentDetailPanel` (element), `pageDetailPanel`, `featureDetailPanel`, `widgetDetailPanel`, `flowDetailPanel`, `primitiveDetailPanel`, `regionDetailPanel`.
223
212
 
224
- Disable or customize the shortcut:
213
+ All eight are vanilla TypeScript built on Zag machines and Tailwind classes.
225
214
 
226
- ```tsx
227
- // Disable
228
- <UidexDevtools inspectShortcut={false} />
215
+ ### Report uidex issue
229
216
 
230
- // Custom shortcut
231
- <UidexDevtools inspectShortcut={{ key: 'i', ctrlKey: true }} />
232
- ```
217
+ Every view's footer Actions popup (⌘K from depth 1, or the footer affordance) includes a **Report uidex issue** entry. It opens the SDK's built-in feedback form and submits to a uidex-maintained project — separate from your host `cloud` adapter, so SDK bugs surface to the uidex team without contaminating your own ticketing. The report is stamped with the SDK version and the originating view id (e.g. `uidex-sdk:command-palette`); when the SDK cannot reach its endpoint the form falls back to copying a Markdown report to the clipboard.
233
218
 
234
- ## Playwright Integration
219
+ ## Scanner
235
220
 
236
- Test your annotated components with type-safe locators and coverage reporting.
221
+ One pipeline, six stages, each stage in its own file and independently callable:
237
222
 
238
- ### Test Fixture
223
+ ```
224
+ discover(cwd) // locate .uidex.json files across the monorepo
225
+ -> walk(sources) // enumerate files per include/exclude
226
+ -> extract(files) // data-uidex* attrs + export const uidex AST literals
227
+ -> resolve(ann) // apply conventions, compute scopes, back-references
228
+ -> audit(registry) // diagnostics: missing, scope-leak, acceptance coverage
229
+ -> emit(registry) // deterministic uidex.gen.ts
230
+ ```
239
231
 
240
- ```ts
241
- import { test, expect } from 'uidex/playwright';
232
+ CLI:
242
233
 
243
- test('submit button works', async ({ uidex }) => {
244
- await uidex('submit-btn').click();
245
- await uidex('success-message').waitFor();
246
- });
234
+ ```bash
235
+ npx uidex init # scaffold .uidex.json
236
+ npx uidex scan # run full pipeline
237
+ npx uidex scan --check # fail on registry drift (CI gate)
238
+ npx uidex scan --lint # annotation lint diagnostics (includes legacy-jsdoc)
239
+ npx uidex scan --audit # --check + --lint + coverage
240
+ npx uidex scan --json # machine-readable output
241
+ npx uidex scaffold widget <id> # emit Playwright spec from declared acceptance
242
+ npx uidex claude install # Claude Code rules + skill + hooks
247
243
  ```
248
244
 
249
- ### Coverage Reporter
245
+ See the [docs site](https://github.com/soel/uidex#docs) for the convention table, annotation surfaces, and the acceptance workflow.
250
246
 
251
- Track which `data-uidex` components are covered by tests:
247
+ ### Bundler plugins
252
248
 
253
- ```ts
254
- // playwright.config.ts
255
- import UidexCoverageReporter from 'uidex/playwright/reporter';
256
- import { componentIds } from './src/uidex.gen.test';
249
+ Optional opt-in watch integrations regenerate `uidex.gen.ts` on file save:
257
250
 
258
- export default defineConfig({
259
- reporter: [
260
- ['html'],
261
- [UidexCoverageReporter, { componentIds }],
262
- ],
263
- });
264
- ```
251
+ - `@uidex/vite-plugin` — `plugins: [uidex()]` in `vite.config.ts`.
252
+ - `@uidex/webpack-plugin` — `new UidexPlugin()` in `webpack.config.js`.
253
+ - `@uidex/next-plugin` — `withUidex(config)` wrapper for `next.config.*`. Webpack transport today; Turbopack support tracked as a follow-up.
265
254
 
266
- Outputs `uidex-coverage.json` with covered/uncovered components and percentage.
255
+ All three wrap `@uidex/plugin-core` so behaviour and diagnostic shapes are identical across bundlers.
267
256
 
268
- ### Scaffold Tests
257
+ ## Config (`.uidex.json`)
269
258
 
270
- Generate test stubs from your pages and features:
259
+ Flat schema. Legacy nested `scanner.*`, `defaults`, `colors` are rejected with descriptive errors.
271
260
 
272
- ```bash
273
- npx uidex scaffold [dir]
261
+ ```json
262
+ {
263
+ "$schema": "./node_modules/uidex/uidex.schema.json",
264
+ "sources": [
265
+ {
266
+ "rootDir": "src",
267
+ "include": ["**/*.{ts,tsx}"],
268
+ "exclude": ["**/*.test.*"]
269
+ }
270
+ ],
271
+ "output": "src/uidex.gen.ts",
272
+ "flows": ["e2e/**/*.spec.ts"],
273
+ "typeMode": "strict",
274
+ "audit": { "scopeLeak": true, "coverage": true, "acceptance": true },
275
+ "conventions": {
276
+ "primitives": ["src/ui/**", "src/components/ui/**"],
277
+ "features": "src/features/*",
278
+ "pages": "auto",
279
+ "flows": ["e2e/**/*.spec.ts"],
280
+ "regions": "landmarks"
281
+ }
282
+ }
274
283
  ```
275
284
 
276
- ## Claude Code Integration
285
+ Set any `conventions.*` entry to `false` to disable that auto-discovery stage.
277
286
 
278
- Set up Claude Code with uidex-aware rules, a `/uidex-audit` skill, and a post-edit hook that validates coverage:
287
+ `typeMode` controls whether emitted id types are literal unions (`"strict"`, default) or `string` (`"loose"`, a temporary migration knob).
279
288
 
280
- ```bash
281
- npx uidex claude init # Add rules + skill + hooks
282
- npx uidex claude teardown # Remove everything
283
- ```
289
+ ## Acceptance workflow
284
290
 
285
- Manage individually:
291
+ Declare criteria in the module-scoped `export const uidex`:
286
292
 
287
- ```bash
288
- npx uidex claude rules add|remove # .claude/rules/uidex.md
289
- npx uidex claude skill add|remove # .claude/commands/uidex-audit.md
290
- npx uidex claude hooks add|remove # PostToolUse hook in .claude/settings.json
291
- ```
293
+ ```ts
294
+ import type { Uidex } from "@/uidex.gen"
295
+
296
+ export const uidex = {
297
+ widget: "video-player",
298
+ acceptance: [
299
+ "Plays on space",
300
+ "Mutes with m",
301
+ "Scrubs with arrow keys",
302
+ ],
303
+ } as const satisfies Uidex.Widget
292
304
 
293
- ## Theming
294
-
295
- All UI renders inside a Shadow DOM with CSS custom properties. Override any token at `:root`:
296
-
297
- ```css
298
- :root {
299
- --uidex-color-primary: #2e2e2e;
300
- --uidex-color-bg: #ffffff;
301
- --uidex-color-bg-elevated: #f5f5f5;
302
- --uidex-color-text: #1f2937;
303
- --uidex-color-text-strong: #111827;
304
- --uidex-color-text-muted: #6b7280;
305
- --uidex-color-surface-hover: #f3f4f6;
306
- --uidex-color-surface-active: #e5e7eb;
307
- --uidex-color-border: #d1d5db;
308
- --uidex-color-border-light: #e5e7eb;
305
+ export function VideoPlayer() {
306
+ return <div data-uidex-widget="video-player">...</div>
309
307
  }
310
308
  ```
311
309
 
312
- 33 tokens available across colors, shadows, border-radius, z-index, typography, and transitions. The default theme is dark monochromatic with zero border-radius and monospace typography.
310
+ 1. Scanner extracts `acceptance` `entity.meta.acceptance` (in source order).
311
+ 2. Flows touching the widget (directly or via descendants) are collected into `meta.flows`.
312
+ 3. `scan --audit` flags any criterion not covered by a flow; the hint suggests `uidex scaffold widget <id>`.
313
+ 4. `uidex scaffold widget video-player` emits a Playwright spec with one `test()` per criterion, tagged `@uidex:flow`. Re-running is idempotent unless `--force` is passed.
313
314
 
314
- ## Package Exports
315
+ ## Cloud + ingest
315
316
 
316
- | Import | Description |
317
- |--------|-------------|
318
- | `uidex` | React components (re-exports `uidex/react`) |
319
- | `uidex/core` | Vanilla JS classes (`createUidexUI`, `Overlay`, `Inspector`) |
320
- | `uidex/react` | React wrappers (`UidexDevtools`, `UidexOverlay`) |
321
- | `uidex/playwright` | Test fixture and helpers |
322
- | `uidex/playwright/reporter` | Coverage reporter |
323
- | `uidex/styles.css` | Standalone CSS (optional, auto-injected via Shadow DOM) |
317
+ Configure cloud by passing a `CloudAdapter` into `createUidex({ cloud })` or, from React, by passing `projectKey` / `cloud` to `<UidexProvider>`. The built-in client lives at `uidex/cloud`:
324
318
 
325
- ## CLI Reference
319
+ ```ts
320
+ import { cloud } from "uidex/cloud"
326
321
 
327
- ```
328
- npx uidex Initialize project (alias for init)
329
- npx uidex init Create .uidex.json and update .gitignore
330
- npx uidex scan Run component scanner
331
- npx uidex scan --check Validate UIDEX_PAGE.md coverage
332
- npx uidex scaffold [dir] Generate Playwright test stubs
333
- npx uidex claude init Set up Claude Code integration
334
- npx uidex claude teardown Remove Claude Code integration
322
+ const adapter = cloud({ projectKey: "pk_..." })
323
+ await adapter.feedback.submit(payload)
324
+ await adapter.integrations.getConfig()
335
325
  ```
336
326
 
337
- ## TypeScript
327
+ `cloud()` takes `{ projectKey, endpoint?, fetch? }`. `endpoint` defaults to the production cloud URL; `fetch` allows tests or self-hosted deployments to inject a custom transport. Errors surface as `CloudError` with `status`, `retryAfter`, and `details`.
338
328
 
339
- Full type definitions included:
329
+ When `cloud` is configured, `uidex/ingest` auto-enables:
330
+
331
+ - Console capture: `warn`/`error`, ring buffer ≤ 50. Originals always invoked.
332
+ - Network capture: failed fetches only, ring buffer ≤ 20. Native `fetch` reference is captured at module load, so `cloud.feedback.submit` always uses an un-intercepted fetch.
333
+
334
+ Override per-channel via `createUidex({ ingest: { console: {...}, network: null } })`, or pass `ingest: null` to disable entirely.
335
+
336
+ ## Playwright
340
337
 
341
338
  ```ts
342
- import type {
343
- UidexConfig,
344
- UidexDefaults,
345
- UidexMap,
346
- UidexLocation,
347
- UidexPage,
348
- UidexFeature,
349
- BorderStyle,
350
- LabelPosition,
351
- ButtonPosition,
352
- KeyboardShortcut,
353
- } from 'uidex';
339
+ import { test, expect } from "uidex/playwright"
340
+
341
+ test.describe("Add and complete a todo", { tag: "@uidex:flow" }, () => {
342
+ test("adds a todo", async ({ uidex }) => {
343
+ await uidex("todo-text-field").fill("Buy milk")
344
+ await uidex("todo-add-button").click()
345
+ await expect(uidex("todo-item")).toHaveCount(1)
346
+ })
347
+ })
354
348
  ```
355
349
 
356
- ## Examples
350
+ Resolves any `data-uidex`, `data-uidex-region`, `data-uidex-widget`, or `data-uidex-primitive`. Register the coverage reporter to emit flow → entity coverage data — it is additive, compatible with any other reporter.
357
351
 
358
- See `examples/` for working demos:
359
-
360
- - `examples/next-app/` — Next.js App Router
361
- - `examples/vite-app/` — Vite + React
352
+ ```ts
353
+ // playwright.config.ts
354
+ reporter: [["list"], ["uidex/playwright/reporter"]]
355
+ ```
362
356
 
363
- ## License
357
+ ## Scripts
364
358
 
365
- MIT
359
+ ```bash
360
+ pnpm build # build:css + tsup + check:bundles
361
+ pnpm test # vitest (watch)
362
+ pnpm test:run # vitest (run)
363
+ pnpm typecheck # tsc --noEmit
364
+ pnpm lint # eslint src/
365
+ ```