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