uidex 0.2.4 → 0.4.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 (65) hide show
  1. package/README.md +253 -353
  2. package/dist/cli/cli.cjs +3324 -0
  3. package/dist/cli/cli.cjs.map +1 -0
  4. package/dist/cloud/index.cjs +169 -0
  5. package/dist/cloud/index.cjs.map +1 -0
  6. package/dist/cloud/index.js +140 -0
  7. package/dist/cloud/index.js.map +1 -0
  8. package/dist/headless/index.cjs +4143 -0
  9. package/dist/headless/index.cjs.map +1 -0
  10. package/dist/headless/index.d.cts +220 -0
  11. package/dist/headless/index.d.ts +220 -0
  12. package/dist/headless/index.js +4130 -0
  13. package/dist/headless/index.js.map +1 -0
  14. package/dist/index.cjs +8704 -9883
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +968 -146
  17. package/dist/index.d.ts +968 -146
  18. package/dist/index.js +8327 -9492
  19. package/dist/index.js.map +1 -1
  20. package/dist/playwright/index.cjs +164 -24
  21. package/dist/playwright/index.cjs.map +1 -1
  22. package/dist/playwright/index.d.cts +30 -53
  23. package/dist/playwright/index.d.ts +30 -53
  24. package/dist/playwright/index.js +148 -21
  25. package/dist/playwright/index.js.map +1 -1
  26. package/dist/playwright/reporter.cjs +62 -28
  27. package/dist/playwright/reporter.cjs.map +1 -1
  28. package/dist/playwright/reporter.d.cts +24 -12
  29. package/dist/playwright/reporter.d.ts +24 -12
  30. package/dist/playwright/reporter.js +62 -28
  31. package/dist/playwright/reporter.js.map +1 -1
  32. package/dist/react/index.cjs +8706 -9883
  33. package/dist/react/index.cjs.map +1 -1
  34. package/dist/react/index.d.cts +720 -146
  35. package/dist/react/index.d.ts +720 -146
  36. package/dist/react/index.js +8518 -9629
  37. package/dist/react/index.js.map +1 -1
  38. package/dist/scan/index.cjs +3360 -0
  39. package/dist/scan/index.cjs.map +1 -0
  40. package/dist/scan/index.d.cts +378 -0
  41. package/dist/scan/index.d.ts +378 -0
  42. package/dist/scan/index.js +3303 -0
  43. package/dist/scan/index.js.map +1 -0
  44. package/package.json +67 -60
  45. package/templates/claude/audit.md +43 -0
  46. package/templates/claude/rules.md +227 -0
  47. package/claude/audit-command.md +0 -46
  48. package/claude/rules.md +0 -167
  49. package/dist/api/index.cjs +0 -254
  50. package/dist/api/index.cjs.map +0 -1
  51. package/dist/api/index.d.cts +0 -236
  52. package/dist/api/index.d.ts +0 -236
  53. package/dist/api/index.js +0 -226
  54. package/dist/api/index.js.map +0 -1
  55. package/dist/core/index.cjs +0 -11045
  56. package/dist/core/index.cjs.map +0 -1
  57. package/dist/core/index.d.cts +0 -424
  58. package/dist/core/index.d.ts +0 -424
  59. package/dist/core/index.global.js +0 -66516
  60. package/dist/core/index.global.js.map +0 -1
  61. package/dist/core/index.js +0 -10995
  62. package/dist/core/index.js.map +0 -1
  63. package/dist/core/style.css +0 -1529
  64. package/dist/scripts/cli.cjs +0 -3904
  65. package/uidex.schema.json +0 -93
package/README.md CHANGED
@@ -1,465 +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, annotation linting, 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
41
-
42
- ```js
43
- import './uidex.gen';
44
- import { createUidexUI } from 'uidex/core';
45
-
46
- const ui = createUidexUI();
47
- ui.mount();
48
-
49
- // Cleanup
50
- ui.destroy();
51
- ```
52
-
53
- ### Script Tag
54
-
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>
61
- ```
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.
62
35
 
63
- ## Configuration
36
+ ### Dev-only mount
64
37
 
65
- `npx uidex init` creates a `.uidex.json` file:
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.
66
39
 
67
- ```json
40
+ ```tsx
41
+ // React — gate <UidexMount> on NODE_ENV
68
42
  {
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
- }
43
+ process.env.NODE_ENV !== "production" && <UidexMount />
90
44
  }
91
45
  ```
