@exxatdesignux/ui 0.3.0 → 0.4.1

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 (214) hide show
  1. package/CHANGELOG.md +701 -6
  2. package/README.md +138 -0
  3. package/bin/init.mjs +134 -31
  4. package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
  5. package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
  6. package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
  7. package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
  8. package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
  9. package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
  10. package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
  11. package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
  12. package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
  13. package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
  14. package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
  15. package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
  16. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
  17. package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
  18. package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
  19. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
  20. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
  21. package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
  22. package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
  23. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
  24. package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
  25. package/consumer-extras/handbook/HANDBOOK.md +2 -0
  26. package/consumer-extras/handbook/glossary.md +2 -1
  27. package/consumer-extras/handbook/reference-implementations.md +31 -4
  28. package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
  29. package/consumer-extras/patterns/data-views-pattern.md +18 -16
  30. package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
  31. package/dist/components/data-table/index.js +2 -2
  32. package/dist/components/data-table/index.js.map +1 -1
  33. package/dist/components/data-table/pagination.js +3 -3
  34. package/dist/components/data-table/pagination.js.map +1 -1
  35. package/dist/components/data-table/use-table-state.d.ts +1 -1
  36. package/dist/components/data-table/use-table-state.js.map +1 -1
  37. package/dist/components/data-views/data-row-list.js.map +1 -1
  38. package/dist/components/data-views/finder-panel-view.d.ts +1 -1
  39. package/dist/components/data-views/finder-panel-view.js.map +1 -1
  40. package/dist/components/data-views/hub-table.d.ts +9 -3
  41. package/dist/components/data-views/hub-table.js +262 -40
  42. package/dist/components/data-views/hub-table.js.map +1 -1
  43. package/dist/components/data-views/index.js +262 -40
  44. package/dist/components/data-views/index.js.map +1 -1
  45. package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
  46. package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
  47. package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
  48. package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
  49. package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
  50. package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
  51. package/dist/components/data-views/os-folder-glyph.js.map +1 -1
  52. package/dist/components/ui/avatar.d.ts +1 -1
  53. package/dist/components/ui/key-metrics.js.map +1 -1
  54. package/dist/index.js +136 -39
  55. package/dist/index.js.map +1 -1
  56. package/package.json +3 -2
  57. package/src/components/data-table/index.tsx +2 -2
  58. package/src/components/data-table/pagination.tsx +5 -1
  59. package/src/components/data-table/use-table-state.ts +1 -1
  60. package/src/components/data-views/data-row-list.tsx +1 -1
  61. package/src/components/data-views/finder-panel-view.tsx +2 -2
  62. package/src/components/data-views/hub-table.tsx +149 -41
  63. package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
  64. package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
  65. package/src/components/data-views/os-folder-glyph.tsx +1 -1
  66. package/src/components/ui/key-metrics.tsx +1 -1
  67. package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
  68. package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
  69. package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
  70. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
  71. package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
  72. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
  73. package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
  74. package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
  75. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
  76. package/template/AGENTS.md +43 -37
  77. package/template/app/(app)/columns/page.tsx +11 -0
  78. package/template/app/(app)/library/all/page.tsx +11 -0
  79. package/template/app/(app)/library/find/page.tsx +12 -0
  80. package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
  81. package/template/app/(app)/library/list/page.tsx +12 -0
  82. package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
  83. package/template/app/(app)/library/page.tsx +11 -0
  84. package/template/app/(app)/tokens-themes/page.tsx +11 -0
  85. package/template/components/ask-leo-composer.tsx +2 -2
  86. package/template/components/columns-client.tsx +158 -0
  87. package/template/components/columns-showcase.tsx +541 -0
  88. package/template/components/data-views/index.ts +32 -6
  89. package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
  90. package/template/components/data-views/table-cells.tsx +673 -0
  91. package/template/components/folder-details-shell.tsx +11 -11
  92. package/template/components/hub-tree-panel-view.tsx +24 -24
  93. package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
  94. package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
  95. package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
  96. package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
  97. package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
  98. package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
  99. package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
  100. package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
  101. package/template/components/library-panel-activator.tsx +8 -0
  102. package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
  103. package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
  104. package/template/components/list-hub-status-badge.tsx +2 -2
  105. package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
  106. package/template/components/sidebar/app-sidebar.tsx +61 -5
  107. package/template/components/sidebar/secondary-panel.tsx +109 -56
  108. package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
  109. package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
  110. package/template/components/table-properties/types.ts +1 -1
  111. package/template/components/templates/discovery-hub-template.tsx +1 -1
  112. package/template/components/templates/new-focus-template.tsx +2 -2
  113. package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
  114. package/template/components/tokens-secondary-nav.tsx +192 -0
  115. package/template/components/tokens-themes-client.tsx +476 -0
  116. package/template/components/tokens-themes-section.tsx +386 -0
  117. package/template/docs/HANDBOOK.md +187 -0
  118. package/template/docs/blueprints/README.md +1 -1
  119. package/template/docs/blueprints/board-card.md +1 -1
  120. package/template/docs/blueprints/data-table.md +2 -2
  121. package/template/docs/blueprints/list-page-template.md +3 -3
  122. package/template/docs/blueprints/page-header.md +4 -4
  123. package/template/docs/collaboration-access-pattern.md +7 -7
  124. package/template/docs/component-selection-guide.md +1 -1
  125. package/template/docs/data-views-pattern.md +18 -16
  126. package/template/docs/glossary.md +58 -0
  127. package/template/docs/kpi-flat-band-pattern.md +3 -3
  128. package/template/docs/kpi-trend-pattern.md +18 -3
  129. package/template/docs/large-dataset-strategy.md +155 -0
  130. package/template/docs/library-hub-header-pattern.md +25 -0
  131. package/template/docs/migrations/_template.md +1 -1
  132. package/template/docs/reference-implementations.md +151 -0
  133. package/template/docs/token-taxonomy.md +1 -1
  134. package/template/docs/voice-and-tone.md +262 -0
  135. package/template/eslint.config.mjs +9 -39
  136. package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
  137. package/template/lib/ask-leo-route-context.ts +6 -18
  138. package/template/lib/coach-mark-registry.ts +0 -16
  139. package/template/lib/command-menu-config.ts +5 -12
  140. package/template/lib/command-menu-search-data.ts +8 -39
  141. package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
  142. package/template/lib/library-dedicated-search.ts +19 -0
  143. package/template/lib/library-hub-search.ts +90 -0
  144. package/template/lib/library-nav.ts +477 -0
  145. package/template/lib/library-recent-searches.ts +22 -0
  146. package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
  147. package/template/lib/list-status-badges.ts +16 -104
  148. package/template/lib/mock/dashboard.ts +1 -1
  149. package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
  150. package/template/lib/mock/library-header-collaborators.ts +54 -0
  151. package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
  152. package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
  153. package/template/lib/mock/library.ts +249 -0
  154. package/template/lib/mock/navigation.tsx +32 -26
  155. package/template/lib/table-state-lifecycle.ts +1 -1
  156. package/template/next.config.mjs +7 -4
  157. package/template/package.json +0 -1
  158. package/tokens/hooks-index.json +2874 -0
  159. package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
  160. package/template/app/(app)/examples/page.tsx +0 -41
  161. package/template/app/(app)/question-bank/find/page.tsx +0 -12
  162. package/template/app/(app)/question-bank/library/page.tsx +0 -11
  163. package/template/app/(app)/question-bank/list/page.tsx +0 -12
  164. package/template/app/(app)/question-bank/page.tsx +0 -11
  165. package/template/components/compliance-board-view.tsx +0 -142
  166. package/template/components/compliance-client.tsx +0 -92
  167. package/template/components/compliance-page-header.tsx +0 -89
  168. package/template/components/compliance-table.tsx +0 -468
  169. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  170. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  171. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  172. package/template/components/new-placement-back-btn.tsx +0 -28
  173. package/template/components/new-placement-form.tsx +0 -942
  174. package/template/components/placement-board-card.tsx +0 -250
  175. package/template/components/placement-detail.tsx +0 -438
  176. package/template/components/placements-board-view.tsx +0 -397
  177. package/template/components/placements-client.tsx +0 -220
  178. package/template/components/placements-list-view.tsx +0 -124
  179. package/template/components/placements-page-header.tsx +0 -166
  180. package/template/components/placements-table-cells.test.tsx +0 -22
  181. package/template/components/placements-table-cells.tsx +0 -173
  182. package/template/components/placements-table-columns.tsx +0 -210
  183. package/template/components/placements-table.tsx +0 -934
  184. package/template/components/question-bank-panel-activator.tsx +0 -8
  185. package/template/components/rotations-empty-state.tsx +0 -50
  186. package/template/components/rotations-panel-activator.tsx +0 -8
  187. package/template/components/sites-board-view.tsx +0 -67
  188. package/template/components/sites-client.tsx +0 -154
  189. package/template/components/sites-table.tsx +0 -249
  190. package/template/components/team-board-view.tsx +0 -122
  191. package/template/components/team-client.tsx +0 -100
  192. package/template/components/team-page-header.tsx +0 -92
  193. package/template/components/team-table.tsx +0 -553
  194. package/template/docs/question-bank-hub-header-pattern.md +0 -25
  195. package/template/lib/compliance-supported-views.ts +0 -10
  196. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  197. package/template/lib/mock/compliance-kpi.ts +0 -61
  198. package/template/lib/mock/compliance.ts +0 -146
  199. package/template/lib/mock/placements-kpi.ts +0 -134
  200. package/template/lib/mock/placements.ts +0 -176
  201. package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
  202. package/template/lib/mock/question-bank.ts +0 -249
  203. package/template/lib/mock/sites-directory.ts +0 -16
  204. package/template/lib/mock/sites-kpi.ts +0 -25
  205. package/template/lib/mock/team-kpi.ts +0 -60
  206. package/template/lib/mock/team.ts +0 -118
  207. package/template/lib/placement-board-card-layout.ts +0 -79
  208. package/template/lib/question-bank-dedicated-search.ts +0 -19
  209. package/template/lib/question-bank-hub-search.ts +0 -90
  210. package/template/lib/question-bank-nav.ts +0 -477
  211. package/template/lib/question-bank-recent-searches.ts +0 -22
  212. package/template/lib/question-bank-supported-views.ts +0 -12
  213. package/template/lib/sites-supported-views.ts +0 -10
  214. package/template/lib/team-supported-views.ts +0 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,687 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ### `@exxatdesignux/ui` — publish hygiene + smarter scaffolder
