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.
- package/README.md +263 -263
- package/dist/cli/cli.cjs +3243 -0
- package/dist/cli/cli.cjs.map +1 -0
- package/dist/cloud/index.cjs +149 -0
- package/dist/cloud/index.cjs.map +1 -0
- package/dist/cloud/index.d.cts +108 -0
- package/dist/cloud/index.d.ts +108 -0
- package/dist/cloud/index.js +120 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/headless/index.cjs +3580 -0
- package/dist/headless/index.cjs.map +1 -0
- package/dist/headless/index.d.cts +214 -0
- package/dist/headless/index.d.ts +214 -0
- package/dist/headless/index.js +3562 -0
- package/dist/headless/index.js.map +1 -0
- package/dist/index.cjs +7977 -3301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +898 -108
- package/dist/index.d.ts +898 -108
- package/dist/index.js +7934 -3270
- package/dist/index.js.map +1 -1
- package/dist/playwright/index.cjs +164 -24
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.d.cts +32 -55
- package/dist/playwright/index.d.ts +32 -55
- package/dist/playwright/index.js +148 -21
- package/dist/playwright/index.js.map +1 -1
- package/dist/playwright/reporter.cjs +62 -28
- package/dist/playwright/reporter.cjs.map +1 -1
- package/dist/playwright/reporter.d.cts +24 -12
- package/dist/playwright/reporter.d.ts +24 -12
- package/dist/playwright/reporter.js +62 -28
- package/dist/playwright/reporter.js.map +1 -1
- package/dist/react/index.cjs +7970 -3267
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +670 -108
- package/dist/react/index.d.ts +670 -108
- package/dist/react/index.js +8016 -3274
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +3281 -0
- package/dist/scan/index.cjs.map +1 -0
- package/dist/scan/index.d.cts +373 -0
- package/dist/scan/index.d.ts +373 -0
- package/dist/scan/index.js +3224 -0
- package/dist/scan/index.js.map +1 -0
- package/package.json +74 -56
- package/templates/claude/audit.md +37 -0
- package/templates/claude/rules.md +212 -0
- package/claude/audit-command.md +0 -16
- package/claude/rules.md +0 -88
- package/dist/core/index.cjs +0 -3490
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -441
- package/dist/core/index.d.ts +0 -441
- package/dist/core/index.global.js +0 -3469
- package/dist/core/index.global.js.map +0 -1
- package/dist/core/index.js +0 -3444
- package/dist/core/index.js.map +0 -1
- package/dist/core/style.css +0 -971
- package/dist/scripts/cli.cjs +0 -1168
- package/uidex.schema.json +0 -93
package/README.md
CHANGED
|
@@ -1,365 +1,365 @@
|
|
|
1
1
|
# uidex
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
+
pnpm add uidex
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
## Quick
|
|
13
|
+
## Quick start (React)
|
|
12
14
|
|
|
13
15
|
```bash
|
|
14
|
-
#
|
|
15
|
-
npx uidex
|
|
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
|
-
|
|
28
|
-
import {
|
|
21
|
+
// any React root
|
|
22
|
+
import { UidexProvider, UidexMount } from "uidex/react"
|
|
29
23
|
|
|
30
|
-
function
|
|
24
|
+
export default function Root({ children }: { children: React.ReactNode }) {
|
|
31
25
|
return (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
40
|
+
```tsx
|
|
41
|
+
// React — gate <UidexMount> on NODE_ENV
|
|
42
|
+
{
|
|
43
|
+
process.env.NODE_ENV !== "production" && <UidexMount />
|
|
44
|
+
}
|
|
45
|
+
```
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
+
## Quick start (vanilla)
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createUidex } from "uidex"
|
|
58
|
+
import { cloud } from "uidex/cloud"
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
##
|
|
66
|
+
## Package layout
|
|
64
67
|
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
## Runtime API
|
|
96
111
|
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
npx uidex scan --check # Validate UIDEX_PAGE.md coverage
|
|
100
|
-
```
|
|
112
|
+
```ts
|
|
113
|
+
import { createUidex } from "uidex"
|
|
101
114
|
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
+
`createUidex` returns:
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
121
|
-
|
|
137
|
+
### React wiring
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { UidexProvider, UidexMount, useUidex } from "uidex/react"
|
|
122
141
|
```
|
|
123
142
|
|
|
124
|
-
|
|
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
|
-
|
|
147
|
+
### Headless
|
|
127
148
|
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
152
|
+
const h = createHeadless({ theme: "auto" })
|
|
153
|
+
h.mount()
|
|
154
|
+
h.overlay.show(el, { label: "target" })
|
|
155
|
+
h.inspector.start()
|
|
156
|
+
```
|
|
136
157
|
|
|
137
|
-
|
|
158
|
+
No panels, no `ViewStack`. Same Shadow DOM, Overlay, MenuBar, CursorTooltip, CommandPalette (empty). Intended for agents, screencasts, and non-panel consumers.
|
|
138
159
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
182
|
+
import { createReactPanel } from "uidex/react"
|
|
160
183
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
193
|
+
### Element menu
|
|
177
194
|
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
+
`createUidex({ defaultPanels: true })` (the default) registers:
|
|
221
210
|
|
|
222
|
-
|
|
211
|
+
`commandPalettePanel`, `componentDetailPanel` (element), `pageDetailPanel`, `featureDetailPanel`, `widgetDetailPanel`, `flowDetailPanel`, `primitiveDetailPanel`, `regionDetailPanel`.
|
|
223
212
|
|
|
224
|
-
|
|
213
|
+
All eight are vanilla TypeScript built on Zag machines and Tailwind classes.
|
|
225
214
|
|
|
226
|
-
|
|
227
|
-
// Disable
|
|
228
|
-
<UidexDevtools inspectShortcut={false} />
|
|
215
|
+
### Report uidex issue
|
|
229
216
|
|
|
230
|
-
|
|
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
|
-
##
|
|
219
|
+
## Scanner
|
|
235
220
|
|
|
236
|
-
|
|
221
|
+
One pipeline, six stages, each stage in its own file and independently callable:
|
|
237
222
|
|
|
238
|
-
|
|
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
|
-
|
|
241
|
-
import { test, expect } from 'uidex/playwright';
|
|
232
|
+
CLI:
|
|
242
233
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
245
|
+
See the [docs site](https://github.com/soel/uidex#docs) for the convention table, annotation surfaces, and the acceptance workflow.
|
|
250
246
|
|
|
251
|
-
|
|
247
|
+
### Bundler plugins
|
|
252
248
|
|
|
253
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
255
|
+
All three wrap `@uidex/plugin-core` so behaviour and diagnostic shapes are identical across bundlers.
|
|
267
256
|
|
|
268
|
-
|
|
257
|
+
## Config (`.uidex.json`)
|
|
269
258
|
|
|
270
|
-
|
|
259
|
+
Flat schema. Legacy nested `scanner.*`, `defaults`, `colors` are rejected with descriptive errors.
|
|
271
260
|
|
|
272
|
-
```
|
|
273
|
-
|
|
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
|
-
|
|
285
|
+
Set any `conventions.*` entry to `false` to disable that auto-discovery stage.
|
|
277
286
|
|
|
278
|
-
|
|
287
|
+
`typeMode` controls whether emitted id types are literal unions (`"strict"`, default) or `string` (`"loose"`, a temporary migration knob).
|
|
279
288
|
|
|
280
|
-
|
|
281
|
-
npx uidex claude init # Add rules + skill + hooks
|
|
282
|
-
npx uidex claude teardown # Remove everything
|
|
283
|
-
```
|
|
289
|
+
## Acceptance workflow
|
|
284
290
|
|
|
285
|
-
|
|
291
|
+
Declare criteria in the module-scoped `export const uidex`:
|
|
286
292
|
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
315
|
+
## Cloud + ingest
|
|
315
316
|
|
|
316
|
-
|
|
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
|
-
|
|
319
|
+
```ts
|
|
320
|
+
import { cloud } from "uidex/cloud"
|
|
326
321
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
352
|
+
```ts
|
|
353
|
+
// playwright.config.ts
|
|
354
|
+
reporter: [["list"], ["uidex/playwright/reporter"]]
|
|
355
|
+
```
|
|
362
356
|
|
|
363
|
-
##
|
|
357
|
+
## Scripts
|
|
364
358
|
|
|
365
|
-
|
|
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
|
+
```
|