92
46
 
93
- ## Scanner
94
-
95
- The scanner finds `data-uidex` attributes in your source files and generates a typed registry.
96
-
97
- ```bash
98
- npx uidex scan # Generate registry
99
- npx uidex scan --audit # Validate coverage and annotations
100
- ```
101
-
102
- Add it to your build:
103
-
104
- ```json
105
- {
106
- "scripts": {
107
- "prebuild": "npx uidex scan",
108
- "build": "next build"
109
- }
47
+ ```ts
48
+ // Vanilla — gate createUidex().mount() on NODE_ENV
49
+ if (process.env.NODE_ENV !== "production") {
50
+ createUidex({ cloud: cloud({ projectKey: "pk_..." }) }).mount()
110
51
  }
111
52
  ```
112
53
 
113
- ### Annotating Components
54
+ ## Quick start (vanilla)
114
55
 
115
- ```tsx
116
- <nav data-uidex="main-nav">
117
- <a href="/">Home</a>
118
- </nav>
56
+ ```ts
57
+ import { createUidex } from "uidex"
58
+ import { cloud } from "uidex/cloud"
119
59
 
120
- {/* Optional: add a JSDoc description for richer documentation */}
121
- /** @uidex cta-button - Primary call-to-action button */
122
- <button data-uidex="cta-button">Click Me</button>
60
+ const uidex = createUidex({
61
+ cloud: cloud({ projectKey: "pk_your_project" }),
62
+ })
63
+ uidex.mount() // defaults to document.body
123
64
  ```
124
65
 
125
- ### Generated Output
66
+ ## Package layout
126
67
 
127
- The scanner generates `src/uidex.gen.ts` (gitignored) with:
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. |
128
77
 
129
- - `components` map of id to file locations
130
- - `componentIds` — array of all ids
131
- - `ComponentId` — union type of all ids
132
- - `pages` — from `UIDEX_PAGE.md` files (directory-based component association)
133
- - `features` — from `UIDEX_FEATURE.md` files (explicit component association)
134
- - `uiComponents` — presentational primitives detected under `ui/` directories (see [UI Primitives](#ui-primitives))
135
- - Auto-registration calls
78
+ The build enforces subpath isolation: `uidex` contains no React or network code, `uidex/react` contains no network code, `uidex/cloud` contains no React.
136
79
 
137
- ### Monorepo Support
80
+ ## Entity model
138
81
 
139
- The scanner auto-discovers multiple `.uidex.json` files in monorepo setups. Use `sources` for multiple scan roots within a single config:
82
+ Eight kinds, one registry, one ref shape:
140
83
 
141
- ```json
142
- {
143
- "scanner": {
144
- "sources": [
145
- { "rootDir": "src", "include": ["**/*.tsx"] },
146
- { "rootDir": "../packages/ui/src", "include": ["**/*.tsx"], "prefix": "@myorg/ui" }
147
- ],
148
- "exclude": ["**/*.test.*"],
149
- "outputPath": "src/uidex.gen.ts"
150
- }
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
151
98
  }
152
99
  ```
153
100
 
154
- ## UI Primitives
155
-
156
- The scanner treats any `.tsx` file whose path contains a literal `ui/` segment as a presentational primitive (e.g. `components/ui/button.tsx`, `features/auth/ui/Form.tsx`). Primitives don't need `data-uidex` annotations the scanner detects them by convention and emits a separate `uiComponents` array in `uidex.gen.ts` alongside the usual component map.
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 }`.
157
109
 
158
- Each primitive gets a **scope** resolved by walking up from its file:
110
+ ## Runtime API
159
111
 
160
- | Marker found first | Scope |
161
- |---|---|
162
- | `UIDEX_FEATURE.md` | `feature:<dir-name>` |
163
- | `UIDEX_PAGE.md` | `page:<dir-name>` |
164
- | neither | `global` |
165
-
166
- `npx uidex scan --audit` flags imports of feature- or page-scoped primitives from outside their scope tree. Global primitives are exempt.
112
+ ```ts
113
+ import { createUidex } from "uidex"
167
114
 
168
- ### Shape
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
+ })
169
121
 