8
+
9
+ Three real bugs in the `0.4.0` tarball made first-run consumer installs
10
+ fail. All three are now fixed.
11
+ - **`tokens/hooks-index.json` ships.** The `exports` map declared
12
+ `"./tokens/hooks-index.json"` but the `files` array did not include
13
+ `tokens`, so the file was silently omitted from the published tarball.
14
+ Any `import "@exxatdesignux/ui/tokens/hooks-index.json"` (used by
15
+ `@exxatdesignux/eslint-plugin`'s `no-deprecated-tokens` rule and by
16
+ workspaces that read the token registry) blew up with
17
+ `MODULE_NOT_FOUND`. The `files` array now includes `tokens`.
18
+ - **No more `workspace:*` leaking into the published template.** The
19
+ scaffolder template was sync'd from `apps/web/package.json`, which
20
+ uses `workspace:*` for both `@exxatdesignux/ui` (correct, gets
21
+ rewritten to a real semver pin) and a handful of monorepo-internal
22
+ dev tools (`@exxatdesignux/eslint-plugin`, …) which are not published
23
+ on npm. The previous sync rewrote only the design-system line, so
24
+ every consumer install (`npm` / `pnpm` / `yarn`) crashed on the
25
+ leaked entries. The sync script now walks every dep map
26
+ (`dependencies` / `devDependencies` / `peerDependencies` /
27
+ `optionalDependencies`), pins `@exxatdesignux/ui`, deletes any
28
+ remaining `workspace:` value, and fails the build if any leaks
29
+ survive — so the bug surfaces here, not on a consumer's terminal.
30
+ - **Consumer-safe `eslint.config.mjs` in the template.** The repo's
31
+ own `apps/web/eslint.config.mjs` imports
32
+ `@exxatdesignux/eslint-plugin` for DS guardrails (no hex literals,
33
+ no deprecated tokens, no SLDS leakage, no Sonner toasts). Because
34
+ that plugin isn't published, `pnpm lint` in a freshly-scaffolded app
35
+ crashed with `Cannot find package '@exxatdesignux/eslint-plugin'`.
36
+ The sync script now overwrites `template/eslint.config.mjs` with a
37
+ clean `eslint-config-next + globalIgnores + unused-vars` setup that
38
+ every consumer can run. The DS guardrails graduate to this template
39
+ whenever the plugin lands on npm.
40
+
41
+ ### Smarter `bin/init.mjs` scaffolder
42
+
43
+ The `create-exxat-app` binary now matches the conventions of
44
+ `create-next-app` and `create-vite`:
45
+ - **Positional target directory.** `create-exxat-app my-app` creates
46
+ `./my-app` and scaffolds into it. `create-exxat-app .` scaffolds
47
+ into the current directory. Missing directories are auto-created.
48
+ - **Package-manager auto-detection.** Reads `npm_config_user_agent`
49
+ and runs the matching install (`pnpm install` / `yarn install` /
50
+ `bun install` / `npm install`). The post-install hint prints the
51
+ matching `<pm> run dev` command.
52
+ - **`--force` flag.** Allow scaffolding into a non-empty directory
53
+ (existing files are not overwritten unless the template ships the
54
+ same path). The previous "directory must be empty" hard-error
55
+ blocked every "install the DS first, then scaffold" sequence.
56
+ - **`--no-install` flag.** Skip the install step (useful in CI or
57
+ sandboxed setups).
58
+ - **`--help` / `-h`.** Print usage.
59
+ - **Clearer blocked-directory error.** When the target is non-empty
60
+ without `--force`, the CLI lists which files are blocking and
61
+ suggests both options (`create-exxat-app my-new-app` or
62
+ `create-exxat-app . --force`) instead of failing silently.
63
+
64
+ ### Real README on the npm package page
65
+
66
+ `packages/ui/` had no `README.md` — the npm landing page at
67
+ [npmjs.com/package/@exxatdesignux/ui](https://www.npmjs.com/package/@exxatdesignux/ui)
68
+ showed only the bare package.json description. The new README leads
69
+ with the 60-second start (`pnpm create exxat-app my-app`), covers
70
+ "adding to an existing Next.js app", documents `exxat-ui sync-extras`
71
+ for Cursor skill installation, and links the full doc set.
72
+
73
+ ### `create-exxat-app@0.0.x` → new package
74
+
75
+ A new `create-exxat-app` discovery package now ships on npm so
76
+ `pnpm create exxat-app my-app` (and the npm / yarn / bun variants)
77
+ resolve natively without a `--package=` flag. The shim is intentionally
78
+ tiny — it depends on `@exxatdesignux/ui` (via `workspace:^`, pinned to
79
+ `^<version>` at publish) and delegates to
80
+ `@exxatdesignux/ui/bin/init.mjs`. All template, version-pinning, and
81
+ package-manager-detection logic stays in `@exxatdesignux/ui` so there
82
+ is exactly one source of truth.
83
+
84
+ ### Migration
85
+
86
+ Consumers on `0.4.0` should bump to `0.4.1`:
87
+
88
+ ```bash
89
+ pnpm up @exxatdesignux/ui@latest
90
+ ```
91
+
92
+ If you scaffolded with `0.4.0` and edited `eslint.config.mjs` /
93
+ `package.json` to work around the workspace leak, you can either keep
94
+ your manual edits or re-scaffold with `0.4.1` for a clean baseline.
95
+
96
+ ## 0.4.0
97
+
98
+ ### Minor Changes
99
+
100
+ - ### Build and distribution overhaul
101
+
102
+ The package now ships a real compiled `dist/` (ESM + `.d.ts` + source
103
+ maps) produced by `tsup`, replacing the previous shadcn-style "ship raw
104
+ `.tsx` source" approach. Non-Next consumers (Vite, Remix, plain Node
105
+ tooling) can now install and import without configuring a TS/JSX
106
+ transform for `node_modules`.
107
+ - `exports`, `main`, `module`, `types` point at `./dist/*`.
108
+ - `"use client"` directives are preserved on emit via a post-build pass
109
+ in `tsup.config.ts`.
110
+ - `prepack` builds before publish so `dist/` is always current.
111
+ - Per-component subpath imports
112
+ (`@exxatdesignux/ui/components/<name>`) continue to work — they now
113
+ resolve to `dist/components/ui/<name>.js`.
114
+
115
+ ### New primitives
116
+ - **`AlertDialog`** — blocking confirm dialog backed by Radix
117
+ `AlertDialog`. Ships `AlertDialogAction` + `AlertDialogCancel` that
118
+ wrap `Button` so consumers can pair destructive verbs without
119
+ re-styling. Read the new
120
+ `.cursor/rules/exxat-drawer-vs-dialog.mdc` for when to choose this
121
+ over the generic `Dialog`.
122
+ - **`Accordion`** — Radix-backed expandable section list with
123
+ chevron-flipping triggers. Use for FAQ-style or settings-grouping
124
+ surfaces where each panel toggles independently (`type="multiple"`)
125
+ or as a single-open group (`type="single"`).
126
+ - **`ContextMenu`** — right-click + long-press menu mirroring the
127
+ `DropdownMenu` surface (same item heights, tints, separators,
128
+ sub-menu chevron, checkbox / radio item indicators). Pair with a
129
+ visible `…` overflow `DropdownMenu` so keyboard-only users can reach
130
+ the same actions.
131
+ - **`HoverCard`** — richer hover surface for content too large or
132
+ interactive for a `Tooltip` (avatars, link previews, glossary
133
+ entries). Comes with the same animation tokens as `Popover`.
134
+ - **`ScrollArea`** — explicitly-bounded scroll container with
135
+ always-visible-on-hover scrollbars that respect platform conventions;
136
+ for sidebar trees, popover lists, long inspector panels.
137
+ - **`Slider`** — single- and dual-thumb numeric range backed by Radix
138
+ `Slider`. Thumbs render automatically based on `value` / `defaultValue`
139
+ cardinality.
140
+
141
+ Each primitive is exported from both the umbrella entry
142
+ (`@exxatdesignux/ui`) and a subpath
143
+ (`@exxatdesignux/ui/components/<name>`); matching `apps/web`
144
+ re-export shims live at `apps/web/components/ui/<name>.tsx`.
145
+
146
+ ### Promoted patterns (no longer leaf-only)
147
+
148
+ The package now ships product patterns alongside primitives — the same
149
+ shape as SLDS, Adobe Spectrum, Primer, MUI, Chakra.
150
+ - **`PageHeader`** — full hub header (h1, subtitle, optional
151
+ collaboration variant with face row + access info). Moved from
152
+ `apps/web/components/page-header.tsx`. `apps/web` keeps a 1-line
153
+ re-export shim at the original path; every existing
154
+ `import … from "@/components/page-header"` site continues to work.
155
+ - **`ListPageViewFrame`** — shared horizontal gutter + optional
156
+ centered max-width for non-`DataTable` list-hub view bodies (folder
157
+ icon grid, finder panel, OS-style explorer). Moved from
158
+ `apps/web/components/data-views/list-page-view-frame.tsx`.
159
+ - **`KeyMetrics`** (with `KeyMetricsContent`, `MetricItem`,
160
+ `MetricInsight`, `metricTrendTone`, `metricTrendAriaQualifier`) —
161
+ the full ~1.1 k LOC KPI strip moved from
162
+ `apps/web/components/key-metrics.tsx`. Hard-wired references to the
163
+ host app's `useAskLeo()` hook and `<AskLeoShortcutKbds />` component
164
+ are gone; both flow through a new injectable context.
165
+
166
+ Consumers wire their assistant by mounting `KeyMetricsProvider`
167
+ near the root and passing two optional values:
168
+ - **`defaultInsightAction: () => void`** — fires when the user
169
+ clicks the default "Ask Leo about these metrics" header CTA.
170
+ When omitted, the CTA is hidden entirely so the strip stays
171
+ useful in apps without an AI surface.
172
+ - **`shortcutHint: React.ReactNode`** — inline kbd chord (or any
173
+ node) rendered next to the action label inside the tooltip.
174
+ When omitted, the tooltip shows just the label.
175
+ - **`defaultActionLabel: string`** — visible + accessible name
176
+ for the header CTA. Defaults to "Ask Leo".
177
+
178
+ This is the same injection shape Adobe Spectrum (`Provider`-based
179
+ services) and Material UI (`Theme` context) use, so design-system
180
+ patterns can ship without taking a hard dependency on the host
181
+ app's AI / shortcut wiring.
182
+
183
+ `apps/web` keeps a 1-line `key-metrics.tsx` re-export shim plus a
184
+ new `key-metrics-ask-leo-bridge.tsx` adapter that's mounted in
185
+ `(app)/layout.tsx` just inside `AskLeoProvider`. All 20 existing
186
+ `<KeyMetrics />` consumers continue to work with no source
187
+ changes.
188
+
189
+ ### New shared utilities (`@exxatdesignux/ui/lib/*`)
190
+
191
+ Foundation modules promoted from `apps/web` so the upcoming
192
+ `DataTable`, `useTableState`, and `TablePropertiesDrawer` promotions
193
+ have a stable shared contract.
194
+ - **`row-height`** — `RowHeight` type + `ROW_HEIGHT_TILES` density
195
+ presets (compact / default / comfortable). Shared by the Properties
196
+ drawer tiles and `useTableState`.
197
+ - **`raf-throttle`** — coalesces high-frequency event handlers
198
+ (scroll, resize, ResizeObserver) into one call per animation frame
199
+ with a `.cancel()` for effect cleanup.
200
+ - **`editable-target`** — `isEditableTarget()` guard used by global
201
+ keyboard-shortcut handlers (Export `⌘⇧E`, rename `F2`, …) so
202
+ hotkeys don't steal keystrokes from text inputs / `contenteditable`
203
+ surfaces.
204
+ - **`conditional-rule-match`** — pure matchers
205
+ (`conditionalRuleMatchesRow`, `getConditionalRowBackground`,
206
+ `getConditionalCellBackground`) shared by `DataTable` cells,
207
+ `data-row-list` rows, and board card row tints so all three views
208
+ paint conditional formatting identically.
209
+ - **`table-properties-types`** — generic filter / sort /
210
+ conditional-rule type contracts (`FilterOperator`,
211
+ `FilterTextMask`, `FilterFieldDef`, `ActiveFilter`, `SortRule`,
212
+ `ColDef`, `ConditionalRule`) plus the design-system
213
+ `OPERATOR_LABELS` and `RULE_COLORS` palette. Product-specific seed
214
+ data (Placements `FILTER_FIELDS` + `COLUMNS` arrays) intentionally
215
+ stays in `apps/web/components/table-properties/types.ts`, which
216
+ now re-exports the types from the package.
217
+
218
+ `apps/web` keeps 1-line shims at the original `@/lib/*` paths
219
+ (`row-height.ts`, `raf-throttle.ts`, `editable-target.ts`,
220
+ `conditional-rule-match.ts`) so every existing import site continues
221
+ to work with no source change. The 12 files that import from
222
+ `@/components/table-properties/types` also keep working because the
223
+ file now re-exports the package types alongside the Placements
224
+ defaults.
225
+
226
+ ### Promoted DataTable (the biggest one)
227
+
228
+ The full generic `DataTable<TData>` family moved into the package as
229
+ `@exxatdesignux/ui/components/data-table` — ~3 k LOC across six files:
230
+ - **`./components/data-table`** (the umbrella) — `DataTable`,
231
+ `DataTableExtendedProps`, the column header context menu, resizing,
232
+ drag-to-reorder, per-column quick search, pinned columns,
233
+ group-by + collapsible groups, hidden columns, bulk-action bar,
234
+ conditional cell tints, sticky group headers, the column auto-fit
235
+ algorithm, and the inline filter popovers. Imports from
236
+ `next-themes` (light/dark column resizers) and `react-dom`
237
+ (`createPortal` for the bulk-action bar) are passed through; everything
238
+ else routes to DS primitives or DS lib utilities.
239
+ - **`./components/data-table/use-table-state`** — the 758-LOC hook
240
+ that owns filtering, sorting, search, density, pinning, hidden
241
+ columns, group-by, and column ordering for any consumer table.
242
+ No app-specific coupling; takes a generic `TData[]` + column defs.
243
+ - **`./components/data-table/pagination`** — `PaginationBar`,
244
+ `DataTablePaginated`, and `CountSyncer`. Wraps the umbrella
245
+ `DataTable` with a page-size selector + page navigation.
246
+ - **`./components/data-table/types`** — `ColumnDef`, `CellContext`,
247
+ `DataTableProps`, `PaginationConfig`, `SortDir`. Re-exports
248
+ `ConditionalRule` and `FilterTextMask` from the package
249
+ `table-properties-types` so column-def authors only need one import.
250
+ - **`./components/data-table/filter-date-calendar`** — single-date
251
+ YYYY-MM-DD filter calendar shared by the inline filter popover and
252
+ the Properties drawer date inputs.
253
+ - **`./components/data-table/filter-text-value-input`** — masked /
254
+ unmasked text input router for filter values (`phone`, `zip`,
255
+ `dateMDY`, free text). Falls back to the package `Input` when no
256
+ mask is requested.
257
+
258
+ The promotion required **no decoupling work** — Wave 7's `KeyMetrics`
259
+ context refactor and Wave 8's lib promotions had already moved every
260
+ shared dependency into the package. The DataTable suite has no
261
+ `useAskLeo`-style app-runtime coupling; every `@/` import the original
262
+ files used now resolves inside `packages/ui`.
263
+
264
+ Package `exports` map adds two new entries so the umbrella + subpath
265
+ imports both work:
266
+ - `./components/data-table` → `dist/components/data-table/index.js`
267
+ - `./components/data-table/*` →
268
+ `dist/components/data-table/*.{js,d.ts}`
269
+
270
+ These are declared **before** the generic `./components/*` rule so
271
+ the literal key and longer pattern win specificity resolution.
272
+
273
+ `apps/web/components/data-table/*.{ts,tsx}` (all 6 files) become 1-line
274
+ re-export shims so every existing
275
+ `import { DataTable } from "@/components/data-table"` site (~12 hubs
276
+ plus `useTableState` consumers) continues to work with no source
277
+ change.
278
+
279
+ The package now publishes `dist/components/data-table/{index,
280
+ pagination, use-table-state, filter-date-calendar,
281
+ filter-text-value-input, types}.{js,d.ts,js.map}` with the leading
282
+ `"use client"` directive preserved on every client module.
283
+
284
+ ### Linting
285
+ - Add `eslint-plugin-react-hooks` to `packages/ui` and register
286
+ `react-hooks/rules-of-hooks` (error) + `react-hooks/exhaustive-deps`
287
+ (warn). Required so the promoted `DataTable` and `useTableState`
288
+ files keep working — both carry targeted `eslint-disable-next-line
289
+ react-hooks/exhaustive-deps` comments that previously errored out
290
+ with "Definition for rule '…' was not found".
291
+
292
+ ### Promoted Table Properties drawer + data-list view registry
293
+
294
+ The full Table Properties drawer family moved into the package as
295
+ `@exxatdesignux/ui/components/table-properties` — ~1.6 k LOC across six
296
+ files plus the five data-list-view lib modules that the drawer (and
297
+ `ListPageTemplate` view tabs) read for label / icon / render-kind
298
+ metadata.
299
+ - **`./components/table-properties`** (the umbrella):
300
+ - `drawer.tsx` (~1.1 k LOC) — Properties sheet with View type tiles,
301
+ Filters / Sort / Group / Columns panels, Conditional formatting
302
+ rules, and the Display sub-sheet (gridlines, row height,
303
+ pagination, board swimlane field, line count). Sheet uses `z-[90]`
304
+ so portaled menus from inside it stack on top of `z-50` dropdowns.
305
+ - `drawer-button.tsx` (~280 LOC) — Reusable Properties button + drawer
306
+ combo. Accepts any `useTableState<T>` return value as `state`
307
+ (structural typing reads only display-agnostic fields, so it works
308
+ for any row shape). Mutators are read via `stateRef.current.X`
309
+ inside the Sheet portal, so handler identities stay stable
310
+ across re-renders.
311
+ - `filter-card.tsx` (~250 LOC) — One filter row inside the drawer
312
+ (operator dropdown, value picker, remove button). Used by both
313
+ filter rules and conditional-formatting rules.
314
+ - `sort-card.tsx` (~60 LOC) — One sort rule in the Sort panel (label,
315
+ direction toggle, drag handle, remove).
316
+ - `column-row.tsx` (~90 LOC) — One row in the Columns panel
317
+ (visibility toggle, drag handle).
318
+ - `draggable-list.ts` (~50 LOC) — `useDraggableList` pointer-driven
319
+ reorder hook shared by sort cards, column rows, and conditional
320
+ rule lists.
321
+
322
+ Both `drawer.tsx` and `drawer-button.tsx` get cleaner contracts as
323
+ part of the move: **`fieldDefinitions` and `filterFields` are now
324
+ required props**, replacing the silent fallback to the Placements
325
+ `COLUMNS` / `FILTER_FIELDS` constants. Every real consumer was
326
+ already passing both through `drawerToolbarProps` from
327
+ `HubTable` (built once via `columnsToFieldDefinitions` /
328
+ `columnsToFilterFields`); the fallback path was unreachable and
329
+ leaked Placements column shapes into the design-system package.
330
+ `DrawerSortCard` similarly drops its `COLUMNS.find(…).label`
331
+ fallback — the drawer always passes a `fieldLabel`
332
+ (`resolveColumnLabel(rule.fieldKey)`).
333
+
334
+ - **`./lib/data-list-view`** — `DataListViewType` vocabulary
335
+ (`table | list | board | dashboard | calendar | folder | panel |
336
+ tree-panel`), `DATA_LIST_VIEW_TILES`, `dataListViewLabel`,
337
+ `dataListViewIcon`, `dataListViewAddShortcut`.
338
+ - **`./lib/data-list-view-registry`** — Registry that maps each view
339
+ type to its render kind + chrome rules (`hubMetricsStrip`,
340
+ `dataListViewTilesForHub`, `dataListViewSelectionTilesForHub`,
341
+ `DATA_LIST_SURFACE_VIEW_TYPES`, `isDataListSurfaceViewType`,
342
+ `isDataListViewTypeSupported`).
343
+ - **`./lib/data-list-view-surface`** — `DataListViewRenderKind` enum
344
+ - `getDataListViewRenderKind`, `usesDataTableComponent`,
345
+ `usesDashboardSurface`, `usesToolbarWithFilteredRows` predicates so
346
+ `view === "dashboard"` is never mistaken for `view === "board"` in
347
+ switch chains.
348
+ - **`./lib/data-list-display-options`** — `DataListDisplayOptions`
349
+ (board line count, board swimlane key, show titles / column labels /
350
+ search) + `DEFAULT_DATA_LIST_DISPLAY_OPTIONS`.
351
+ - **`./lib/list-page-table-properties`** —
352
+ `createListPageEditViewHandler` + `OpenTablePropertiesHandle` so
353
+ `ListPageTemplate`'s "View → Edit" callback opens the drawer (after
354
+ coercing the tab to `table` when the active view does not host
355
+ Properties).
356
+
357
+ Package `exports` map adds:
358
+ - `./components/table-properties` →
359
+ `dist/components/table-properties/index.js` (umbrella)
360
+ - `./components/table-properties/*` →
361
+ `dist/components/table-properties/*.{js,d.ts}` (subpath)
362
+
363
+ `apps/web` keeps 1-line shims at all the original paths:
364
+ - 6 drawer-family files at `apps/web/components/table-properties/*`
365
+ (`drawer`, `drawer-button`, `filter-card`, `sort-card`, `column-row`,
366
+ `draggable-list`) — all re-export from
367
+ `@exxatdesignux/ui/components/table-properties/*`.
368
+ - 5 lib files at `apps/web/lib/*` (`data-list-view`,
369
+ `data-list-view-registry`, `data-list-view-surface`,
370
+ `data-list-display-options`, `list-page-table-properties`).
371
+
372
+ `apps/web/components/table-properties/types.ts` keeps the
373
+ Placements-specific `FILTER_FIELDS` + `COLUMNS` seed data (those are
374
+ **product data**, not design-system primitives) and continues to
375
+ re-export the generic types from `@exxatdesignux/ui/lib/table-properties-types`,
376
+ so every existing `import … from "@/components/table-properties"`
377
+ site keeps working unchanged.
378
+
379
+ The package now ships
380
+ `dist/components/table-properties/{drawer, drawer-button, filter-card,
381
+ sort-card, column-row, draggable-list, index}.{js,d.ts,js.map}` with
382
+ the leading `"use client"` directive preserved on the 5 client modules
383
+ (`draggable-list` is a pure hook so the inheriting consumer's `"use
384
+ client"` boundary covers it).
385
+
386
+ ### Promoted Data views — list-page binding layer
387
+
388
+ The list-page binding layer moved into the package as
389
+ `@exxatdesignux/ui/components/data-views` — ~1.15 k LOC across 9
390
+ files. `HubTable<TRow>` is now the canonical composition that ties
391
+ `DataTable` + `useTableState` + `TablePropertiesDrawerButton` + view
392
+ tabs into one generic component:
393
+ - **`./components/data-views/hub-table`** (~497 LOC) — `HubTable<TRow>`
394
+ - `columnsToFilterFields` + `columnsToFieldDefinitions` helpers +
395
+ `HubTableProps<TRow>` / `HubTableRendererArgs<TRow>` /
396
+ `HubDrawerToolbarProps`. Pass columns, rows, supported view types,
397
+ and a `renderers: { [renderKind]: ReactNode | () => ReactNode }` map.
398
+ The component owns `useTableState`, builds
399
+ `filterFields`/`fieldDefinitions` from your columns, mounts the
400
+ Properties button, and routes the active view tab through
401
+ `ListPageConnectedViewBody`.
402
+ - **`./components/data-views/list-page-connected-view-body`** —
403
+ `ListPageConnectedViewBody` + `ListPageViewNotConfigured` + the
404
+ `ListPageConnectedViewRenderers` type. Switches view bodies by
405
+ `DataListViewRenderKind` and shows a clear empty state when a
406
+ registered view is missing a renderer (never silently falls through
407
+ to dashboard).
408
+ - **`./components/data-views/data-row-list`** — virtualized list-view
409
+ body backed by `@tanstack/react-virtual` (new package dependency,
410
+ declared in `peerDependencies` shape; tree-shaken when only board
411
+ consumers import).
412
+ - **`./components/data-views/list-page-board-template`** — Reusable
413
+ kanban shell where columns are defined by predicates over each row.
414
+ - **`./components/data-views/list-page-board-card`** —
415
+ `ListPageBoardCard` shell with `stack` and `row` layouts, optional
416
+ `ListPageBoardCardAvatar`, `ListPageBoardCardTitleRow`,
417
+ `ListPageBoardCardBadgeRow`, `ListPageBoardCardBody`,
418
+ `ListPageBoardCardSecondary` slots. Matches placement-board card
419
+ visuals one-to-one.
420
+ - **`./components/data-views/board-card-primitives`** —
421
+ `BoardCardTwoLineBlock`, `BoardCardIconRow`,
422
+ `BoardNewCardPlaceholder` primitives shared between
423
+ `list-page-board-template` and consumer-built board cards. Reads
424
+ `BoardLineCount` from `data-list-display-options` to clamp text
425
+ lines.
426
+ - **`./components/data-views/list-page-tree-column-header`** /
427
+ **`list-page-split-details-placeholder`** /
428
+ **`list-page-split-hub-chrome`** — Pure-CSS shells used by the
429
+ finder / tree-panel / multi-column explorer views (centered card
430
+ - fixed viewport height; composes `ListPageViewFrame`).
431
+
432
+ Package `exports` map adds:
433
+ - `./components/data-views` →
434
+ `dist/components/data-views/index.js` (umbrella)
435
+ - `./components/data-views/*` →
436
+ `dist/components/data-views/*.{js,d.ts}` (subpath)
437
+
438
+ All 9 modules are exported from the main `@exxatdesignux/ui` barrel
439
+ and `apps/web/components/data-views/{hub-table, data-row-list,
440
+ list-page-connected-view-body, board-card-primitives,
441
+ list-page-board-card, list-page-board-template,
442
+ list-page-tree-column-header, list-page-split-details-placeholder,
443
+ list-page-split-hub-chrome}.tsx` are all 1-line re-export shims, so
444
+ every existing consumer (Placements, Team, Compliance, Question Bank,
445
+ plus every `HubTableRendererArgs<TRow>`-typed table renderer) keeps
446
+ working with no source change.
447
+
448
+ ### Tooling
449
+ - Add `@tanstack/react-virtual` to `packages/ui` `dependencies` (the
450
+ virtualized `DataRowList` body needs it). External in `tsup` so the
451
+ consumer bundle dedupes against any pre-existing `apps/web` copy.
452
+ - Add `packages/ui/src/globals.d.ts` declaring a minimal
453
+ `process.env.NODE_ENV` ambient so `HubTable`'s dev-time
454
+ missing-renderer `console.warn` typechecks without taking on a
455
+ `@types/node` dependency. Both bundlers and Next.js's RSC compiler
456
+ statically replace `process.env.NODE_ENV` at build time, so the
457
+ declaration only affects type-checking.
458
+ - Clean up `useImperativeHandle` deps in `HubTable` to extract the
459
+ stable `setSheetOpen` setter from `useTableState` before the hook
460
+ call, removing the misplaced
461
+ `eslint-disable-next-line react-hooks/exhaustive-deps` comment.
462
+
463
+ ### Promoted ExportDrawer + ListPageTemplate + small templates
464
+
465
+ The page-scale list-hub composition moved into the package as
466
+ `@exxatdesignux/ui/components/templates` so non-Next consumers (Vite,
467
+ Remix, plain CRA) can mount a complete hub without copying app source.
468
+ - **`./components/export-drawer`** — `ExportDrawer` (format + date range
469
+ - columns + active-filter toggle), single-file component composed from
470
+ in-package primitives only. App-specific `devLog` came along as
471
+ `./lib/dev-log` (`process.env.NODE_ENV` gated — bundlers strip the
472
+ call site in production).
473
+ - **`./components/templates/list-page`** — `ListPageTemplate` +
474
+ `ViewTab` / `ViewType` / `FilterOption` types + the canonical
475
+ `VIEW_TYPES` array derived from `DATA_LIST_VIEW_TILES`. The template
476
+ composes `PageHeader` slots (`header`, `metrics`,
477
+ `beforeSiteHeader`), the view-segmented control, and the now-promoted
478
+ `ExportDrawer` directly.
479
+ - **`./components/templates/nested-secondary-panel-shell`** — Single
480
+ responsive shell pairing a primary sidebar with a nested secondary
481
+ panel (used by Question bank).
482
+ - **`./components/templates/dedicated-search-landing-template`** —
483
+ Empty-`?q=` landing surface (`DotPattern` + lens + suggestions
484
+ slot) composed on `ListPageViewFrame`.
485
+ - **`./components/templates/dedicated-search-results-template`** —
486
+ `DedicatedSearchResultsHeaderChrome` + the
487
+ `DEDICATED_SEARCH_RESULTS_OUTER_CONTENT_CLASSNAME` shared classname
488
+ for the populated `?q=` branch.
489
+
490
+ App-side compositions that wrap product-specific shells
491
+ (`PrimaryPageTemplate`, `SecondaryPanelHubTemplate`,
492
+ `DiscoveryHubTemplate`, `NewFocusTemplate`) stay in
493
+ `apps/web/components/templates/` because they pull in `SiteHeader`,
494
+ `useAskLeo`, or per-route mock data — those will graduate when their
495
+ respective shells are decoupled.
496
+
497
+ Package `exports` map adds:
498
+ - `./components/templates` → `dist/components/templates/index.js`
499
+ - `./components/templates/*` →
500
+ `dist/components/templates/*.{js,d.ts}`
501
+
502
+ `apps/web` ships 1-line re-export shims at
503
+ `apps/web/components/export-drawer.tsx`,
504
+ `apps/web/lib/dev-log.ts`, and
505
+ `apps/web/components/templates/{list-page,
506
+ nested-secondary-panel-shell, dedicated-search-landing-template,
507
+ dedicated-search-results-template}.tsx` so every existing consumer
508
+ keeps working unchanged. All 15 Next.js routes still compile.
509
+
510
+ ### Promoted Data view bodies — finder / folder grid / outline tree / tree-panel shell + folder glyph
511
+
512
+ Six view-body modules moved into
513
+ `@exxatdesignux/ui/components/data-views/`, finishing the data-views
514
+ surface graduation that started with `HubTable`:
515
+ - **`./components/data-views/list-page-split-hub-tokens`** — Shared
516
+ layout classnames (`LIST_PAGE_SPLIT_MILLER_COLUMN_PANEL_CLASS`,
517
+ `LIST_PAGE_SPLIT_MILLER_DETAIL_PANEL_CLASS`,
518
+ `LIST_PAGE_SPLIT_RESIZABLE_HANDLE_CLASS`) so finder / tree-panel /
519
+ multi-column hubs stay visually aligned.
520
+ - **`./components/data-views/outline-tree-menu`** — VS Code–style
521
+ outline chrome (`OutlineTreeMenu`, `OutlineTreeMenuItem`,
522
+ `OutlineTreeLeafButton`, `OutlineTreeSub`, `OutlineTreeSubItem`,
523
+ `OutlineTreeCollapsibleContentRail`) with the guide-spacer + sub-row
524
+ shift classnames consumers compose for inset rails.
525
+ - **`./components/data-views/folder-grid-view`** — Generic icon-grid
526
+ layout with empty state, `aria-label`-named list, `renderTile`
527
+ render-prop, and optional centered max-width (icon-grid frame).
528
+ - **`./components/data-views/finder-panel-view`** — Miller-style
529
+ 3-column split for list hubs (`FinderPanelView`) reusing the split
530
+ hub tokens + `ListPageTreeColumnHeader` +
531
+ `ListPageSplitDetailsPlaceholder`.
532
+ - **`./components/data-views/list-page-tree-panel-shell`** — Generic
533
+ two-pane layout (tree column + details column) with persisted
534
+ `react-resizable-panels` group id and shared
535
+ `ListPageSplitHubChrome` chrome.
536
+ - **`./components/data-views/os-folder-glyph`** — Windows-11-style
537
+ folder art (Icons8) + FA glyph overlay, with a generic
538
+ `FolderGlyphColorKey` palette type. Product `QuestionBankFolderColorKey`
539
+ stays in app mock data (same string union — structurally compatible
540
+ with `FolderGlyphColorKey`, no source changes required at call sites).
541
+
542
+ Package `exports` map continues to route
543
+ `./components/data-views/*` to the new modules. The package barrel
544
+ re-exports them all. App-side 1-line shims continue to provide the
545
+ `@/components/data-views/<file>` import paths.
546
+
547
+ `apps/web/components/data-views/question-bank-folder-tree-branch.tsx`
548
+ stays in the app — it pulls product mock data
549
+ (`QuestionBankFolder` / `QUESTION_BANK_FOLDER_ICON_COLORS`) and is the
550
+ right boundary between "design-system primitive" and "product
551
+ composition".
552
+
553
+ ### Tailwind L0 token namespace — verified
554
+
555
+ The `--exxat-color-*` / `--exxat-radius-*` / `--exxat-spacing-*` L0
556
+ canonical namespace (migration `0002-exxat-token-namespace.md`) is live
557
+ in the package's `src/globals.css` with Tailwind bridges (`bg-surface-1`,
558
+ `text-ink-1`, `rounded-2`, …) wired through `@theme inline`. The hooks
559
+ index (`packages/ui/tokens/hooks-index.json`) is regenerated to 197
560
+ tokens and passes `tokens:check`. `apps/web/app/globals.css` imports
561
+ the package CSS, so consumers inherit every L0 alias automatically. No
562
+ existing token was renamed; opportunistic per-file migration to L0
563
+ forms is non-breaking and continues as devs touch the code.
564
+
565
+ ### Documentation — three new blueprints
566
+
567
+ Added SLDS-style spec docs for the most-touched compositions, completing
568
+ the patterns the blueprints README itself flagged as "future":
569
+ - **`apps/web/docs/blueprints/list-page-template.md`** — view tabs +
570
+ metrics slot + properties drawer wiring, with WCAG mapping and
571
+ Do / Don't table.
572
+ - **`apps/web/docs/blueprints/board-card.md`** — `ListPageBoardCard`
573
+ shell + status badge + body primitives, including the list-row
574
+ counterpart (`layout="row"`).
575
+ - **`apps/web/docs/blueprints/key-metrics.md`** — flat vs card
576
+ variant, KPI count cap, trend polarity, and the service-injection
577
+ bridge for app-specific actions like Ask Leo.
578
+
579
+ The blueprints README links each new doc to its narrative + cursor
580
+ rules + React component. (Storybook / Ladle catalog is intentionally
581
+ not adopted in this pass: the Next.js demo routes plus blueprints
582
+ already cover the documentation surface, and adopting Storybook would
583
+ add infra without immediate signal for this DS.)
584
+
585
+ ### Hygiene
586
+ - Add `license: "UNLICENSED"` + `author` fields to `package.json`.
587
+ - Add `--exxat-color-wordmark-ink-light` / `-dark` tokens (used by the
588
+ Exxat product logo SVG + HTML wordmark prefix; replaces five `[#273441](https://github.com/ExxatDesign/Exxat-DS-Workspace/issues/273441)`
589
+ / `#A8B2BA` literals in `apps/web/components/exxat-product-logo.tsx`
590
+ - `product-wordmark.tsx`).
591
+ - ESLint config landed alongside source. Runs in PR CI with the
592
+ workspace `@exxatdesignux/eslint-plugin` rules (no hex literals, no
593
+ deprecated tokens, no SLDS / Lightning leakage, no Sonner toasts).
594
+ - `npm publish` runs with Sigstore `--provenance` from the
595
+ `.github/workflows/publish-ui.yml` workflow + creates an automated
596
+ GitHub Release.
597
+ - New PR-time CI workflow gates every PR on install → lint → typecheck
598
+ → tokens drift check → vitest → build, plus the Changesets release
599
+ flow on `main`.
600
+ - Scope the `brace-expansion` security override (GHSA-v6h2-p8h4-qcjw)
601
+ to per-major lines so `minimatch@3.1.5` keeps its compatible 1.x
602
+ branch and ESLint config loading no longer crashes with
603
+ `expand is not a function`.
604
+ - Prune unused mutator destructures in
605
+ `apps/web/components/table-properties/drawer-button.tsx` (left behind
606
+ by the earlier `react-hooks/refs` refactor). Setters are now read
607
+ exclusively via `stateRef.current.X` so handler identities stay
608
+ stable while the Sheet is portaled.
609
+
610
+ - ### Demo / template hub renamed from "Question bank" → "Library"
611
+
612
+ The reference hub that ships in `packages/ui/template/**` and the
613
+ consumer-side mirrors in `packages/ui/consumer-extras/**` were renamed
614
+ from a domain-specific "Question bank" to a generic, placeholder-labeled
615
+ **Library** hub. This affects three surfaces consumers see:
616
+ 1. **Template route + components** (used by `exxat-ui scaffold-app`):
617
+ - `app/(app)/question-bank/**` → `app/(app)/library/**`. The
618
+ `library/library` sub-route was simultaneously renamed to
619
+ `library/all` so the canonical "list everything" path reads
620
+ `/library/all`.
621
+ - `components/question-bank-*.tsx` (13 files) →
622
+ `components/library-*.tsx`. `components/new-question-composer.tsx`
623
+ → `components/new-library-item-form.tsx`.
624
+ - `components/data-views/question-bank-folder-tree-branch.tsx` →
625
+ `components/data-views/library-folder-tree-branch.tsx`.
626
+ - `lib/mock/question-bank{,-folders,-inspector,-kpi,-header-collaborators}.ts`
627
+ → `lib/mock/library-*.ts`.
628
+ - `lib/question-bank-{nav,supported-views,authoring,recent-searches,hub-search,dedicated-search}.ts`
629
+ → `lib/library-*.ts`.
630
+ 2. **TypeScript surface (template only — package itself ships
631
+ generic shells):** `QuestionBankItem` → `LibraryItem`,
632
+ `QuestionBankFolder` → `LibraryFolder`, `QuestionBankNavState` →
633
+ `LibraryNavState`, `QuestionBankTable` → `LibraryTable`, etc. The
634
+ nested secondary-panel id flipped from `"question-bank"` to
635
+ `"library"`; consumers wiring `useAutoPanel` or the `secondaryPanel`
636
+ prop on a nav row must update the literal.
637
+ 3. **Consumer docs + agent rules** (`packages/ui/consumer-extras/`):
638
+ `cursor-rules/exxat-question-bank-hub-header.mdc` →
639
+ `cursor-rules/exxat-library-hub-header.mdc`. The skill and
640
+ handbook entries that cited "Question bank" as the reference
641
+ implementation now cite the **Library** hub instead.
642
+
643
+ Mock content was also abstracted from clinical/medical strings (Bloom
644
+ taxonomy, NBME, anatomy, clinical decks) to neutral placeholders:
645
+ `Item 01..12`, `Owner A..E`, `Category 1..4`, `Type 1..3`,
646
+ `Low / Normal / High`, `Tier 1..6`, `Folder 1..6`, `LIB-2026-###` ids.
647
+ This makes the template usable as a starter for any domain — clinical,
648
+ education, retail, ops, etc. — without rewriting demo data first.
649
+
650
+ ### Migration for consumer apps
651
+
652
+ Run `pnpm exxat-ui sync-extras` (or whatever wrapper your repo uses)
653
+ to refresh `cursor-rules/`, `cursor-skills/`, `patterns/`, and
654
+ `handbook/` from the new mirror. The two breaking edges to check
655
+ manually inside your own app code:
656
+ - Any nav row that pinned `secondaryPanel: "question-bank"` →
657
+ `secondaryPanel: "library"`.
658
+ - Any direct import from
659
+ `@/components/question-bank-*` / `@/lib/question-bank-*` /
660
+ `@/lib/mock/question-bank-*` (likely only if you forked the demo
661
+ hub) → rename to the matching `library-*` path.
662
+
663
+ The `DataTable`, `useTableState`, `HubTable`, `ListPageTemplate`,
664
+ `TablePropertiesDrawer`, `KeyMetrics`, and `PageHeader` APIs are
665
+ unchanged — only the demo hub naming flipped.
666
+
667
+ ### Pagination chrome regression fixed
668
+
669
+ Independent of the rename, `HubTable.defaultTableRenderer` and the
670
+ `list-with-toolbar` default block now embed `<PaginationBar>` inside
671
+ the table card with `sticky bottom-0` instead of floating it below the
672
+ border. The new layout passes `hasFooter` through to `<DataTable>` so
673
+ the table's top corners stay `rounded-t-lg` and the bottom corners go
674
+ square, then anchors the pagination slab to the page scroll container
675
+ (`PrimaryPageTemplate.bodyClassName`). The shipped `PaginationBar`
676
+ component itself is unchanged.
677
+
678
+ ### Docs
679
+ - `apps/web/docs/large-dataset-strategy.md` — new doc covering today's
680
+ client-mode behavior, when to enable pagination, the server-mode
681
+ upgrade path via `paginationOverride`, and the row-virtualization
682
+ follow-up. Cross-linked from `apps/web/AGENTS.md` and
683
+ `apps/web/docs/data-views-pattern.md`.
684
+
3
685
  All notable changes to `@exxatdesignux/ui` are documented here. The file ships in the npm tarball at `node_modules/@exxatdesignux/ui/CHANGELOG.md`.
