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/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "uidex",
3
- "version": "0.2.4",
4
- "description": "A framework-agnostic library for highlighting and annotating UI elements",
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.cjs",
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
- "claude",
49
- "uidex.schema.json"
49
+ "templates"
50
50
  ],
51
51
  "bin": {
52
- "uidex": "dist/scripts/cli.cjs"
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": "npm run build:tailwind && npm run generate:css && tsup && npm run build:css",
57
- "build:tailwind": "tailwindcss --input src/ui/tailwind.css --output src/ui/tailwind.out.css",
58
- "build:css": "cp src/core/style.css dist/core/style.css",
59
- "generate:css": "tsx scripts/generate-css-text.ts",
60
- "scan": "tsx scripts/cli.ts scan",
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
- "prepublishOnly": "npm run build && npm run test:run && npm run typecheck"
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": "^18.3.18",
89
- "@types/react-dom": "^18.3.5",
90
- "@typescript-eslint/eslint-plugin": "^8.21.0",
91
- "@typescript-eslint/parser": "^8.21.0",
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
- "tw-animate-css": "^1.4.0",
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
- "annotation",
112
- "highlight",
113
- "overlay",
114
- "component",
126
+ "uidex",
127
+ "devtools",
115
128
  "react",
116
- "vanilla-js",
117
- "framework-agnostic"
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.
@@ -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.