170
- ```ts
171
- import type { PrimitiveEntry } from 'uidex';
172
-
173
- // interface PrimitiveEntry {
174
- // name: string; // exported component name, e.g. "Button"
175
- // filePath: string; // source-root-relative path, e.g. "ui/button.tsx"
176
- // scope: string; // "global" | "feature:<name>" | "page:<name>"
177
- // composes: string[]; // names of other primitives this one imports
178
- // usedBy: string[]; // file paths of consumers (non-primitive sources)
179
- // kind: 'ui';
180
- // }
122
+ uidex.mount() // defaults to document.body
123
+ uidex.unmount()
181
124
  ```
182
125
 
183
- `composes` and `usedBy` are filled in by a cross-source provenance pass, so you get the full dependency graph without maintaining it by hand. Barrel (`index.ts`) re-exports are **not** followed — import primitives directly from their source file for accurate tracking.
126
+ `createUidex` returns:
184
127
 
185
- ### Building a custom catalog from `uiComponents`
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`. |
186
136
 
187
- `uiComponents` is the public contract for building your own design-system docs page or component explorer. Import it from the generated file and render however you like:
137
+ ### React wiring
188
138
 
189
139
  ```tsx
190
- // app/gallery/page.tsx
191
- import { uiComponents } from '@/uidex.gen';
192
- import { Button } from '@/components/ui/button';
140
+ import { UidexProvider, UidexMount, useUidex } from "uidex/react"
141
+ ```
193
142
 
194
- // The only thing you maintain by hand: a map from primitive name to a live preview.
195
- const previews: Record<string, React.ReactNode> = {
196
- Button: <Button>Click me</Button>,
197
- };
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.
198
146
 
199
- export default function Gallery() {
200
- return (
201
- <ul>
202
- {uiComponents.map((entry) => (
203
- <li key={entry.filePath + entry.name}>
204
- <h3>{entry.name} <small>({entry.scope})</small></h3>
205
- <div>{previews[entry.name] ?? <em>no preview</em>}</div>
206
- <p>Used by {entry.usedBy.length} file(s)</p>
207
- </li>
208
- ))}
209
- </ul>
210
- );
211
- }
147
+ ### Headless
148
+
149
+ ```ts
150
+ import { createHeadless } from "uidex/headless"
151
+
152
+ const h = createHeadless({ theme: "auto" })
153
+ h.mount()
154
+ h.overlay.show(el, { label: "target" })
155
+ h.inspector.start()
212
156
  ```
213
157
 
214
- That's the whole pattern: **uidex ships the data, you ship the renderer.** Group by `scope` for sectioned layouts, filter on `composes`/`usedBy` to surface dependency graphs, or skip live previews entirely and render it as a plain data table.
158
+ No panels, no `ViewStack`. Same Shadow DOM, Overlay, MenuBar, CursorTooltip, CommandPalette (empty). Intended for agents, screencasts, and non-panel consumers.
215
159
 
216
- > **Note:** the `uiComponents` export is only present when the scanner finds at least one primitive. If your project has none yet, create a file under a `ui/` directory and re-run `npx uidex scan`.
160
+ ## Panel system
217
161
 
218
- See [`examples/next-app/app/gallery/page.tsx`](./examples/next-app/app/gallery/page.tsx) for a worked example that groups primitives by scope and renders each with a live preview + `composes`/`usedBy` metadata.
162
+ Panels are the pluggable UI surface. Both detail views and palette commands are `Panel`s. Panels are framework-agnostic `render` is imperative:
219
163
 
220
- ## Components
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
170
+ }
171
+ ```
221
172
 