4
686
 
5
687
  ## For AI assistants (upgrade handoff)
@@ -17,12 +699,25 @@ After the user bumps `@exxatdesignux/ui`, do this in order:
17
699
 
18
700
  ## [Unreleased]
19
701
 
702
+ ## [0.3.1] – 2026-05-21
703
+
704
+ ### Fixed
705
+
706
+ - **Customer demo `template/` was stale.** Re-ran `sync-template-from-web` and confirmed the npm `template/` payload now matches what the monorepo runs (`/columns`, `/tokens-themes`, `/question-bank`, `/dashboard`, settings) byte-for-byte. Previously the published `template/` still carried deleted entity-domain files (`placements-*`, `sites-*`, `team-*`, `compliance-*`, `rotation-*`) — they're gone now.
707
+
708
+ ### Changed
709
+
710
+ - **`prepack` now auto-syncs template + consumer-extras before `tsup`.** New chain: `sync-template-from-web && vendor-consumer-extras && tsup`. Every `npm publish` (and every `npm pack`) guarantees the shipped `template/` mirrors `apps/web` and the AI bundle under `consumer-extras/` mirrors the latest skills + patterns. No more "I bumped the version but the template is stale" mode — drift is now impossible at publish time.
711
+ - **Handbook + skills refreshed for the post-cleanup canonical refs.** `apps/web/AGENTS.md`, `apps/web/docs/HANDBOOK.md`, `apps/web/docs/data-views-pattern.md`, `apps/web/docs/glossary.md`, `apps/web/docs/reference-implementations.md`, `apps/web/docs/blueprints/{data-table,page-header}.md`, `apps/web/docs/kpi-flat-band-pattern.md`, and `apps/web/docs/component-selection-guide.md` (and the matching `.cursor/skills/exxat-ds-skill/SKILL.md`) all stop citing the deleted Placements / Sites / Team / Compliance components. New canonical references: `columns-showcase.tsx` (smallest single-view hub), `tokens-themes-client.tsx` (smallest secondary-panel + URL-driven scope hub with built-in pagination chrome), `question-bank-table.tsx` + `question-bank-hub-client.tsx` (full multi-view hub: table, board, dashboard).
712
+
20
713
  ## [0.3.0] – 2026-05-21
