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.
- package/README.md +253 -353
- package/dist/cli/cli.cjs +3324 -0
- package/dist/cli/cli.cjs.map +1 -0
- package/dist/cloud/index.cjs +169 -0
- package/dist/cloud/index.cjs.map +1 -0
- package/dist/cloud/index.js +140 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/headless/index.cjs +4143 -0
- package/dist/headless/index.cjs.map +1 -0
- package/dist/headless/index.d.cts +220 -0
- package/dist/headless/index.d.ts +220 -0
- package/dist/headless/index.js +4130 -0
- package/dist/headless/index.js.map +1 -0
- package/dist/index.cjs +8704 -9883
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +968 -146
- package/dist/index.d.ts +968 -146
- package/dist/index.js +8327 -9492
- 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 +30 -53
- package/dist/playwright/index.d.ts +30 -53
- 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 +8706 -9883
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +720 -146
- package/dist/react/index.d.ts +720 -146
- package/dist/react/index.js +8518 -9629
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +3360 -0
- package/dist/scan/index.cjs.map +1 -0
- package/dist/scan/index.d.cts +378 -0
- package/dist/scan/index.d.ts +378 -0
- package/dist/scan/index.js +3303 -0
- package/dist/scan/index.js.map +1 -0
- package/package.json +67 -60
- package/templates/claude/audit.md +43 -0
- package/templates/claude/rules.md +227 -0
- package/claude/audit-command.md +0 -46
- package/claude/rules.md +0 -167
- package/dist/api/index.cjs +0 -254
- package/dist/api/index.cjs.map +0 -1
- package/dist/api/index.d.cts +0 -236
- package/dist/api/index.d.ts +0 -236
- package/dist/api/index.js +0 -226
- package/dist/api/index.js.map +0 -1
- package/dist/core/index.cjs +0 -11045
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -424
- package/dist/core/index.d.ts +0 -424
- package/dist/core/index.global.js +0 -66516
- package/dist/core/index.global.js.map +0 -1
- package/dist/core/index.js +0 -10995
- package/dist/core/index.js.map +0 -1
- package/dist/core/style.css +0 -1529
- package/dist/scripts/cli.cjs +0 -3904
- package/uidex.schema.json +0 -93
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uidex",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Convention-driven UI element registry and devtools surface for React apps.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
@@ -12,17 +12,26 @@
|
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
14
|
},
|
|
15
|
-
"./core": {
|
|
16
|
-
"types": "./dist/core/index.d.ts",
|
|
17
|
-
"import": "./dist/core/index.js",
|
|
18
|
-
"require": "./dist/core/index.cjs"
|
|
19
|
-
},
|
|
20
|
-
"./core/style.css": "./dist/core/style.css",
|
|
21
15
|
"./react": {
|
|
22
16
|
"types": "./dist/react/index.d.ts",
|
|
23
17
|
"import": "./dist/react/index.js",
|
|
24
18
|
"require": "./dist/react/index.cjs"
|
|
25
19
|
},
|
|
20
|
+
"./cloud": {
|
|
21
|
+
"types": "./dist/cloud/index.d.ts",
|
|
22
|
+
"import": "./dist/cloud/index.js",
|
|
23
|
+
"require": "./dist/cloud/index.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./headless": {
|
|
26
|
+
"types": "./dist/headless/index.d.ts",
|
|
27
|
+
"import": "./dist/headless/index.js",
|
|
28
|
+
"require": "./dist/headless/index.cjs"
|
|
29
|
+
},
|
|
30
|
+
"./scan": {
|
|
31
|
+
"types": "./dist/scan/index.d.ts",
|
|
32
|
+
"import": "./dist/scan/index.js",
|
|
33
|
+
"require": "./dist/scan/index.cjs"
|
|
34
|
+
},
|
|
26
35
|
"./playwright": {
|
|
27
36
|
"types": "./dist/playwright/index.d.ts",
|
|
28
37
|
"import": "./dist/playwright/index.js",
|
|
@@ -32,40 +41,33 @@
|
|
|
32
41
|
"types": "./dist/playwright/reporter.d.ts",
|
|
33
42
|
"import": "./dist/playwright/reporter.js",
|
|
34
43
|
"require": "./dist/playwright/reporter.cjs"
|
|
35
|
-
}
|
|
36
|
-
"./api": {
|
|
37
|
-
"types": "./dist/api/index.d.ts",
|
|
38
|
-
"import": "./dist/api/index.js",
|
|
39
|
-
"require": "./dist/api/index.cjs"
|
|
40
|
-
},
|
|
41
|
-
"./styles.css": "./dist/core/style.css"
|
|
44
|
+
}
|
|
42
45
|
},
|
|
43
|
-
"sideEffects":
|
|
44
|
-
"*.css"
|
|
45
|
-
],
|
|
46
|
+
"sideEffects": false,
|
|
46
47
|
"files": [
|
|
47
48
|
"dist",
|
|
48
|
-
"
|
|
49
|
-
"uidex.schema.json"
|
|
49
|
+
"templates"
|
|
50
50
|
],
|
|
51
51
|
"bin": {
|
|
52
|
-
"uidex": "dist/
|
|
52
|
+
"uidex": "dist/cli/cli.cjs"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
53
59
|
},
|
|
54
60
|
"scripts": {
|
|
55
61
|
"dev": "tsup --watch",
|
|
56
|
-
"build": "
|
|
57
|
-
"build:
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"test": "vitest",
|
|
62
|
-
"test:run": "vitest run",
|
|
63
|
-
"test:coverage": "vitest run --coverage",
|
|
62
|
+
"build": "pnpm run build:css && tsup && pnpm run check:bundles",
|
|
63
|
+
"build:css": "tailwindcss --input src/styles/tailwind.css --output src/styles/tailwind.built.css",
|
|
64
|
+
"check:bundles": "node scripts/check-bundles.mjs",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
64
67
|
"lint": "eslint src/",
|
|
65
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
|
|
66
|
-
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"",
|
|
67
68
|
"typecheck": "tsc --noEmit",
|
|
68
|
-
"
|
|
69
|
+
"views-map": "tsx scripts/extract-views-map.ts",
|
|
70
|
+
"views-map:check": "tsx scripts/extract-views-map.ts --check"
|
|
69
71
|
},
|
|
70
72
|
"peerDependencies": {
|
|
71
73
|
"@playwright/test": ">=1.40",
|
|
@@ -77,56 +79,61 @@
|
|
|
77
79
|
"optional": true
|
|
78
80
|
}
|
|
79
81
|
},
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"@clack/prompts": "^1.2.0",
|
|
84
|
+
"@zag-js/combobox": "^1.40.0",
|
|
85
|
+
"@zag-js/core": "^1.40.0",
|
|
86
|
+
"@zag-js/dialog": "^1.40.0",
|
|
87
|
+
"@zag-js/menu": "^1.40.0",
|
|
88
|
+
"@zag-js/popover": "^1.40.0",
|
|
89
|
+
"@zag-js/react": "^1.40.0",
|
|
90
|
+
"@zag-js/scroll-area": "^1.40.0",
|
|
91
|
+
"@zag-js/tabs": "^1.40.0",
|
|
92
|
+
"@zag-js/tooltip": "^1.40.0",
|
|
93
|
+
"@zag-js/types": "^1.40.0",
|
|
94
|
+
"@zag-js/utils": "^1.40.0",
|
|
95
|
+
"clsx": "^2.1.1",
|
|
96
|
+
"lucide": "^1.8.0",
|
|
97
|
+
"lucide-react": "^1.7.0",
|
|
98
|
+
"modern-screenshot": "^4.7.0",
|
|
99
|
+
"tailwind-merge": "^3.5.0",
|
|
100
|
+
"xstate": "^5.30.0",
|
|
101
|
+
"zod": "^4.3.6",
|
|
102
|
+
"zustand": "^5.0.2"
|
|
103
|
+
},
|
|
80
104
|
"devDependencies": {
|
|
81
|
-
"@eslint/js": "^9.39.2",
|
|
82
|
-
"@phosphor-icons/react": "^2.1.10",
|
|
83
105
|
"@playwright/test": "^1.58.2",
|
|
84
106
|
"@tailwindcss/cli": "^4.2.2",
|
|
85
107
|
"@testing-library/jest-dom": "^6.6.3",
|
|
86
108
|
"@testing-library/react": "^16.1.0",
|
|
87
109
|
"@types/node": "^22.10.5",
|
|
88
|
-
"@types/react": "^
|
|
89
|
-
"@types/react-dom": "^
|
|
90
|
-
"@
|
|
91
|
-
"@
|
|
110
|
+
"@types/react": "^19.2.14",
|
|
111
|
+
"@types/react-dom": "^19.2.3",
|
|
112
|
+
"@uidex/eslint-config": "workspace:*",
|
|
113
|
+
"@uidex/tsconfig": "workspace:*",
|
|
92
114
|
"@vitejs/plugin-react": "^4.3.4",
|
|
93
|
-
"@vitest/coverage-v8": "^2.1.8",
|
|
94
115
|
"eslint": "^9.18.0",
|
|
95
|
-
"eslint-plugin-react": "^7.37.4",
|
|
96
|
-
"eslint-plugin-react-hooks": "^5.1.0",
|
|
97
116
|
"jsdom": "^26.0.0",
|
|
98
|
-
"prettier": "^3.4.2",
|
|
99
117
|
"react": "^18.3.1",
|
|
100
118
|
"react-dom": "^18.3.1",
|
|
101
|
-
"shadcn": "^4.1.2",
|
|
102
119
|
"tailwindcss": "^4.2.2",
|
|
103
120
|
"tsup": "^8.3.5",
|
|
104
121
|
"tsx": "^4.7.0",
|
|
105
|
-
"
|
|
106
|
-
"typescript": "^5.7.3",
|
|
107
|
-
"typescript-eslint": "^8.54.0",
|
|
122
|
+
"typescript": "^5.9.3",
|
|
108
123
|
"vitest": "^2.1.8"
|
|
109
124
|
},
|
|
110
125
|
"keywords": [
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"overlay",
|
|
114
|
-
"component",
|
|
126
|
+
"uidex",
|
|
127
|
+
"devtools",
|
|
115
128
|
"react",
|
|
116
|
-
"
|
|
117
|
-
"
|
|
129
|
+
"playwright",
|
|
130
|
+
"feedback",
|
|
131
|
+
"scanner"
|
|
118
132
|
],
|
|
119
133
|
"author": "soel",
|
|
120
134
|
"license": "MIT",
|
|
121
135
|
"repository": {
|
|
122
136
|
"type": "git",
|
|
123
137
|
"url": "https://github.com/soel/uidex"
|
|
124
|
-
},
|
|
125
|
-
"dependencies": {
|
|
126
|
-
"@base-ui/react": "^1.3.0",
|
|
127
|
-
"class-variance-authority": "^0.7.1",
|
|
128
|
-
"clsx": "^2.1.1",
|
|
129
|
-
"lucide-react": "^1.7.0",
|
|
130
|
-
"tailwind-merge": "^3.5.0"
|
|
131
138
|
}
|
|
132
139
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Run `npx uidex scan --audit` and fix all reported diagnostics.
|
|
2
|
+
|
|
3
|
+
1. **`marker-md-ignored`** — legacy `UIDEX_PAGE.md` / `UIDEX_FEATURE.md` files from v1. Migrate their content:
|
|
4
|
+
- Create a `uidex.page.ts` (in the route directory) or `uidex.feature.ts` (in the feature directory) and add an `export const uidex` block carrying the `page`/`feature` id, the description, and the acceptance list.
|
|
5
|
+
- Each `- [ ]` checkbox becomes one entry in the `acceptance` array.
|
|
6
|
+
- Delete the `UIDEX_PAGE.md` / `UIDEX_FEATURE.md` file.
|
|
7
|
+
|
|
8
|
+
2. **`acceptance-uncovered`** — a declared `@acceptance` criterion has no flow exercising it.
|
|
9
|
+
- For widgets: run `npx uidex scaffold widget <id>` to generate a Playwright stub in `e2e/` with one `test()` per criterion; fill in the stubs.
|
|
10
|
+
- For features/pages: write a Playwright spec under `e2e/**` tagged `{ tag: "@uidex:flow" }` that exercises the criterion by calling `uidex('<element-id>').*()` on the relevant elements.
|
|
11
|
+
|
|
12
|
+
3. **`unknown-reference`** — a flow's `uidex('<id>')` call references an id that isn't in the registry.
|
|
13
|
+
- If the id is a typo, fix the string literal in the spec.
|
|
14
|
+
- If the component was removed, remove the call.
|
|
15
|
+
- If the component lost its `data-uidex`, restore it.
|
|
16
|
+
- Dynamic ids (variables, template literals) are invisible to the scanner. If a literal is flagged, it is actually a literal in the source.
|
|
17
|
+
|
|
18
|
+
4. **`scope-leak`** — a primitive scoped to `feature:<name>` or `page:<name>` is being imported from outside that scope.
|
|
19
|
+
- Either move the primitive to `src/components/ui/**` so it becomes global, or route the consumer through an appropriate boundary.
|
|
20
|
+
|
|
21
|
+
5. **`missing-element-annotation`** (INFO) — an interactive `<button>`, `<a>`, `<input>`, `<select>`, or `<textarea>` has no `data-uidex`.
|
|
22
|
+
- Add `data-uidex="<kebab-id>"`. Skip this diagnostic only when the element is purely decorative (e.g., a skip link that never needs automated reference).
|
|
23
|
+
|
|
24
|
+
6. **`acceptance-orphan`** — an `@acceptance` tag appears on a JSDoc that doesn't declare `@uidex page|feature|widget`.
|
|
25
|
+
- Move the acceptance tags into the correct JSDoc block, or remove them.
|
|
26
|
+
|
|
27
|
+
7. **`prefer-well-known-file`** (INFO) — a page or feature's `export const uidex` lives on an arbitrary file instead of the well-known file (`uidex.page.ts` for pages, `uidex.feature.ts` for features). Migrate:
|
|
28
|
+
- Create `uidex.page.ts` (in the route directory) or `uidex.feature.ts` (in the feature directory).
|
|
29
|
+
- Move the `export const uidex` block from the current file into the new well-known file.
|
|
30
|
+
- Remove the export (and any now-unused `Uidex` type import) from the original file.
|
|
31
|
+
- Run `npx uidex scan` to regenerate the gen file — `loc.file` updates but the entity itself is unchanged.
|
|
32
|
+
|
|
33
|
+
After fixing, run `npx uidex scan` to regenerate the gen file before re-running the audit.
|
|
34
|
+
|
|
35
|
+
## Decision rules for primitives
|
|
36
|
+
|
|
37
|
+
Add `data-uidex-primitive="kebab-name"` ONLY when the component lives outside `src/components/ui/**` and meets all of:
|
|
38
|
+
|
|
39
|
+
- Exports a reusable presentational component (not a page, layout, or feature orchestrator).
|
|
40
|
+
- Wraps native elements or other primitives — no business logic or data fetching (`useQuery`, `useMutation`, etc.).
|
|
41
|
+
- Imported by multiple consumers.
|
|
42
|
+
|
|
43
|
+
If it's in `src/components/ui/**`, the scanner auto-promotes it — no attribute needed.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# uidex conventions
|
|
2
|
+
|
|
3
|
+
This project uses [uidex](https://github.com/soel/uidex) for UI element tracking and feedback ingestion.
|
|
4
|
+
|
|
5
|
+
## AI behavior
|
|
6
|
+
|
|
7
|
+
Before editing any file inside a feature directory (`src/features/<name>/`) or a Next.js route directory, read the entity's well-known metadata file first:
|
|
8
|
+
|
|
9
|
+
- Feature directories: `uidex.feature.ts` — the feature's `export const uidex` block lives here.
|
|
10
|
+
- Route directories: `uidex.page.ts` — the page's `export const uidex` block lives here.
|
|
11
|
+
|
|
12
|
+
These files declare `acceptance` criteria and a `description`. Treat them as **context steering**, not test assertions:
|
|
13
|
+
|
|
14
|
+
- Acceptance criteria capture what matters about the page or feature so changes preserve the right behaviors. They are NOT the testing source of truth — Playwright flows under `e2e/**` are.
|
|
15
|
+
- Before making a change, check whether it would violate a declared acceptance criterion. If it would, flag the conflict to the user before proceeding.
|
|
16
|
+
- If a change adds or removes user-visible behavior, update the acceptance list in the well-known file alongside the code change.
|
|
17
|
+
|
|
18
|
+
If a feature or page directory has no `uidex.feature.ts` / `uidex.page.ts`, the metadata may live on an arbitrary file in the directory (legacy convention) — `uidex scan --audit` will surface a `prefer-well-known-file` diagnostic with the current location.
|
|
19
|
+
|
|
20
|
+
## Entity model
|
|
21
|
+
|
|
22
|
+
Eight entity kinds make up the registry. Most are discovered by convention; annotations only override.
|
|
23
|
+
|
|
24
|
+
| Kind | Source | How discovered |
|
|
25
|
+
| ----------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| `element` | Interactive DOM element | `data-uidex="id"` (always manual) |
|
|
27
|
+
| `region` | Structural area | `data-uidex-region="id"` OR HTML5 landmark (`<header>`/`<nav>`/`<main>`/`<aside>`/`<footer>`/`role="region"`) |
|
|
28
|
+
| `widget` | Composite behavioural unit (video player, date picker, autocomplete) | `data-uidex-widget="id"` on the DOM root **AND** `export const uidex = { widget: "id", ... }` on the component module |
|
|
29
|
+
| `primitive` | Reusable presentational component definition | Any file under `src/components/ui/**` (auto-promoted); `data-uidex-primitive="name"` opts in a non-conventional location |
|
|
30
|
+
| `page` | Route-rendered top-level | Auto-derived from your framework's route detection (Next.js / Vite / TanStack Router); `export const uidex = { page: "id", ... }` overrides |
|
|
31
|
+
| `feature` | Cross-cutting capability | Any directory under `src/features/*`; `export const uidex = { feature: "id", ... }` overrides |
|
|
32
|
+
| `flow` | End-to-end user journey | Top-level `test.describe` in `e2e/**` (auto-promoted); `{ tag: "@uidex:flow" }` adds richer metadata; opt out with `export const uidex = { notFlow: true }` |
|
|
33
|
+
| `route` | URL → page mapping | Auto-derived from your framework's router |
|
|
34
|
+
|
|
35
|
+
## DOM annotations
|
|
36
|
+
|
|
37
|
+
Four attributes. All use kebab-case ids. DOM attributes are the sole surface for multi-per-file kinds (elements, regions, widget roots).
|
|
38
|
+
|
|
39
|
+
### `data-uidex` — every interactive element
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<button data-uidex="save-btn">Save</button>
|
|
43
|
+
<input data-uidex="email-field" />
|
|
44
|
+
<a data-uidex="nav-home" href="/">Home</a>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`uidex scan --audit` flags unannotated `<button>`, `<a>`, `<input>`, `<select>`, `<textarea>` as INFO diagnostics. Rename carefully — ids are referenced by flow tests.
|
|
48
|
+
|
|
49
|
+
### `data-uidex-region` — structural regions (only when HTML5 doesn't suffice)
|
|
50
|
+
|
|
51
|
+
HTML5 landmarks (`<header>`, `<nav>`, `<main>`, `<aside>`, `<footer>`, `role="region"`) auto-register as regions. Only add `data-uidex-region` on non-semantic `<div>` wrappers you want in the registry:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<div data-uidex-region="settings-form">
|
|
55
|
+
<button data-uidex="save-btn">Save</button>
|
|
56
|
+
</div>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `data-uidex-widget` — composite behavioural units
|
|
60
|
+
|
|
61
|
+
Place on the root of a self-contained UI unit with internal state (video player, date picker, rich text editor). The DOM attribute is the runtime boundary; acceptance criteria live on the `export const uidex` at the component definition.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import type { Uidex } from "@/uidex.gen"
|
|
65
|
+
|
|
66
|
+
export const uidex = {
|
|
67
|
+
widget: "video-player",
|
|
68
|
+
acceptance: [
|
|
69
|
+
"Plays/pauses on Space",
|
|
70
|
+
"Scrubber updates as video plays",
|
|
71
|
+
"M toggles mute",
|
|
72
|
+
],
|
|
73
|
+
} as const satisfies Uidex.Widget
|
|
74
|
+
|
|
75
|
+
export function VideoPlayer() {
|
|
76
|
+
return (
|
|
77
|
+
<div data-uidex-widget="video-player">
|
|
78
|
+
<button data-uidex="play">Play</button>
|
|
79
|
+
<input data-uidex="scrubber" type="range" />
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Widget id duplication rule.** The id must appear in **both** places — on `data-uidex-widget="..."` at the DOM root (runtime boundary) **and** on `export const uidex = { widget: "..." }` (scan-time metadata). The scanner cross-validates: a mismatch or one-sided presence is an error.
|
|
86
|
+
|
|
87
|
+
Child `data-uidex` elements are automatically composed onto the widget (via DOM nesting at scan time).
|
|
88
|
+
|
|
89
|
+
### `data-uidex-primitive` — reusable component definitions
|
|
90
|
+
|
|
91
|
+
Any `.tsx` under `src/components/ui/**` is auto-promoted to a primitive (name = kebab of filename). Add `data-uidex-primitive="name"` only to opt-in a reusable component that lives outside the convention directory.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// src/components/ui/button.tsx — auto-promoted as 'button', no attribute needed
|
|
95
|
+
export function Button({ children, ...props }) {
|
|
96
|
+
return <button {...props}>{children}</button>
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## `export const uidex` — module-scoped authoring surface
|
|
101
|
+
|
|
102
|
+
One named export per module, on the file that **defines** the entity. The RHS must be a plain AST literal — object / array / string / number / boolean literals, optional `as const`, optional `satisfies Uidex.<Kind>`. Identifier references in value position, calls, spreads, conditionals, and template literals with expressions are rejected with an error diagnostic.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Page
|
|
106
|
+
import type { Uidex } from "@/uidex.gen"
|
|
107
|
+
|
|
108
|
+
export const uidex = {
|
|
109
|
+
page: "org-settings",
|
|
110
|
+
acceptance: [
|
|
111
|
+
"User can update the organization name",
|
|
112
|
+
"Error message is shown if the update fails",
|
|
113
|
+
],
|
|
114
|
+
features: ["billing"],
|
|
115
|
+
} as const satisfies Uidex.Page
|
|
116
|
+
|
|
117
|
+
export default function SettingsPage() { ... }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Kinds (discriminator fields):
|
|
121
|
+
|
|
122
|
+
| Kind | Discriminator | Other fields |
|
|
123
|
+
| ----------- | --------------------------------- | --------------------------------------------------------------------- |
|
|
124
|
+
| `page` | `page: PageId \| false` | `acceptance?`, `features?`, `widgets?`, `description?` |
|
|
125
|
+
| `feature` | `feature: FeatureId \| false` | `acceptance?`, `description?` |
|
|
126
|
+
| `widget` | `widget: WidgetId` | `acceptance?`, `description?` |
|
|
127
|
+
| `primitive` | `primitive: PrimitiveId \| false` | `description?` |
|
|
128
|
+
| `flow` | `flow: FlowId` | `description?` |
|
|
129
|
+
| opt-out | `notFlow: true` | (no other fields — opts a Playwright spec out of flow auto-promotion) |
|
|
130
|
+
|
|
131
|
+
Setting `page: false` / `feature: false` / `primitive: false` explicitly opts a module out of auto-promotion.
|
|
132
|
+
|
|
133
|
+
### Typed cross-references
|
|
134
|
+
|
|
135
|
+
`uidex.gen.ts` exports per-kind id unions (`PageId`, `FeatureId`, `WidgetId`, etc.) and a `Uidex` namespace of shape types. `satisfies Uidex.Page` makes `tsc` flag unknown ids at the literal — no `uidex scan --audit` round-trip required:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
export const uidex = {
|
|
139
|
+
page: "checkout",
|
|
140
|
+
features: ["billng"], // tsc error: "billng" is not assignable to FeatureId
|
|
141
|
+
} as const satisfies Uidex.Page
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Precedence
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
configuration override > export const uidex > DOM attribute > convention
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Playwright flows
|
|
151
|
+
|
|
152
|
+
End-to-end user journeys are Playwright tests in `e2e/**`. Any top-level `test.describe` auto-promotes to a flow; the `@uidex:flow` tag adds richer metadata. Opt out by adding `export const uidex = { notFlow: true } as const satisfies Uidex.NotFlow` at the top of the spec file.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { expect, test } from "uidex/playwright"
|
|
156
|
+
import type { Uidex } from "@/uidex.gen"
|
|
157
|
+
|
|
158
|
+
export const uidex = {
|
|
159
|
+
flow: "add-and-complete-todo",
|
|
160
|
+
description: "User adds a todo and marks it complete.",
|
|
161
|
+
} as const satisfies Uidex.Flow
|
|
162
|
+
|
|
163
|
+
test.describe("Add and complete a todo", { tag: "@uidex:flow" }, () => {
|
|
164
|
+
test("adds a todo", async ({ uidex }) => {
|
|
165
|
+
await uidex("todo-field").fill("Buy milk")
|
|
166
|
+
await uidex("todo-add").click()
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Rules:
|
|
172
|
+
|
|
173
|
+
- One tagged describe per `flow-*.spec.ts`.
|
|
174
|
+
- Component ids inside `uidex(...)` MUST be string literals (static-analysable).
|
|
175
|
+
- `page-*.spec.ts` and `feature-*.spec.ts` scoped tests remain untagged.
|
|
176
|
+
|
|
177
|
+
## Scanner
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npx uidex scan # regenerate src/uidex.gen.ts
|
|
181
|
+
npx uidex scan --check # fail non-zero if the committed gen file is stale
|
|
182
|
+
npx uidex scan --audit # validate (--check + --lint; exits non-zero on errors)
|
|
183
|
+
npx uidex scan --json # emit diagnostics as JSON
|
|
184
|
+
npx uidex scaffold widget <id> # emit Playwright stub from declared acceptance
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Run `uidex scan` after any `data-uidex*` attribute change, `export const uidex` edit, or new flow. The gen file (`src/uidex.gen.ts` by default) is auto-generated and **committed** — consumers import types from it directly.
|
|
188
|
+
|
|
189
|
+
### Bundler plugins (optional)
|
|
190
|
+
|
|
191
|
+
Install the matching bundler plugin so `uidex.gen.ts` regenerates on file save:
|
|
192
|
+
|
|
193
|
+
- `@uidex/vite-plugin` — for Vite
|
|
194
|
+
- `@uidex/webpack-plugin` — for raw Webpack
|
|
195
|
+
- `@uidex/next-plugin` — `withUidex(config)` wrapper for Next.js (Webpack transport)
|
|
196
|
+
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
`.uidex.json` is flat. Example:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"sources": [{ "rootDir": "src" }],
|
|
204
|
+
"output": "src/uidex.gen.ts",
|
|
205
|
+
"flows": ["e2e/**/*.spec.ts"],
|
|
206
|
+
"typeMode": "strict"
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`typeMode: "strict"` (default) emits literal id unions — `tsc` rejects unknown ids at the `satisfies Uidex.X` site. `typeMode: "loose"` emits `string` — a **temporary migration knob** for repos mid-migration from legacy JSDoc.
|
|
211
|
+
|
|
212
|
+
Multi-source (monorepo) example:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"sources": [
|
|
217
|
+
{ "rootDir": "apps/web/src" },
|
|
218
|
+
{ "rootDir": "packages/ui/src", "prefix": "@org/ui" }
|
|
219
|
+
],
|
|
220
|
+
"output": "apps/web/src/uidex.gen.ts",
|
|
221
|
+
"flows": ["apps/web/e2e/**/*.spec.ts"]
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Migration from legacy JSDoc
|
|
226
|
+
|
|
227
|
+
JSDoc tags (`@uidex page|feature|widget`, `@acceptance`, `@uidex:not-flow`) are **no longer recognised** — the scanner registers nothing from them. `uidex scan --lint` ships a `legacy-jsdoc` rule that warns on every legacy tag and points at the replacement `export const uidex` form. Set `typeMode: "loose"` in `.uidex.json` during migration to keep `tsc` quiet; flip to `strict` once every JSDoc tag is gone.
|
package/claude/audit-command.md
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
Run `npx uidex scan --audit` and fix all reported issues:
|
|
2
|
-
|
|
3
|
-
1. **Uncovered components** (components missing doc coverage):
|
|
4
|
-
- Create a `UIDEX_PAGE.md` in the component's directory for page-specific components
|
|
5
|
-
- For cross-cutting components (auth, billing, search, etc.), create or update a `UIDEX_FEATURE.md` in `features/<name>/` and list the component ID in the `components:` frontmatter
|
|
6
|
-
- Include a `# Title`, `> description` blockquote, and `## Acceptance` with `- [ ]` checkboxes
|
|
7
|
-
|
|
8
|
-
2. **Route directories missing UIDEX_PAGE.md** (route with `page.tsx` but no doc):
|
|
9
|
-
- Create a `UIDEX_PAGE.md` in the route directory
|
|
10
|
-
- Without its own doc, the route's components get absorbed by a parent page doc
|
|
11
|
-
|
|
12
|
-
3. **Orphaned UIDEX_PAGE.md files** (docs with no components underneath):
|
|
13
|
-
- If the components were removed, delete the UIDEX_PAGE.md
|
|
14
|
-
- If components are missing annotations, add them
|
|
15
|
-
|
|
16
|
-
4. **Orphaned UIDEX_FEATURE.md files** (features referencing no known components):
|
|
17
|
-
- Update the `components:` frontmatter to reference valid component IDs
|
|
18
|
-
- If the feature is no longer relevant, delete it
|
|
19
|
-
|
|
20
|
-
5. **Missing descriptions** (pages/features without a `>` blockquote description):
|
|
21
|
-
- Add a `> description` blockquote immediately after the `# Title` heading
|
|
22
|
-
|
|
23
|
-
6. **Missing block annotations** (structural regions):
|
|
24
|
-
- Add `data-uidex-block="kebab-id"` to page root wrappers and section containers
|
|
25
|
-
- Blocks do NOT need JSDoc comments
|
|
26
|
-
|
|
27
|
-
7. **Missing interactive annotations**:
|
|
28
|
-
- Add `data-uidex="kebab-id"` to buttons, inputs, links, and other interactive elements
|
|
29
|
-
- Optionally add `/** @uidex id - description */` JSDoc above interactive elements
|
|
30
|
-
|
|
31
|
-
8. **Primitive annotations** — use `data-uidex-primitive="kebab-name"` when a component is a **reusable presentational building block**, not a consumer/page component. Apply when ALL of these are true:
|
|
32
|
-
- The file **exports** a component (not a page or layout)
|
|
33
|
-
- The component **wraps native HTML elements or other primitives** (buttons, inputs, cards, badges) rather than composing app-level features
|
|
34
|
-
- The component is **imported by multiple consumers** or is designed to be reusable
|
|
35
|
-
- The component does **not fetch data or contain business logic** — it receives everything via props
|
|
36
|
-
|
|
37
|
-
Do NOT add `data-uidex-primitive` to:
|
|
38
|
-
- Page components, layouts, or route-level files
|
|
39
|
-
- Feature orchestrators (dialogs that compose multiple primitives with business logic)
|
|
40
|
-
- Components that call hooks for data fetching (`useQuery`, `useMutation`, etc.)
|
|
41
|
-
- One-off components only used in a single place (use `data-uidex` or `data-uidex-block` instead)
|
|
42
|
-
|
|
43
|
-
Files with `data-uidex-primitive` are exempt from missing-annotation lint checks.
|
|
44
|
-
The scanner detects primitives by the attribute alone — directory structure does not matter.
|
|
45
|
-
|
|
46
|
-
After fixing all issues, run `npx uidex scan` to regenerate the component registry.
|