222
- ### UidexDevtools (React)
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.
223
178
 
224
- Floating devtools panel with component list, pages, features, and inspect mode.
179
+ Authoring a panel with React:
225
180
 
226
181
  ```tsx
227
- import { UidexDevtools } from 'uidex';
182
+ import { createReactPanel } from "uidex/react"
228
183
 
229
- <UidexDevtools
230
- buttonPosition="bottom-right"
231
- theme="auto"
232
- onSelect={(id) => console.log(id)}
233
- />
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
+ })
234
189
  ```
235
190
 
236
- | Prop | Type | Default | Description |
237
- |------|------|---------|-------------|
238
- | `components` | `UidexMap` | registry | Components map |
239
- | `config` | `UidexConfig` | - | Styling configuration |
240
- | `buttonPosition` | `ButtonPosition` | `'bottom-right'` | Initial button position |
241
- | `theme` | `UidexTheme` | `'auto'` | Theme: `'dark'`, `'light'`, or `'auto'` |
242
- | `disabled` | `boolean` | `false` | Disable devtools |
243
- | `onSelect` | `(id: string) => void` | - | Selection callback |
244
- | `inspectShortcut` | `KeyboardShortcut \| false` | `Shift+Cmd+U` | Inspect mode shortcut |
245
- | `ingest` | `IngestConfig` | - | Server feedback submission config |
246
- | `onSubmit` | `(report, result) => void` | - | Callback after feedback submission |
191
+ `createReactPanel` wraps `createRoot` / `unmount` and produces a conforming `Panel`.
247
192
 
248
- ### UidexOverlay (React)
193
+ ### Element menu
249
194
 
250
- 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.
251
196
 
252
- ```tsx
253
- 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. |
254
204
 
255
- <UidexOverlay
256
- target={element}
257
- label="Button"
258
- color="#3b82f6"
259
- />
260
- ```
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)`.
261
206
 
262
- | Prop | Type | Default | Description |
263
- |------|------|---------|-------------|
264
- | `target` | `HTMLElement \| null` | required | Element to highlight |
265
- | `label` | `string` | - | Label text |
266
- | `color` | `string` | - | Border color (hex or named) |
267
- | `borderStyle` | `BorderStyle` | `'solid'` | `solid`, `dashed`, or `dotted` |
268
- | `borderWidth` | `number` | `2` | Border width in px |
269
- | `showLabel` | `boolean` | `true` | Show label |
270
- | `labelPosition` | `LabelPosition` | `'top-left'` | Label position |
271
- | `colors` | `Record<string, string>` | - | Named colors map |
272
-
273
- ### createUidexUI (Vanilla JS)
274
-
275
- ```js
276
- import { createUidexUI } from 'uidex/core';
277
-
278
- const ui = createUidexUI({
279
- components: { ... },
280
- pages: [ ... ],
281
- features: [ ... ],
282
- config: { defaults: { color: '#3b82f6' } },
283
- buttonPosition: 'bottom-right',
284
- theme: 'auto',
285
- inspectShortcut: { key: 'u', shiftKey: true, metaKey: true },
286
- onSelect: (id) => console.log(id),
287
- });
288
-
289
- ui.mount();
290
- ui.destroy();
291
- ```
207
+ ### Bundled panels
292
208
 
293
- ## Theming
209
+ `createUidex({ defaultPanels: true })` (the default) registers:
294
210
 
295
- The devtools widget supports automatic theme detection. Set `theme="auto"` (the default) to match the host page's theme — it checks for `.dark` / `.light` classes on `<html>` and falls back to the OS `prefers-color-scheme` media query. Changes are tracked live via `MutationObserver` and `matchMedia` listeners.
211
+ `commandPalettePanel`, `componentDetailPanel` (element), `pageDetailPanel`, `featureDetailPanel`, `widgetDetailPanel`, `flowDetailPanel`, `primitiveDetailPanel`, `regionDetailPanel`.
296
212
 
297
- ```tsx
298
- // Auto-detect (default) — matches host page or OS preference
299
- <UidexDevtools theme="auto" />
213
+ All eight are vanilla TypeScript built on Zag machines and Tailwind classes.
300
214
 