21
714
 
22
715
  ### Added
23
716
 
717
+ - **`exxat-token-economy` skill** (`.cursor/skills/exxat-token-economy/SKILL.md` + Claude mirror) — the new "read this first" entry for any AI working with the design system. Targets **~50% fewer input tokens per design turn** by giving the assistant: a task → minimum-file-set table (so it doesn't reflexively open `AGENTS.md`), a five-question pre-flight that catches the top rule violations before generation, canonical primitive aliases (no `grep` needed), tiny copy-verbatim scaffolds for hub client / column def / KPI item, a deny-list of expensive files, an ask-vs-assume heuristic, and an output-discipline section that keeps the assistant's own response lean. `HANDBOOK.md` now opens with a callout pointing to it.
718
+ - **Importable cell primitives** — every SaaS-grid cell pattern demoed in `columns-showcase.tsx` (progress, currency, numeric, rating, signal bars, boolean toggle, attachment count, external link, relative time, face rail with `+N`, type pill, tag list with `+N`, generic `RowActionsCell<TRow>`) is now a named export from `@/components/data-views` (`apps/web/components/data-views/table-cells.tsx`). The showcase becomes a thin orchestrator that imports these — proving they're production-ready, not demo-only. The token-economy skill's §3 ("primitive aliases") and §4 ("`ColumnDef` scaffolds") both name the cells so the AI imports them on the first try instead of re-deriving `Intl.NumberFormat`, star loops, or `<a target="_blank">` inline. `.cursor/rules/exxat-data-tables.mdc` adds a MUST-USE clause; `reference-implementations.md` gains a "Cell primitives (importable)" table; `glossary.md` adds a "Cell primitive" entry.
24
719
  - **`exxat-ui sync-extras` now installs the full AI bundle in one command.** Previously the CLI shipped only Cursor skills + pattern docs; consumers had to `degit` Cursor rules and handbook tier docs separately. The CLI now writes, in one pass:
25
- 1. **Skills to both clients** — `.cursor/skills/exxat-*/` **and** `.claude/skills/exxat-*/` (same `SKILL.md` shape, two readers, one source of truth — no more `ln -s` step).
720
+ 1. **Skills to both clients** — `.cursor/skills/exxat-*/` **and** `.claude/skills/exxat-*/` (16 folders including the new **`exxat-token-economy`**; same `SKILL.md` shape, two readers, one source of truth — no more `ln -s` step).
26
721
  2. **Cursor rules** — `.cursor/rules/exxat-*.mdc` (29 binding `MUST` / `MUST NOT` files: data-tables, accessibility, kbd shortcuts, KPI trends/flat-band/max-four, board cards, list-page-view-shells, centralized-list-dataset, reuse-before-custom, no-toast, drawer-vs-dialog, page-vs-drawer, no-slds-leakage, token-discipline, mono-ids, fontawesome-icons, dashboard-view-charts, …).
27
722
  3. **Handbook tier** — `docs/exxat-ds/handbook/{HANDBOOK,glossary,voice-and-tone,reference-implementations}.md`. These are stage-rewritten copies of `apps/web/docs/*.md` — links to shipped patterns use `../`, links to unshipped neighbours (blueprints, token-taxonomy, root `AGENTS.md`, `lib/*`) are rewritten to absolute GitHub URLs at bundle time so every link in the consumer's repo resolves.
28
723
  4. **Patterns + checklist** — unchanged from prior releases (`docs/exxat-ds/*-pattern.md`, `consumer-upgrade-checklist.md`).
@@ -76,11 +771,11 @@ After the user bumps `@exxatdesignux/ui`, do this in order:
76
771
  ### Fixed
77
772
 
78
773
  - **Dark mode surface elevation ladder — popover / card split, secondary panel nestled** (`globals.css`):
79
- 1. **`--popover` lifted to L=0.275** (was 0.225). Sharing the card's subtle 0.225 value made floating dropdowns blend straight into the canvas (only +0.025 above L=0.20) — the dropdown chrome lost its boundary. Popover now sits +0.075 above canvas with a slightly elevated chroma (built-ins 0.022 / custom `max(0.018, c·0.16)`), giving dropdowns and menus the visible lift they need over content. `--card` stays at 0.225 — it's an *inline* surface, not a floating one, and the earlier "subtle wash" reading was correct for it.
80
- 2. **`--secondary-panel-bg` rewired** from `var(--brand-tint)` (which resolved to L=0.30) down to **L=0.22**. The old value made the secondary nav panel *brighter* than the surrounding sidebar (L=0.245), so the Library / question-bank rail popped out as a bright tile instead of nesting in. The new value wedges it between sidebar (0.245) and canvas (0.20). Per-theme overrides carry the brand hue + chroma (Exxat One 0.025, Prism 0.030, Assessment 0.025, custom `max(0.020, c·0.20)`).
774
+ 1. **`--popover` lifted to L=0.275** (was 0.225). Sharing the card's subtle 0.225 value made floating dropdowns blend straight into the canvas (only +0.025 above L=0.20) — the dropdown chrome lost its boundary. Popover now sits +0.075 above canvas with a slightly elevated chroma (built-ins 0.022 / custom `max(0.018, c·0.16)`), giving dropdowns and menus the visible lift they need over content. `--card` stays at 0.225 — it's an _inline_ surface, not a floating one, and the earlier "subtle wash" reading was correct for it.
775
+ 2. **`--secondary-panel-bg` rewired** from `var(--brand-tint)` (which resolved to L=0.30) down to **L=0.22**. The old value made the secondary nav panel _brighter_ than the surrounding sidebar (L=0.245), so the Library / question-bank rail popped out as a bright tile instead of nesting in. The new value wedges it between sidebar (0.245) and canvas (0.20). Per-theme overrides carry the brand hue + chroma (Exxat One 0.025, Prism 0.030, Assessment 0.025, custom `max(0.020, c·0.20)`).
81
776
  3. **Dark surface ladder is now** `--background` 0.20 → `--secondary-panel-bg` 0.22 → `--card` 0.225 → `--sidebar` / `--input-background` 0.245 → `--popover` 0.275 — clear, monotonic stepping with floating elements lifting further than inline ones.
82
- - **Dark mode card / popover / input-background — softened to a subtle brand wash, not a tinted panel** (`globals.css`): The previous pass at `L=0.255 C=0.030` made cards read as a *separate tinted surface* floating above canvas. Pulled lightness down to `L=0.225` (only 0.025 above `--background` at L=0.20) and chroma down to `C=0.017` (built-ins) / `max(0.015, calc(c*0.13))` (custom). Cards now read as the same surface family as canvas with a small elevation step and a whisper of brand hue — the saturated brand expression remains on `--brand-tint` and `--sidebar` where it belongs. Input background follows the same shift (L 0.245 / C 0.010). Foreground contrast unchanged (≥ 12:1).
83
- - **Hydration warnings from the Cursor IDE browser preview** (`app/(app)/layout.tsx`, `components/templates/nested-secondary-panel-shell.tsx`, `components/ask-leo-sidebar.tsx`): The in-IDE browser MCP injects a `data-cursor-ref` attribute on top-level layout chrome *before* React hydrates so it can target those nodes for click automation. React then warned about an attribute it didn't render. Added `suppressHydrationWarning` on just the three SSR-rendered shell roots that the MCP tags. The flag is scoped to *each element's own attributes only* — children still hydrate normally and any real mismatch inside the panel still surfaces. Has zero effect outside the Cursor IDE preview.
777
+ - **Dark mode card / popover / input-background — softened to a subtle brand wash, not a tinted panel** (`globals.css`): The previous pass at `L=0.255 C=0.030` made cards read as a _separate tinted surface_ floating above canvas. Pulled lightness down to `L=0.225` (only 0.025 above `--background` at L=0.20) and chroma down to `C=0.017` (built-ins) / `max(0.015, calc(c*0.13))` (custom). Cards now read as the same surface family as canvas with a small elevation step and a whisper of brand hue — the saturated brand expression remains on `--brand-tint` and `--sidebar` where it belongs. Input background follows the same shift (L 0.245 / C 0.010). Foreground contrast unchanged (≥ 12:1).
778
+ - **Hydration warnings from the Cursor IDE browser preview** (`app/(app)/layout.tsx`, `components/templates/nested-secondary-panel-shell.tsx`, `components/ask-leo-sidebar.tsx`): The in-IDE browser MCP injects a `data-cursor-ref` attribute on top-level layout chrome _before_ React hydrates so it can target those nodes for click automation. React then warned about an attribute it didn't render. Added `suppressHydrationWarning` on just the three SSR-rendered shell roots that the MCP tags. The flag is scoped to _each element's own attributes only_ — children still hydrate normally and any real mismatch inside the panel still surfaces. Has zero effect outside the Cursor IDE preview.
84
779
  - **Dark mode brand surfaces — every product chrome surface now reads as the brand per product** (`globals.css`):
85
780
  1. **`--brand-tint`** was never overridden in `.dark` or any `.theme-*.dark` block, so it cascaded down from the light-mode pale value (e.g. `oklch(0.97 0.02 343)` for Prism). Because the dark `--secondary-panel-bg` formula derived it as `color-mix(--card 32%, --brand-tint 68%)`, the question-bank Library secondary panel rendered as a pale rose / lavender pastel on dark canvas. Fixed by adding `--brand-tint` / `--brand-tint-light` / `--brand-tint-subtle` overrides at dark-mode lightness inside `.dark`, `.theme-one.dark`, `.theme-prism.dark`, `.theme-assessment.dark`, and `.theme-custom.dark`.
86
781
  2. **`--secondary-panel-bg` formula in `.dark`** rewritten from `color-mix(--card 32%, --brand-tint 68%)` to plain `var(--brand-tint)`. The old mix folded in `--card`'s neutral hue 270 — never overridden per theme in dark mode — so every product's secondary panel was pulled toward the same purple-neutral. Now the panel renders as a fully brand-tinted dark surface that varies clearly per product: Exxat One `#2c2a46` (navy-lavender), Prism `#402235` (wine), Assessment `#143525` (forest green).
@@ -113,7 +808,7 @@ After the user bumps `@exxatdesignux/ui`, do this in order:
113
808
 
114
809
  ### Changed
115
810
 
116
- - **Tokens**: `globals.css` refinements and starter **`template/`** parity with the web app (layout, Question bank hub chrome, navigation). *(Note: `theme.css` was a duplicate of `globals.css` and has been removed — see [`0003-globals-css-canonical.md`](../../apps/web/docs/migrations/0003-globals-css-canonical.md).)*
811
+ - **Tokens**: `globals.css` refinements and starter **`template/`** parity with the web app (layout, Question bank hub chrome, navigation). _(Note: `theme.css` was a duplicate of `globals.css` and has been removed — see [`0003-globals-css-canonical.md`](../../apps/web/docs/migrations/0003-globals-css-canonical.md).)_
117
812
  - **Consumer extras**: Cursor skills + pattern docs refreshed for collaboration / Question bank hub header.
118
813
 
119
814
  ### Chore (monorepo)