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/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uidex",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.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,34 +41,21 @@
|
|
|
32
41
|
"types": "./dist/playwright/reporter.d.ts",
|
|
33
42
|
"import": "./dist/playwright/reporter.js",
|
|
34
43
|
"require": "./dist/playwright/reporter.cjs"
|
|
35
|
-
}
|
|
36
|
-
"./styles.css": "./dist/core/style.css"
|
|
44
|
+
}
|
|
37
45
|
},
|
|
38
|
-
"sideEffects":
|
|
39
|
-
"*.css"
|
|
40
|
-
],
|
|
46
|
+
"sideEffects": false,
|
|
41
47
|
"files": [
|
|
42
48
|
"dist",
|
|
43
|
-
"
|
|
44
|
-
"uidex.schema.json"
|
|
49
|
+
"templates"
|
|
45
50
|
],
|
|
46
51
|
"bin": {
|
|
47
|
-
"uidex": "dist/
|
|
52
|
+
"uidex": "dist/cli/cli.cjs"
|
|
48
53
|
},
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"scan": "tsx scripts/cli.ts scan",
|
|
55
|
-
"test": "vitest",
|
|
56
|
-
"test:run": "vitest run",
|
|
57
|
-
"test:coverage": "vitest run --coverage",
|
|
58
|
-
"lint": "eslint src/",
|
|
59
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
|
|
60
|
-
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"",
|
|
61
|
-
"typecheck": "tsc --noEmit",
|
|
62
|
-
"prepublishOnly": "npm run build && npm run test:run && npm run typecheck"
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
63
59
|
},
|
|
64
60
|
"peerDependencies": {
|
|
65
61
|
"@playwright/test": ">=1.40",
|
|
@@ -67,54 +63,76 @@
|
|
|
67
63
|
"react-dom": ">=18"
|
|
68
64
|
},
|
|
69
65
|
"peerDependenciesMeta": {
|
|
70
|
-
"react": {
|
|
71
|
-
"optional": true
|
|
72
|
-
},
|
|
73
|
-
"react-dom": {
|
|
74
|
-
"optional": true
|
|
75
|
-
},
|
|
76
66
|
"@playwright/test": {
|
|
77
67
|
"optional": true
|
|
78
68
|
}
|
|
79
69
|
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@clack/prompts": "^1.2.0",
|
|
72
|
+
"@zag-js/combobox": "^1.40.0",
|
|
73
|
+
"@zag-js/core": "^1.40.0",
|
|
74
|
+
"@zag-js/dialog": "^1.40.0",
|
|
75
|
+
"@zag-js/menu": "^1.40.0",
|
|
76
|
+
"@zag-js/popover": "^1.40.0",
|
|
77
|
+
"@zag-js/react": "^1.40.0",
|
|
78
|
+
"@zag-js/scroll-area": "^1.40.0",
|
|
79
|
+
"@zag-js/tabs": "^1.40.0",
|
|
80
|
+
"@zag-js/tooltip": "^1.40.0",
|
|
81
|
+
"@zag-js/types": "^1.40.0",
|
|
82
|
+
"@zag-js/utils": "^1.40.0",
|
|
83
|
+
"clsx": "^2.1.1",
|
|
84
|
+
"lucide": "^1.8.0",
|
|
85
|
+
"lucide-react": "^1.7.0",
|
|
86
|
+
"tailwind-merge": "^3.5.0",
|
|
87
|
+
"xstate": "^5.30.0",
|
|
88
|
+
"zod": "^4.3.6",
|
|
89
|
+
"zustand": "^5.0.2"
|
|
90
|
+
},
|
|
80
91
|
"devDependencies": {
|
|
81
|
-
"@eslint/js": "^9.39.2",
|
|
82
92
|
"@playwright/test": "^1.58.2",
|
|
93
|
+
"@tailwindcss/cli": "^4.2.2",
|
|
83
94
|
"@testing-library/jest-dom": "^6.6.3",
|
|
84
95
|
"@testing-library/react": "^16.1.0",
|
|
85
96
|
"@types/node": "^22.10.5",
|
|
86
|
-
"@types/react": "^
|
|
87
|
-
"@types/react-dom": "^
|
|
88
|
-
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
|
89
|
-
"@typescript-eslint/parser": "^8.21.0",
|
|
97
|
+
"@types/react": "^19.2.14",
|
|
98
|
+
"@types/react-dom": "^19.2.3",
|
|
90
99
|
"@vitejs/plugin-react": "^4.3.4",
|
|
91
|
-
"@vitest/coverage-v8": "^2.1.8",
|
|
92
100
|
"eslint": "^9.18.0",
|
|
93
|
-
"eslint-plugin-react": "^7.37.4",
|
|
94
|
-
"eslint-plugin-react-hooks": "^5.1.0",
|
|
95
101
|
"jsdom": "^26.0.0",
|
|
96
|
-
"prettier": "^3.4.2",
|
|
97
102
|
"react": "^18.3.1",
|
|
98
103
|
"react-dom": "^18.3.1",
|
|
104
|
+
"tailwindcss": "^4.2.2",
|
|
99
105
|
"tsup": "^8.3.5",
|
|
100
106
|
"tsx": "^4.7.0",
|
|
101
|
-
"typescript": "^5.
|
|
102
|
-
"
|
|
103
|
-
"
|
|
107
|
+
"typescript": "^5.9.3",
|
|
108
|
+
"vitest": "^2.1.8",
|
|
109
|
+
"@uidex/eslint-config": "0.0.0",
|
|
110
|
+
"@uidex/tsconfig": "0.0.0"
|
|
104
111
|
},
|
|
105
112
|
"keywords": [
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"overlay",
|
|
109
|
-
"component",
|
|
113
|
+
"uidex",
|
|
114
|
+
"devtools",
|
|
110
115
|
"react",
|
|
111
|
-
"
|
|
112
|
-
"
|
|
116
|
+
"playwright",
|
|
117
|
+
"feedback",
|
|
118
|
+
"scanner"
|
|
113
119
|
],
|
|
114
120
|
"author": "soel",
|
|
115
121
|
"license": "MIT",
|
|
116
122
|
"repository": {
|
|
117
123
|
"type": "git",
|
|
118
124
|
"url": "https://github.com/soel/uidex"
|
|
125
|
+
},
|
|
126
|
+
"scripts": {
|
|
127
|
+
"dev": "tsup --watch",
|
|
128
|
+
"build": "pnpm run build:css && tsup && pnpm run check:bundles",
|
|
129
|
+
"build:css": "tailwindcss --input src/styles/tailwind.css --output src/styles/tailwind.built.css",
|
|
130
|
+
"check:bundles": "node scripts/check-bundles.mjs",
|
|
131
|
+
"test": "vitest run",
|
|
132
|
+
"test:watch": "vitest",
|
|
133
|
+
"lint": "eslint src/",
|
|
134
|
+
"typecheck": "tsc --noEmit",
|
|
135
|
+
"views-map": "tsx scripts/extract-views-map.ts",
|
|
136
|
+
"views-map:check": "tsx scripts/extract-views-map.ts --check"
|
|
119
137
|
}
|
|
120
|
-
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
- Copy the description and acceptance list into a `@uidex page <id>` or `@uidex feature <id>` JSDoc block on the corresponding module (`page.tsx` for pages; the feature's main index/component for features).
|
|
5
|
+
- Each `- [ ]` checkbox becomes one `@acceptance <text>` tag.
|
|
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
|
+
After fixing, run `npx uidex scan` to regenerate the gen file before re-running the audit.
|
|
28
|
+
|
|
29
|
+
## Decision rules for primitives
|
|
30
|
+
|
|
31
|
+
Add `data-uidex-primitive="kebab-name"` ONLY when the component lives outside `src/components/ui/**` and meets all of:
|
|
32
|
+
|
|
33
|
+
- Exports a reusable presentational component (not a page, layout, or feature orchestrator).
|
|
34
|
+
- Wraps native elements or other primitives — no business logic or data fetching (`useQuery`, `useMutation`, etc.).
|
|
35
|
+
- Imported by multiple consumers.
|
|
36
|
+
|
|
37
|
+
If it's in `src/components/ui/**`, the scanner auto-promotes it — no attribute needed.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# uidex conventions
|
|
2
|
+
|
|
3
|
+
This project uses [uidex](https://github.com/soel/uidex) for UI element tracking and feedback ingestion.
|
|
4
|
+
|
|
5
|
+
## Entity model
|
|
6
|
+
|
|
7
|
+
Eight entity kinds make up the registry. Most are discovered by convention; annotations only override.
|
|
8
|
+
|
|
9
|
+
| Kind | Source | How discovered |
|
|
10
|
+
| ----------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `element` | Interactive DOM element | `data-uidex="id"` (always manual) |
|
|
12
|
+
| `region` | Structural area | `data-uidex-region="id"` OR HTML5 landmark (`<header>`/`<nav>`/`<main>`/`<aside>`/`<footer>`/`role="region"`) |
|
|
13
|
+
| `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 |
|
|
14
|
+
| `primitive` | Reusable presentational component definition | Any file under `src/components/ui/**` (auto-promoted); `data-uidex-primitive="name"` opts in a non-conventional location |
|
|
15
|
+
| `page` | Route-rendered top-level | Auto-derived from your framework's route detection (Next.js / Vite / TanStack Router); `export const uidex = { page: "id", ... }` overrides |
|
|
16
|
+
| `feature` | Cross-cutting capability | Any directory under `src/features/*`; `export const uidex = { feature: "id", ... }` overrides |
|
|
17
|
+
| `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 }` |
|
|
18
|
+
| `route` | URL → page mapping | Auto-derived from your framework's router |
|
|
19
|
+
|
|
20
|
+
## DOM annotations
|
|
21
|
+
|
|
22
|
+
Four attributes. All use kebab-case ids. DOM attributes are the sole surface for multi-per-file kinds (elements, regions, widget roots).
|
|
23
|
+
|
|
24
|
+
### `data-uidex` — every interactive element
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
<button data-uidex="save-btn">Save</button>
|
|
28
|
+
<input data-uidex="email-field" />
|
|
29
|
+
<a data-uidex="nav-home" href="/">Home</a>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`uidex scan --audit` flags unannotated `<button>`, `<a>`, `<input>`, `<select>`, `<textarea>` as INFO diagnostics. Rename carefully — ids are referenced by flow tests.
|
|
33
|
+
|
|
34
|
+
### `data-uidex-region` — structural regions (only when HTML5 doesn't suffice)
|
|
35
|
+
|
|
36
|
+
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:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<div data-uidex-region="settings-form">
|
|
40
|
+
<button data-uidex="save-btn">Save</button>
|
|
41
|
+
</div>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `data-uidex-widget` — composite behavioural units
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import type { Uidex } from "@/uidex.gen"
|
|
50
|
+
|
|
51
|
+
export const uidex = {
|
|
52
|
+
widget: "video-player",
|
|
53
|
+
acceptance: [
|
|
54
|
+
"Plays/pauses on Space",
|
|
55
|
+
"Scrubber updates as video plays",
|
|
56
|
+
"M toggles mute",
|
|
57
|
+
],
|
|
58
|
+
} as const satisfies Uidex.Widget
|
|
59
|
+
|
|
60
|
+
export function VideoPlayer() {
|
|
61
|
+
return (
|
|
62
|
+
<div data-uidex-widget="video-player">
|
|
63
|
+
<button data-uidex="play">Play</button>
|
|
64
|
+
<input data-uidex="scrubber" type="range" />
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**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.
|
|
71
|
+
|
|
72
|
+
Child `data-uidex` elements are automatically composed onto the widget (via DOM nesting at scan time).
|
|
73
|
+
|
|
74
|
+
### `data-uidex-primitive` — reusable component definitions
|
|
75
|
+
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// src/components/ui/button.tsx — auto-promoted as 'button', no attribute needed
|
|
80
|
+
export function Button({ children, ...props }) {
|
|
81
|
+
return <button {...props}>{children}</button>
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## `export const uidex` — module-scoped authoring surface
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// Page
|
|
91
|
+
import type { Uidex } from "@/uidex.gen"
|
|
92
|
+
|
|
93
|
+
export const uidex = {
|
|
94
|
+
page: "org-settings",
|
|
95
|
+
acceptance: [
|
|
96
|
+
"User can update the organization name",
|
|
97
|
+
"Error message is shown if the update fails",
|
|
98
|
+
],
|
|
99
|
+
features: ["billing"],
|
|
100
|
+
} as const satisfies Uidex.Page
|
|
101
|
+
|
|
102
|
+
export default function SettingsPage() { ... }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Kinds (discriminator fields):
|
|
106
|
+
|
|
107
|
+
| Kind | Discriminator | Other fields |
|
|
108
|
+
| ----------- | --------------------------------- | --------------------------------------------------------------------- |
|
|
109
|
+
| `page` | `page: PageId \| false` | `acceptance?`, `features?`, `widgets?`, `description?` |
|
|
110
|
+
| `feature` | `feature: FeatureId \| false` | `acceptance?`, `description?` |
|
|
111
|
+
| `widget` | `widget: WidgetId` | `acceptance?`, `description?` |
|
|
112
|
+
| `primitive` | `primitive: PrimitiveId \| false` | `description?` |
|
|
113
|
+
| `flow` | `flow: FlowId` | `description?` |
|
|
114
|
+
| opt-out | `notFlow: true` | (no other fields — opts a Playwright spec out of flow auto-promotion) |
|
|
115
|
+
|
|
116
|
+
Setting `page: false` / `feature: false` / `primitive: false` explicitly opts a module out of auto-promotion.
|
|
117
|
+
|
|
118
|
+
### Typed cross-references
|
|
119
|
+
|
|
120
|
+
`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:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
export const uidex = {
|
|
124
|
+
page: "checkout",
|
|
125
|
+
features: ["billng"], // tsc error: "billng" is not assignable to FeatureId
|
|
126
|
+
} as const satisfies Uidex.Page
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Precedence
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
configuration override > export const uidex > DOM attribute > convention
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Playwright flows
|
|
136
|
+
|
|
137
|
+
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.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { expect, test } from "uidex/playwright"
|
|
141
|
+
import type { Uidex } from "@/uidex.gen"
|
|
142
|
+
|
|
143
|
+
export const uidex = {
|
|
144
|
+
flow: "add-and-complete-todo",
|
|
145
|
+
description: "User adds a todo and marks it complete.",
|
|
146
|
+
} as const satisfies Uidex.Flow
|
|
147
|
+
|
|
148
|
+
test.describe("Add and complete a todo", { tag: "@uidex:flow" }, () => {
|
|
149
|
+
test("adds a todo", async ({ uidex }) => {
|
|
150
|
+
await uidex("todo-field").fill("Buy milk")
|
|
151
|
+
await uidex("todo-add").click()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Rules:
|
|
157
|
+
|
|
158
|
+
- One tagged describe per `flow-*.spec.ts`.
|
|
159
|
+
- Component ids inside `uidex(...)` MUST be string literals (static-analysable).
|
|
160
|
+
- `page-*.spec.ts` and `feature-*.spec.ts` scoped tests remain untagged.
|
|
161
|
+
|
|
162
|
+
## Scanner
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npx uidex scan # regenerate src/uidex.gen.ts
|
|
166
|
+
npx uidex scan --check # fail non-zero if the committed gen file is stale
|
|
167
|
+
npx uidex scan --audit # validate (--check + --lint; exits non-zero on errors)
|
|
168
|
+
npx uidex scan --json # emit diagnostics as JSON
|
|
169
|
+
npx uidex scaffold widget <id> # emit Playwright stub from declared acceptance
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
### Bundler plugins (optional)
|
|
175
|
+
|
|
176
|
+
Install the matching bundler plugin so `uidex.gen.ts` regenerates on file save:
|
|
177
|
+
|
|
178
|
+
- `@uidex/vite-plugin` — for Vite
|
|
179
|
+
- `@uidex/webpack-plugin` — for raw Webpack
|
|
180
|
+
- `@uidex/next-plugin` — `withUidex(config)` wrapper for Next.js (Webpack transport)
|
|
181
|
+
|
|
182
|
+
## Configuration
|
|
183
|
+
|
|
184
|
+
`.uidex.json` is flat. Example:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"sources": [{ "rootDir": "src" }],
|
|
189
|
+
"output": "src/uidex.gen.ts",
|
|
190
|
+
"flows": ["e2e/**/*.spec.ts"],
|
|
191
|
+
"typeMode": "strict"
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`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.
|
|
196
|
+
|
|
197
|
+
Multi-source (monorepo) example:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"sources": [
|
|
202
|
+
{ "rootDir": "apps/web/src" },
|
|
203
|
+
{ "rootDir": "packages/ui/src", "prefix": "@org/ui" }
|
|
204
|
+
],
|
|
205
|
+
"output": "apps/web/src/uidex.gen.ts",
|
|
206
|
+
"flows": ["apps/web/e2e/**/*.spec.ts"]
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Migration from legacy JSDoc
|
|
211
|
+
|
|
212
|
+
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,16 +0,0 @@
|
|
|
1
|
-
Run `npx uidex scan --check` and fix all reported issues:
|
|
2
|
-
|
|
3
|
-
1. **Uncovered components** (components missing UIDEX_PAGE.md coverage):
|
|
4
|
-
- Create a `UIDEX_PAGE.md` in the nearest feature directory
|
|
5
|
-
- Include a heading, brief description, and acceptance criteria
|
|
6
|
-
|
|
7
|
-
2. **Orphaned UIDEX_PAGE.md files** (docs with no components underneath):
|
|
8
|
-
- If the components were removed, delete the UIDEX_PAGE.md
|
|
9
|
-
- If components are missing `data-uidex` attributes, add them
|
|
10
|
-
|
|
11
|
-
3. **Review interactive elements** without `data-uidex` attributes:
|
|
12
|
-
- Scan modified files for buttons, inputs, links, and other interactive elements
|
|
13
|
-
- Add `data-uidex="kebab-id"` attributes where missing
|
|
14
|
-
- Add `/** @uidex id - description */` JSDoc above each annotated element
|
|
15
|
-
|
|
16
|
-
After fixing all issues, run `npx uidex scan` to regenerate the component registry.
|
package/claude/rules.md
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# uidex conventions
|
|
2
|
-
|
|
3
|
-
This project uses [uidex](https://github.com/soel/uidex) for UI element tracking and documentation.
|
|
4
|
-
|
|
5
|
-
## data-uidex attributes
|
|
6
|
-
|
|
7
|
-
- Every user-facing interactive element MUST have a `data-uidex="kebab-id"` attribute.
|
|
8
|
-
- IDs should be descriptive and kebab-cased: `data-uidex="submit-btn"`, `data-uidex="user-avatar"`.
|
|
9
|
-
- When creating new components with interactive elements, always add `data-uidex` attributes.
|
|
10
|
-
- When modifying components, preserve existing `data-uidex` attributes. If you rename or remove an element, update or remove its attribute accordingly.
|
|
11
|
-
|
|
12
|
-
## @uidex JSDoc comments
|
|
13
|
-
|
|
14
|
-
Add a JSDoc comment above annotated elements describing their purpose:
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
/** @uidex submit-btn - Primary action button for form submission */
|
|
18
|
-
<button data-uidex="submit-btn">Submit</button>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Multi-line format for longer descriptions:
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
/**
|
|
25
|
-
* @uidex user-profile-card
|
|
26
|
-
* Displays user avatar, name, and role.
|
|
27
|
-
* Shown in the sidebar and team directory.
|
|
28
|
-
*/
|
|
29
|
-
<div data-uidex="user-profile-card">...</div>
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## UIDEX_PAGE.md files
|
|
33
|
-
|
|
34
|
-
Page docs describe a route/view. Components in the directory are automatically associated.
|
|
35
|
-
|
|
36
|
-
- Each page directory should have a `UIDEX_PAGE.md` with acceptance criteria.
|
|
37
|
-
- When adding components to a directory that lacks a `UIDEX_PAGE.md`, create one.
|
|
38
|
-
- When removing all components from a directory, remove its `UIDEX_PAGE.md`.
|
|
39
|
-
|
|
40
|
-
```markdown
|
|
41
|
-
# Page Name
|
|
42
|
-
|
|
43
|
-
Brief description of the page.
|
|
44
|
-
|
|
45
|
-
## Acceptance
|
|
46
|
-
- User can do X
|
|
47
|
-
- Y is displayed when Z
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## UIDEX_FEATURE.md files
|
|
51
|
-
|
|
52
|
-
Feature docs describe a cross-cutting feature. Components are explicitly listed via frontmatter.
|
|
53
|
-
|
|
54
|
-
- Use `UIDEX_FEATURE.md` for features that span multiple pages (e.g., auth, billing).
|
|
55
|
-
- List associated component IDs in the `components:` frontmatter.
|
|
56
|
-
|
|
57
|
-
```markdown
|
|
58
|
-
---
|
|
59
|
-
components:
|
|
60
|
-
- login-form
|
|
61
|
-
- signup-btn
|
|
62
|
-
---
|
|
63
|
-
# Feature Name
|
|
64
|
-
|
|
65
|
-
Brief description of the feature.
|
|
66
|
-
|
|
67
|
-
## Acceptance
|
|
68
|
-
- User can do X
|
|
69
|
-
- Y is displayed when Z
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Component registry
|
|
73
|
-
|
|
74
|
-
For a project-wide view of all components, pages, and features, read the generated registry file (default: `src/uidex.gen.ts`). This file is kept up to date by the scanner.
|
|
75
|
-
|
|
76
|
-
## Scanner
|
|
77
|
-
|
|
78
|
-
After adding, removing, or renaming `data-uidex` attributes, run:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
npx uidex scan
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
To validate that all components have UIDEX_PAGE.md coverage:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npx uidex scan --check
|
|
88
|
-
```
|