301
- // Force dark or light
302
- <UidexDevtools theme="dark" />
303
- <UidexDevtools theme="light" />
304
- ```
215
+ ### Report uidex issue
305
216
 
306
- All UI renders inside a Shadow DOM with CSS custom properties. The default theme is dark. Override tokens for a light theme:
307
-
308
- ```css
309
- :root {
310
- --background: #ffffff;
311
- --foreground: #262626;
312
- --card: #ffffff;
313
- --card-foreground: #262626;
314
- --popover: #ffffff;
315
- --popover-foreground: #262626;
316
- --primary: #262626;
317
- --primary-foreground: #fafafa;
318
- --muted: rgba(0, 0, 0, 0.04);
319
- --muted-foreground: #6b6b6b;
320
- --border: rgba(0, 0, 0, 0.08);
321
- }
322
- ```
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.
323
218
 
324
- ## Inspect Mode
219
+ ## Scanner
325
220
 
326
- 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.
221
+ One pipeline, six stages, each stage in its own file and independently callable:
327
222
 
328
- Disable or customize the shortcut:
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
+ ```
329
231
 
330
- ```tsx
331
- // Disable
332
- <UidexDevtools inspectShortcut={false} />
232
+ CLI:
333
233
 
334
- // Custom shortcut
335
- <UidexDevtools inspectShortcut={{ key: 'i', ctrlKey: true }} />
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
336
243
  ```
337
244
 
338
- ## Playwright Integration
245
+ See the [docs site](https://github.com/soel/uidex#docs) for the convention table, annotation surfaces, and the acceptance workflow.
339
246
 
340
- Test your annotated components with type-safe locators and coverage reporting.
247
+ ### Bundler plugins
341
248
 
342
- ### Test Fixture
249
+ Optional opt-in watch integrations regenerate `uidex.gen.ts` on file save:
343
250
 
344
- ```ts
345
- import { test, expect } from 'uidex/playwright';
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.
346
254
 
347
- test('submit button works', async ({ uidex }) => {
348
- await uidex('submit-btn').click();
349
- await uidex('success-message').waitFor();
350
- });
351
- ```
255
+ All three wrap `@uidex/plugin-core` so behaviour and diagnostic shapes are identical across bundlers.
352
256
 
353
- ### Coverage Reporter
257
+ ## Config (`.uidex.json`)
354
258
 
355
- Track which `data-uidex` components are covered by tests:
259
+ Flat schema. Legacy nested `scanner.*`, `defaults`, `colors` are rejected with descriptive errors.
356
260
 
357
- ```ts
358
- // playwright.config.ts
359
- import UidexCoverageReporter from 'uidex/playwright/reporter';
360
- import { componentIds } from './src/uidex.gen.test';
361
-
362
- export default defineConfig({
363
- reporter: [
364
- ['html'],
365
- [UidexCoverageReporter, { componentIds }],
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
+ }
366
270
  ],
367
- });
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
+ }
368
283
  ```
369
284
 
370
- Outputs `uidex-coverage.json` with covered/uncovered components and percentage.
285
+ Set any `conventions.*` entry to `false` to disable that auto-discovery stage.
371
286
 
372
- ### Scaffold Tests
287
+ `typeMode` controls whether emitted id types are literal unions (`"strict"`, default) or `string` (`"loose"`, a temporary migration knob).
373
288
 
374
- Generate test stubs from your pages and features:
289
+ ## Acceptance workflow
375
290
 
376
- ```bash
377
- npx uidex scaffold [dir]
291
+ Declare criteria in the module-scoped `export const uidex`:
292
+
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
304
+
305
+ export function VideoPlayer() {
306
+ return <div data-uidex-widget="video-player">...</div>
307
+ }
378
308
  ```
379
309
 
380
- ## Claude Code Integration
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.
381
314
 
382
- Set up Claude Code with uidex-aware rules, a `/uidex:audit` skill, and a post-edit hook that validates coverage:
315
+ ## Cloud + ingest
383
316
 
384
- ```bash
385
- npx uidex claude install # Add rules + skill + hooks
386
- npx uidex claude uninstall # Remove everything
387
- ```
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`:
388
318
 
389
- Manage individually:
319
+ ```ts
320
+ import { cloud } from "uidex/cloud"
390
321
 
391
- ```bash
392
- npx uidex claude rules add|remove # .claude/rules/uidex.md
393
- npx uidex claude skill add|remove # .claude/commands/uidex/audit.md
394
- npx uidex claude hooks add|remove # PostToolUse hook in .claude/settings.json
322
+ const adapter = cloud({ projectKey: "pk_..." })
323
+ await adapter.feedback.submit(payload)
324
+ await adapter.integrations.getConfig()
395
325
  ```
396
326
 
397
- ## Package Exports
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`.
398
328
 
399
- | Import | Description |
400
- |--------|-------------|
401
- | `uidex` | React components (re-exports `uidex/react`) |
402
- | `uidex/core` | Vanilla JS classes (`createUidexUI`, `Overlay`, `Inspector`) |
403
- | `uidex/react` | React wrappers (`UidexDevtools`, `UidexOverlay`) |
404
- | `uidex/playwright` | Test fixture and helpers |
405
- | `uidex/playwright/reporter` | Coverage reporter |
406
- | `uidex/api` | API client (Node.js) |
407
- | `uidex/styles.css` | Standalone CSS (optional, auto-injected via Shadow DOM) |
329
+ When `cloud` is configured, `uidex/ingest` auto-enables:
408
330
 
409
- ## CLI Reference
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.
410
333
 
411
- ```
412
- npx uidex Show help / list commands
413
- npx uidex init Create .uidex.json and update .gitignore
414
- npx uidex scan Run component scanner
415
- npx uidex scan --audit Validate coverage and annotations
416
- npx uidex scaffold [dir] Generate Playwright test stubs
417
- npx uidex claude install Set up Claude Code integration
418
- npx uidex claude uninstall Remove Claude Code integration
419
- npx uidex login Authenticate with uidex server
420
- npx uidex logout Remove stored credentials
421
- npx uidex link Link current directory to org/project
422
- npx uidex status Show auth and link state
423
- npx uidex feedback Manage feedback (list, show, update, delete)
424
- npx uidex triage Manage triage (run, list, show, approve, dismiss, submit)
425
- npx uidex integrations Manage integrations (list, add, remove, test, targets)
426
- ```
334
+ Override per-channel via `createUidex({ ingest: { console: {...}, network: null } })`, or pass `ingest: null` to disable entirely.
427
335
 
428
- ## TypeScript
429
-
430
- Full type definitions included:
336
+ ## Playwright
431
337
 
432
338
  ```ts
433
- import type {
434
- UidexConfig,
435
- UidexDefaults,
436
- UidexMap,
437
- UidexLocation,
438
- UidexPage,
439
- UidexFeature,
440
- UidexTheme,
441
- UidexUIOptions,
442
- PrimitiveEntry,
443
- BorderStyle,
444
- LabelPosition,
445
- ButtonPosition,
446
- KeyboardShortcut,
447
- OverlayOptions,
448
- IngestConfig,
449
- FeedbackReport,
450
- FeedbackResult,
451
- FeedbackType,
452
- FeedbackSeverity,
453
- } 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
+ })
454
348
  ```
455
349
 
456
- ## Examples
457
-
458
- See `examples/` for working demos:
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.
459
351
 
460
- - `examples/next-app/` — Next.js App Router
461
- - `examples/vite-app/` — Vite + React
352
+ ```ts
353
+ // playwright.config.ts
354
+ reporter: [["list"], ["uidex/playwright/reporter"]]
355
+ ```
462
356
 
463
- ## License
357
+ ## Scripts
464
358
 
465
- 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
+ ```