@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/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @exxatdesignux/ui
2
+
3
+ The Exxat Design System — a Next.js + React 19 + Tailwind v4 component
4
+ library plus the full hub-page stack (`ListPageTemplate`, `DataTable`,
5
+ `HubTable`, `TablePropertiesDrawer`, `KeyMetrics`, `PageHeader`, board
6
+ cards, charts, …) wrapped in one publishable package.
7
+
8
+ ## 60-second start
9
+
10
+ Spin up a brand-new app pre-wired with the DS:
11
+
12
+ ```bash
13
+ pnpm create exxat-app my-app
14
+ cd my-app
15
+ pnpm dev
16
+ ```
17
+
18
+ Works with every modern package manager — the scaffolder auto-detects
19
+ the runner and uses the matching install:
20
+
21
+ ```bash
22
+ npm create exxat-app my-app
23
+ yarn create exxat-app my-app
24
+ bun create exxat-app my-app
25
+ ```
26
+
27
+ The scaffolder ships a complete Next.js 16 starter: the reference
28
+ **Library** hub at `/library/all`, the `/columns` cell-catalog showcase,
29
+ typed mock data, the full Cursor skill + pattern doc set under
30
+ `cursor-rules/`, `cursor-skills/`, `patterns/`, and `handbook/`.
31
+
32
+ ## Adding to an existing Next.js app
33
+
34
+ If you already have a Next 15/16 app and just want the components:
35
+
36
+ ```bash
37
+ pnpm add @exxatdesignux/ui
38
+ ```
39
+
40
+ Then in your global CSS (e.g. `app/globals.css`):
41
+
42
+ ```css
43
+ @import "@exxatdesignux/ui/globals.css";
44
+ ```
45
+
46
+ And import primitives or full patterns from the umbrella or a subpath:
47
+
48
+ ```tsx
49
+ import { DataTable, HubTable, ListPageTemplate, KeyMetrics } from "@exxatdesignux/ui"
50
+ import { Button } from "@exxatdesignux/ui/components/button"
51
+ import { useTableState } from "@exxatdesignux/ui/components/data-table/use-table-state"
52
+ ```
53
+
54
+ Mount `KeyMetricsProvider` near the root if you want the "Ask Leo about
55
+ these metrics" CTA wired to your AI surface (otherwise the strip just
56
+ hides that button automatically).
57
+
58
+ ## Refresh Cursor skills + pattern docs in any consumer
59
+
60
+ The package ships a parallel set of binding rules, skills, and pattern
61
+ docs so AI agents in your own repo can read the same guidance the source
62
+ repo uses. To install or refresh them in your repo:
63
+
64
+ ```bash
65
+ pnpm dlx --package=@exxatdesignux/ui@latest exxat-ui sync-extras
66
+ ```
67
+
68
+ This copies `cursor-rules/`, `cursor-skills/`, `patterns/`, and
69
+ `handbook/` into your repo root. The next time Cursor opens, every
70
+ binding rule + skill is live.
71
+
72
+ ## What's inside
73
+
74
+ - **Primitives** (`@exxatdesignux/ui/components/<name>`) — Button, Input,
75
+ Select, Dialog, AlertDialog, Sheet, Drawer, Popover, DropdownMenu,
76
+ ContextMenu, Tabs, Accordion, Tooltip, Toast-free Banner, ScrollArea,
77
+ Slider, HoverCard, Avatar, Badge, StatusBadge, Calendar, Kbd, …
78
+ - **Hub stack** — `DataTable<TRow>`, `useTableState<TRow>`,
79
+ `TablePropertiesDrawer`, `HubTable<TRow>`, `ListPageTemplate`,
80
+ `PageHeader`, `KeyMetrics`, `ListPageBoardCard`,
81
+ `ListPageBoardTemplate`, `DataRowList`, `FinderPanelView`,
82
+ `FolderGridView`, `OutlineTreeMenu`.
83
+ - **Data-list registry** — `DataListViewType`, `DATA_LIST_VIEW_TILES`,
84
+ `dataListViewIcon`, `dataListViewAddShortcut`, `usesDataTableComponent`,
85
+ `usesDashboardSurface`.
86
+ - **Templates** — `ListPageTemplate`, `NestedSecondaryPanelShell`,
87
+ `DedicatedSearchLandingTemplate`, `DedicatedSearchResultsTemplate`.
88
+ - **Tokens** — `tokens/hooks-index.json` (197 tokens · 42 namespaces)
89
+ feeds the workspace ESLint plugin's `no-deprecated-tokens` rule.
90
+ - **Tailwind v4 token bridge** — `globals.css` imports the L0
91
+ `--exxat-color-*` / `--exxat-radius-*` / `--exxat-spacing-*` family
92
+ and wires Tailwind shorthand (`bg-surface-1`, `text-ink-1`,
93
+ `rounded-2`, …) via `@theme inline`.
94
+
95
+ ## CLI commands
96
+
97
+ After `pnpm add @exxatdesignux/ui` (or globally `npm i -g @exxatdesignux/ui`):
98
+
99
+ ```bash
100
+ # In an empty directory (or new sub-folder), scaffold an app:
101
+ npx --package=@exxatdesignux/ui create-exxat-app my-app
102
+
103
+ # Sync Cursor skills + binding rules + pattern docs into your repo:
104
+ npx --package=@exxatdesignux/ui exxat-ui sync-extras
105
+ ```
106
+
107
+ The standalone [`create-exxat-app`](https://www.npmjs.com/package/create-exxat-app)
108
+ package wraps the first command so `<pm> create exxat-app …` resolves
109
+ natively.
110
+
111
+ ## Docs
112
+
113
+ - Design-system **HANDBOOK** — read-this-first orientation, the 6-step
114
+ "how to build a hub" walkthrough, the §13 PR checklist.
115
+ - **Blueprints** — framework-agnostic specs for each major composition
116
+ (`PageHeader`, `DataTable`, `ListPageTemplate`, `BoardCard`,
117
+ `KeyMetrics`, …).
118
+ - **Component selection guide** — top-of-funnel decision tree for
119
+ picking the right composition.
120
+ - **Token taxonomy** — naming + deprecation policy for every CSS
121
+ custom property the DS ships.
122
+
123
+ The full doc set is mirrored under `handbook/` and `patterns/` in this
124
+ package; `exxat-ui sync-extras` drops them into your repo so they sit
125
+ alongside your source.
126
+
127
+ ## Compatibility
128
+
129
+ | Peer | Range |
130
+ | --- | --- |
131
+ | React | `>=19.0.0` |
132
+ | Next.js | `>=15.0.0` (App Router) |
133
+ | Tailwind CSS | `>=4.0.0` |
134
+ | Node | `>=22.0.0` (build / dev) |
135
+
136
+ ## License
137
+
138
+ UNLICENSED — proprietary to Exxat Design.
package/bin/init.mjs CHANGED
@@ -1,43 +1,146 @@
1
1
  #!/usr/bin/env node
2
- import { cpSync, readFileSync, readdirSync, writeFileSync } from 'fs'
3
- import { resolve, dirname } from 'path'
4
- import { fileURLToPath } from 'url'
5
- import { execSync } from 'child_process'
2
+ /**
3
+ * Scaffold a new Exxat DS app from the bundled template.
4
+ *
5
+ * Usage:
6
+ * npx create-exxat-app # scaffold into current directory
7
+ * npx create-exxat-app my-app # scaffold into ./my-app (created if missing)
8
+ * npx create-exxat-app . --force # overwrite-merge into current directory
9
+ * npx create-exxat-app my-app --no-install # skip the package install step
10
+ *
11
+ * Also runs as `npx --package=@exxatdesignux/ui create-exxat-app …` and as
12
+ * `pnpm create exxat-app …` when invoked via the standalone create-exxat-app
13
+ * discovery shim.
14
+ */
15
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"
16
+ import { resolve, dirname, basename, join } from "node:path"
17
+ import { fileURLToPath } from "node:url"
18
+ import { execSync } from "node:child_process"
6
19
 
7
20
  const __dirname = dirname(fileURLToPath(import.meta.url))
8
- const templateDir = resolve(__dirname, '../template')
9
- const selfPkgPath = resolve(__dirname, '../package.json')
10
- const targetDir = process.cwd()
21
+ const templateDir = resolve(__dirname, "../template")
22
+ const selfPkgPath = resolve(__dirname, "../package.json")
23
+ const selfPkg = JSON.parse(readFileSync(selfPkgPath, "utf8"))
11
24
 
12
- const files = readdirSync(targetDir)
13
- const isEmpty = files.length === 0 || (files.length === 1 && files[0] === '.git')
25
+ const args = process.argv.slice(2)
26
+ const flags = new Set(args.filter((a) => a.startsWith("--")))
27
+ const positional = args.filter((a) => !a.startsWith("--"))
28
+ const targetArg = positional[0] ?? "."
29
+ const targetDir = resolve(process.cwd(), targetArg)
30
+ const projectName = targetArg === "." ? basename(targetDir) : targetArg
31
+ const force = flags.has("--force")
32
+ const skipInstall = flags.has("--no-install") || flags.has("--skip-install")
14
33
 
15
- if (!isEmpty) {
16
- console.error('❌ Target directory is not empty. Run this in a new empty folder.')
34
+ if (flags.has("--help") || flags.has("-h")) {
35
+ console.log(`
36
+ create-exxat-app — scaffold a Next.js app on @exxatdesignux/ui
37
+
38
+ Usage:
39
+ create-exxat-app [target-directory] [options]
40
+
41
+ Arguments:
42
+ target-directory Where to create the app (default: ".")
43
+ A relative or absolute path. Created if it does not exist.
44
+
45
+ Options:
46
+ --force Allow scaffolding into a non-empty directory (existing
47
+ files are NOT overwritten unless template ships the
48
+ same path).
49
+ --no-install Skip the dependency install step.
50
+ -h, --help Show this message.
51
+
52
+ Examples:
53
+ create-exxat-app my-app
54
+ create-exxat-app .
55
+ create-exxat-app ../sandbox --force --no-install
56
+ `)
57
+ process.exit(0)
58
+ }
59
+
60
+ if (!existsSync(targetDir)) {
61
+ mkdirSync(targetDir, { recursive: true })
62
+ console.log(`📁 Created ${projectName}/`)
63
+ }
64
+
65
+ const targetStat = statSync(targetDir)
66
+ if (!targetStat.isDirectory()) {
67
+ console.error(`❌ Target ${targetDir} exists but is not a directory.`)
17
68
  process.exit(1)
18
69
  }
19
70
 
20
- console.log('📦 Copying Exxat DS starter app...')
71
+ const existing = readdirSync(targetDir)
72
+ const ignorable = new Set([".git", ".gitignore", ".DS_Store"])
73
+ const blockers = existing.filter((name) => !ignorable.has(name))
74
+
75
+ if (blockers.length > 0 && !force) {
76
+ console.error(`❌ Target directory is not empty (${targetDir}).`)
77
+ console.error("")
78
+ console.error(" Found:")
79
+ for (const name of blockers.slice(0, 8)) console.error(` - ${name}`)
80
+ if (blockers.length > 8) console.error(` … and ${blockers.length - 8} more`)
81
+ console.error("")
82
+ console.error(" Options:")
83
+ console.error(` 1. Scaffold into a new folder: create-exxat-app my-app`)
84
+ console.error(` 2. Allow merging existing files: create-exxat-app . --force`)
85
+ console.error("")
86
+ process.exit(1)
87
+ }
88
+
89
+ console.log(`📦 Copying Exxat DS starter app into ${projectName}/ …`)
21
90
  cpSync(templateDir, targetDir, { recursive: true })
22
91
 
23
- /** Pin DS to this CLI tarball version so `npm install` does not resolve a stale `latest` from cache. */
24
- const selfVersion = JSON.parse(readFileSync(selfPkgPath, 'utf8')).version
25
- const targetPkgPath = resolve(targetDir, 'package.json')
26
- const targetPkg = JSON.parse(readFileSync(targetPkgPath, 'utf8'))
27
- if (targetPkg.dependencies?.['@exxatdesignux/ui']) {
28
- targetPkg.dependencies['@exxatdesignux/ui'] = `^${selfVersion}`
29
- writeFileSync(targetPkgPath, `${JSON.stringify(targetPkg, null, 2)}\n`)
30
- console.log(`📌 Pinned @exxatdesignux/ui to ^${selfVersion} (matches this scaffold).`)
92
+ // Pin DS to this CLI's published version so `latest` cache drift cannot
93
+ // trick the consumer into installing an older dist.
94
+ const targetPkgPath = resolve(targetDir, "package.json")
95
+ const targetPkg = JSON.parse(readFileSync(targetPkgPath, "utf8"))
96
+ if (targetPkg.dependencies?.["@exxatdesignux/ui"]) {
97
+ targetPkg.dependencies["@exxatdesignux/ui"] = `^${selfPkg.version}`
31
98
  }
99
+ // Name the project after the target dir for nicer dev-server / process titles.
100
+ targetPkg.name = projectName.replace(/[^a-z0-9-_]/gi, "-").toLowerCase() || "my-exxat-app"
101
+ writeFileSync(targetPkgPath, `${JSON.stringify(targetPkg, null, 2)}\n`)
102
+ console.log(`📌 Pinned @exxatdesignux/ui to ^${selfPkg.version} and named the project "${targetPkg.name}".`)
32
103
 
33
- console.log('📥 Installing dependencies...')
34
- execSync('npm install', { stdio: 'inherit', cwd: targetDir })
35
-
36
- console.log('')
37
- console.log('✅ Done! Your Exxat DS app is ready.')
38
- console.log('')
39
- console.log(' npm run dev → start dev server')
40
- console.log('')
41
- console.log(' Optional — refresh Cursor skills + pattern docs from the same package:')
42
- console.log(` npx --package=@exxatdesignux/ui@${selfVersion} exxat-ui sync-extras`)
43
- console.log('')
104
+ const pm = detectPackageManager()
105
+ if (skipInstall) {
106
+ console.log(`⏭ Skipped install (--no-install).`)
107
+ } else {
108
+ console.log(`📥 Installing dependencies with ${pm.label} …`)
109
+ try {
110
+ execSync(pm.installCmd, { stdio: "inherit", cwd: targetDir })
111
+ } catch (err) {
112
+ console.error("")
113
+ console.error(`❌ ${pm.label} install failed. You can retry manually:`)
114
+ console.error(` cd ${projectName} && ${pm.installCmd}`)
115
+ process.exit(1)
116
+ }
117
+ }
118
+
119
+ console.log("")
120
+ console.log(`✅ Done! Your Exxat DS app is ready in ./${projectName}/`)
121
+ console.log("")
122
+ console.log(` cd ${projectName}`)
123
+ console.log(` ${pm.runCmd} dev # start dev server`)
124
+ console.log("")
125
+ console.log(" Optional — refresh Cursor skills + pattern docs:")
126
+ console.log(` ${pm.dlxCmd} --package=@exxatdesignux/ui@${selfPkg.version} exxat-ui sync-extras`)
127
+ console.log("")
128
+
129
+ /**
130
+ * Detect the package manager that invoked us — npm_config_user_agent is set
131
+ * by every modern package-runner (npm, pnpm, yarn, bun) and looks like
132
+ * "pnpm/10.33.0 npm/? node/v22.0.0 darwin arm64". We fall back to npm.
133
+ */
134
+ function detectPackageManager() {
135
+ const ua = process.env.npm_config_user_agent ?? ""
136
+ if (ua.startsWith("pnpm")) {
137
+ return { label: "pnpm", installCmd: "pnpm install", runCmd: "pnpm", dlxCmd: "pnpm dlx" }
138
+ }
139
+ if (ua.startsWith("yarn")) {
140
+ return { label: "yarn", installCmd: "yarn install", runCmd: "yarn", dlxCmd: "yarn dlx" }
141
+ }
142
+ if (ua.startsWith("bun")) {
143
+ return { label: "bun", installCmd: "bun install", runCmd: "bun", dlxCmd: "bunx" }
144
+ }
145
+ return { label: "npm", installCmd: "npm install", runCmd: "npm run", dlxCmd: "npx" }
146
+ }
@@ -11,7 +11,7 @@ alwaysApply: true
11
11
 
12
12
  1. **Shell** — Use **`ListPageBoardCard`** from **`components/data-views/list-page-board-card.tsx`** for product board cards (same **`Card` `size="sm"`** treatment as Placements).
13
13
  2. **Hierarchy** — **Title** (`ListPageBoardCardTitleRow`) → optional **avatar** (`ListPageBoardCardAvatar` on `trailing`) → **status row** (`ListPageBoardCardBadgeRow` + **`ListHubStatusBadge`** **`surface="board"`**) when the entity has status → **body** (`ListPageBoardCardBody`) with **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** (**`components/data-views/board-card-primitives.tsx`**).
14
- 3. **Status** — **Placements** (**`PLACEMENT_STATUS_*`** + **`StatusBadge`** in **`placements-table-cells.tsx`**), **Team / Compliance / Question bank** (**`ListHubStatusBadge`** + entity maps): all labels/tints/icons live in **`lib/list-status-badges.ts`**. **`surface="table"`** in grid/list rows, **`surface="board"`** on kanban cards. Prefer semantic **`LIST_HUB_STATUS_TINT_*`**. **MUST NOT** use **`uppercase`** on those chips.
14
+ 3. **Status** — **Placements** (**`PLACEMENT_STATUS_*`** + **`StatusBadge`** in **`placements-table-cells.tsx`**), **Team / Compliance / Library** (**`ListHubStatusBadge`** + entity maps): all labels/tints/icons live in **`lib/list-status-badges.ts`**. **`surface="table"`** in grid/list rows, **`surface="board"`** on kanban cards. Prefer semantic **`LIST_HUB_STATUS_TINT_*`**. **MUST NOT** use **`uppercase`** on those chips.
15
15
  4. **Simple column boards** — **`ListPageBoardTemplate`** + **`renderCard`**; compose **`ListPageBoardCard`** inside **`renderCard`**.
16
16
 
17
17
  ## MUST NOT
@@ -27,14 +27,14 @@ alwaysApply: true
27
27
 
28
28
  8. **`ListPageViewFrame`** — Non-**`DataTable`** view bodies (**folder**, **panel**, icon grids, comparable dashboard sections) **MUST** use **`ListPageViewFrame`** (and exported max-width constants) instead of copy-pasted **`mx-*` / `max-w-*`** per hub (**`exxat-list-page-view-shells.mdc`**).
29
29
 
30
- 9. **`components/data-views/`** — New **record-bearing** view layouts (**grids**, **OS folder**, **finder split**) **MUST** land as **generic** building blocks under **`data-views/`** with **`rows`**, **`getRowId`**, render props — hub **`TeamTable`** / **`QuestionBankTable`** **only** wires props and branch logic (**`AGENTS.md` §4.5**).
30
+ 9. **`components/data-views/`** — New **record-bearing** view layouts (**grids**, **OS folder**, **finder split**) **MUST** land as **generic** building blocks under **`data-views/`** with **`rows`**, **`getRowId`**, render props — hub **`TeamTable`** / **`LibraryTable`** **only** wires props and branch logic (**`AGENTS.md` §4.5**).
31
31
 
32
32
  10. **Hub client composition** — One **`*-client.tsx`** owns **`useTableState`**, passes **`tableState.rows`** into every **`viewType`** branch, and mounts **template** slots (**metrics**, **export**, **`beforeSiteHeader`**) — **MUST NOT** split the same hub across multiple clients with different row sources.
33
33
 
34
34
  ## MUST NOT
35
35
 
36
36
  - Ship alternate mock datasets per **`DataListViewType`** for the same hub without documenting them as **computed derivatives** of one canonical list.
37
- - Duplicate entity fields in inspector-only types that contradict **`QuestionBankItem`** / **`Placement`** / etc.; extend the shared interface **once**.
37
+ - Duplicate entity fields in inspector-only types that contradict **`LibraryItem`** / **`Placement`** / etc.; extend the shared interface **once**.
38
38
 
39
39
  ## See also
40
40
 
@@ -29,4 +29,4 @@ alwaysApply: false
29
29
 
30
30
  - **`exxat-page-vs-drawer.mdc`** — invite is a **sheet**, not a new route.
31
31
  - **`exxat-kbd-shortcuts.mdc`** — workflow **Cancel** / **Send invite** shortcuts on the sheet.
32
- - **`exxat-question-bank-hub-header.mdc`** — Question bank library: when URL is **folder-scoped**, **⋯ More** also includes **Customize folder** (hub client hosts **`QuestionBankNewFolderSheet`**).
32
+ - **`exxat-library-hub-header.mdc`** — Library library: when URL is **folder-scoped**, **⋯ More** also includes **Customize folder** (hub client hosts **`LibraryNewFolderSheet`**).
@@ -16,6 +16,7 @@ For **any app screen that shows a browsable, filterable grid of records** (lists
16
16
  5. **Filters:** Configure per-column `filter:` blocks in `ColumnDef` (text / select / date / number) — `HubTable` turns those into chips automatically via `columnsToFilterFields`. **MUST NOT** ship one-off filter inputs above the table that duplicate this.
17
17
  6. **Table properties:** Always reachable. `HubTable` mounts `TablePropertiesDrawerButton` in `toolbarSlot`; on **`ListPageTemplate`** pages with **table / list / board / dashboard** tabs it **MUST** also receive **`currentView`** and **`onViewChange`** (see **`apps/web/AGENTS.md` §4.2** and **`.cursor/rules/exxat-table-properties-drawer.mdc`**) so Properties matches the selected view.
18
18
  7. **Dropdown menus:** `DropdownMenuContent` uses the shared **`@exxatdesignux/ui`** default (**intrinsic `w-max`**, **`min-w-52`**, capped **`max-w`**) for view settings, row ⋯, column menus, and filter pickers — **pure CSS**, no **`ResizeObserver`**. Override only for deliberate narrow/wide rails (e.g. pagination **`w-20`**, account trigger-width, school switcher **`!w-max min-w-72 …`**). See **`docs/data-views-pattern.md`** (“Dropdown menus”).
19
+ 8. **Cell renderers MUST come from `@/components/data-views` (`table-cells.tsx`).** The DS ships **`ProgressCell`**, **`CurrencyCell`**, **`NumericCell`**, **`RatingCell`**, **`SignalBarsCell`**, **`BooleanToggleCell`**, **`AttachmentCountCell`**, **`ExternalLinkCell`**, **`RelativeTimeCell`**, **`PeopleAvatarRailCell`**, **`PillCell`**, **`TagListCell`**, and a generic **`RowActionsCell<TRow>`**. A `ColumnDef['cell']` for these patterns is a **one-liner** that calls the named cell. **MUST NOT** inline `Intl.NumberFormat`, raw `<a target="_blank">`, `[1,2,3,4,5].map(s => …)` star loops, paperclip + count chips, custom face-rail `AvatarGroup`s, or per-hub `DropdownMenu` overflow menus inside `cell:` — those are signals you're re-deriving a shipped primitive. Catalog: `apps/web/components/columns-showcase.tsx` (`/columns`). Skill: `.cursor/skills/exxat-token-economy/SKILL.md` §3.
19
20
 
20
21
  **Reference implementations:**
21
22
 
@@ -30,6 +31,7 @@ For **any app screen that shows a browsable, filterable grid of records** (lists
30
31
  - Do **not** build product list pages with `@/components/ui/table` alone, raw `<table>` markup, or third-party data grids.
31
32
  - Do **not** mount raw `<DataTable>` inside `ListPageTemplate.renderContent` — use `HubTable`. Raw `<DataTable>` does not ship the Properties drawer or filter chips; users lose discoverability.
32
33
  - Do **not** introduce a second “table component” pattern for the same product surfaces (splitting search/filters/properties across incompatible implementations).
34
+ - Do **not** inline-implement progress bars, currency formatters, rating stars, relative-time helpers, attachment chips, external-link wrappers, face rails, type pills, tag lists, or row-action dropdowns inside a `ColumnDef['cell']`. Import the named cell from `@/components/data-views` instead. New hub with novel cell needs MUST extend `table-cells.tsx` (and ship the catalog entry in `columns-showcase.tsx`), not fork inline JSX.
33
35
 
34
36
  ## Exceptions
35
37
 
@@ -18,7 +18,7 @@ Shared building blocks use **generic** `DedicatedSearch*` names under `component
18
18
 
19
19
  ## MUST NOT
20
20
 
21
- - Introduce parallel `*QuestionBankSearchLanding*` (or similar) components for another entity — extend the generic layer and compose in the hub client.
21
+ - Introduce parallel `*LibrarySearchLanding*` (or similar) components for another entity — extend the generic layer and compose in the hub client.
22
22
 
23
23
  ## See also
24
24
 
@@ -26,7 +26,7 @@ Before implementing or reviewing **list / table / board / dashboard / data-heavy
26
26
  11. **Centralized hub dataset + presentation** — **`apps/web/AGENTS.md` §4.1** / **§4.5** + **`.cursor/rules/exxat-centralized-list-dataset.mdc`** + **`.cursor/skills/exxat-centralized-list-dataset/SKILL.md`** — one **`useTableState`** row bag for **all** views + **`TablePropertiesDrawer`**; **`ListPageViewFrame`** + **`data-views/`** for shared chrome; **no** parallel mock arrays per **`DataListViewType`**.
27
27
  12. **Font Awesome (product icons)** — **`.cursor/rules/exxat-fontawesome-icons.mdc`** — Kit in **`app/layout.tsx`**, **`fa-light` / `fa-solid`**, subset audit, **`aria-hidden`** on decorative **`<i>`**; pair with **`exxat-accessibility.mdc`** for icon-only controls.
28
28
  13. **Monospace system IDs** — **`apps/web/AGENTS.md` §1 (item 9)** + **`.cursor/rules/exxat-mono-ids.mdc`** + **`.cursor/skills/exxat-mono-ids/SKILL.md`** — **`font-mono tabular-nums`** on question/record keys; mono **only** the ID token in mixed lines.
29
- 14. **Primary nav → secondary panel** (Question bank) — **`apps/web/AGENTS.md` §4.6** + **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — **`secondaryPanel`** + **`PANELS`** + **`useAutoPanel`**; **`--secondary-panel-bg`** brand elevation (**`apps/web/docs/shell-surface-elevation-pattern.md`**); **folder URL scope** → **`exxat-question-bank-hub-header.mdc`** + **`apps/web/docs/question-bank-hub-header-pattern.md`**.
29
+ 14. **Primary nav → secondary panel** (Library) — **`apps/web/AGENTS.md` §4.6** + **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — **`secondaryPanel`** + **`PANELS`** + **`useAutoPanel`**; **`--secondary-panel-bg`** brand elevation (**`apps/web/docs/shell-surface-elevation-pattern.md`**); **folder URL scope** → **`exxat-library-hub-header.mdc`** + **`apps/web/docs/library-hub-header-pattern.md`**.
30
30
  15. **Collaboration & access** (shared hubs) — **`apps/web/AGENTS.md` §4.7** + **`.cursor/rules/exxat-collaboration-access.mdc`** + **`.cursor/skills/exxat-collaboration-access/SKILL.md`** — face rail, **`InviteCollaboratorsDrawer`**, **`lib/collaborator-access.ts`**.
31
31
  16. **Dedicated search** (landing vs results, `DedicatedSearch*`) — **`apps/web/AGENTS.md` §4.8** + **`.cursor/rules/exxat-dedicated-search-surfaces.mdc`** + **`.cursor/skills/exxat-dedicated-search-surfaces/SKILL.md`**.
32
32
  17. **No toast** → **`apps/web/AGENTS.md` §6.5** + **`exxat-no-toast.mdc`** — do not use **`toast()`** / Sonner / snackbars for product messaging; use banners, inline status, or dialogs.
@@ -50,7 +50,7 @@ Before implementing or reviewing **list / table / board / dashboard / data-heavy
50
50
  - **`apps/web/docs/blueprints/`** — framework-agnostic specs (start: `page-header.md`, `data-table.md`).
51
51
  - **`apps/web/docs/migrations/`** — token rename + removal history.
52
52
  - **`packages/ui/tokens/hooks-index.json`** — machine-readable token index (regenerate via `pnpm --filter @exxatdesignux/ui tokens:index`).
53
- - `.cursor/rules/exxat-data-tables.mdc`, `exxat-list-page-connected-views.mdc`, **`exxat-centralized-list-dataset.mdc`**, **`exxat-list-page-view-shells.mdc`**, **`exxat-fontawesome-icons.mdc`**, **`exxat-mono-ids.mdc`**, **`exxat-primary-nav-secondary-panel.mdc`**, **`exxat-question-bank-hub-header.mdc`**, **`exxat-collaboration-access.mdc`**, **`exxat-dedicated-search-surfaces.mdc`**, **`exxat-kpi-trends.mdc`**, **`exxat-kpi-flat-band.mdc`**, **`exxat-drawer-vs-dialog.mdc`**, **`exxat-card-vs-list-rows.mdc`**, **`exxat-kpi-max-four.mdc`**, **`exxat-reuse-before-custom.mdc`**, `exxat-table-properties-drawer.mdc`, **`exxat-board-cards.mdc`**, **`exxat-page-vs-drawer.mdc`**, **`exxat-no-toast.mdc`**, **`exxat-command-menu.mdc`**, `exxat-kbd-shortcuts.mdc`, `exxat-accessibility.mdc` at repo root; **`apps/web/.cursor/rules/exxat-dashboard-view-charts.mdc`** for Data tab charts.
53
+ - `.cursor/rules/exxat-data-tables.mdc`, `exxat-list-page-connected-views.mdc`, **`exxat-centralized-list-dataset.mdc`**, **`exxat-list-page-view-shells.mdc`**, **`exxat-fontawesome-icons.mdc`**, **`exxat-mono-ids.mdc`**, **`exxat-primary-nav-secondary-panel.mdc`**, **`exxat-library-hub-header.mdc`**, **`exxat-collaboration-access.mdc`**, **`exxat-dedicated-search-surfaces.mdc`**, **`exxat-kpi-trends.mdc`**, **`exxat-kpi-flat-band.mdc`**, **`exxat-drawer-vs-dialog.mdc`**, **`exxat-card-vs-list-rows.mdc`**, **`exxat-kpi-max-four.mdc`**, **`exxat-reuse-before-custom.mdc`**, `exxat-table-properties-drawer.mdc`, **`exxat-board-cards.mdc`**, **`exxat-page-vs-drawer.mdc`**, **`exxat-no-toast.mdc`**, **`exxat-command-menu.mdc`**, `exxat-kbd-shortcuts.mdc`, `exxat-accessibility.mdc` at repo root; **`apps/web/.cursor/rules/exxat-dashboard-view-charts.mdc`** for Data tab charts.
54
54
  - **`apps/web/docs/kpi-flat-band-pattern.md`**, **`apps/web/docs/shell-surface-elevation-pattern.md`** — flat KPI strip + sidebar/secondary/page OKLCH stack.
55
- - **`apps/web/docs/question-bank-hub-header-pattern.md`** — folder-scoped question bank header **Customize folder**.
55
+ - **`apps/web/docs/library-hub-header-pattern.md`** — folder-scoped library header **Customize folder**.
56
56
  - **`apps/web/docs/collaboration-access-pattern.md`** — shared hub face rail + invite sheet.
@@ -0,0 +1,28 @@
1
+ ---
2
+ description: Library library — folder-scoped hub header More menu must expose Customize folder; sheet on hub client
3
+ globs: apps/web/components/library-*.tsx, packages/ui/template/components/library-*.tsx
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Exxat DS — Library hub header (folder scope)
8
+
9
+ When the library library URL is **scoped to a folder** (`parseLibraryNav` → **`scope === "folder"`** and **`folderId`** set), users are effectively on a **“folder page”**: the hub title matches that folder, and **global** actions belong in the **`LibraryPageHeader`** **⋯ More** menu — not only on per-row or per-tile overflow menus inside a single view tab.
10
+
11
+ **Pattern doc:** **`apps/web/docs/library-hub-header-pattern.md`**. **Handbook:** **`apps/web/AGENTS.md` §4.6** (folder-scoped hub chrome).
12
+
13
+ ## MUST
14
+
15
+ 1. **`LibraryPageHeader`** — When **`navState.scope === "folder"`** and **`navState.folderId`** resolves to a row in **`folders`**, pass **`onCustomizeFolder`** so **⋯ More** includes **Customize folder** ( **`fa-wand-magic-sparkles`** + label **Customize folder** ), placed after **Invite people** (collaboration variant) and before **Export**.
16
+ 2. **Hub client** — Mount **`LibraryNewFolderSheet`** on the **hub client** (e.g. **`LibraryClient`**) next to **`ListPageTemplate`**, driven by local **`open` / `customizingFolder`** state opened from **`onCustomizeFolder`**. **MUST NOT** rely on **`LibraryTable`** alone to host the sheet when some view branches (**table**, **list**, **board**, **dashboard**) do not render that sheet — users would lose **Customize folder** on those tabs.
17
+ 3. **`onCreated`** — On save, **`setFolders`** (or equivalent) **maps** the scoped folder **`id`** to updated **`name`**, **`icon`**, **`colorKey`** — same contract as **`LibraryTable`** panel/tree customize handlers.
18
+
19
+ ## MUST NOT
20
+
21
+ - Omit **Customize folder** from the header **⋯** when the URL is folder-scoped, expecting users to find it only on secondary-nav tree rows or OS-folder tiles.
22
+ - Mount **only** one customize sheet inside **`LibraryTable`** without a **client-level** sheet when the hub uses **`ListPageTemplate`** view tabs that omit that table subtree.
23
+
24
+ ## See also
25
+
26
+ - **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — URL scope + secondary panel.
27
+ - **`.cursor/rules/exxat-collaboration-access.mdc`** — **`variant="collaboration"`** header + **⋯** invite pattern.
28
+ - **`lib/library-nav.ts`** — **`parseLibraryNav`**, **`LibraryNavState`**.
@@ -16,7 +16,7 @@ Use this when rendering **system identifiers** — values a user copies, searche
16
16
 
17
17
  ## SHOULD
18
18
 
19
- - Match existing hubs: **`question-bank-table.tsx`**, **`question-bank-list-view.tsx`**, **`new-question-composer.tsx`** (header subtitle), **`sites-table.tsx`** (`row.id`).
19
+ - Match existing hubs: **`library-table.tsx`**, **`library-list-view.tsx`**, **`new-library-item-form.tsx`** (header subtitle), **`sites-table.tsx`** (`row.id`).
20
20
  - Prefer **`truncate`** / **`min-w-0`** on mono IDs in tight layouts so long tokens do not blow out columns.
21
21
 
22
22
  ## MUST NOT
@@ -24,7 +24,7 @@ Dense or width-constrained surfaces **MUST NOT** squeeze email if it harms scana
24
24
  ## Table / list / drawer / inspector
25
25
 
26
26
  - **Dedicated person column** or **profile / invite / access** surfaces — show **avatar + name + email** (stacked or name with email below), consistent with **`InviteCollaboratorsDrawer`** and **`PageHeader`** collaboration variant.
27
- - **Example:** `QuestionBankTable` **Author** — `AvatarInitials`, **primary name** (`text-sm font-medium`), **muted `text-xs` email** with optional `mailto:` (row click does not navigate when the link is used).
27
+ - **Example:** `LibraryTable` **Author** — `AvatarInitials`, **primary name** (`text-sm font-medium`), **muted `text-xs` email** with optional `mailto:` (row click does not navigate when the link is used).
28
28
 
29
29
  ## Avatar rows — **never overlapping**
30
30
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Primary sidebar item opens nested SecondaryPanel (Question bank pattern)
2
+ description: Primary sidebar item opens nested SecondaryPanel (Library pattern)
3
3
  globs: apps/web/components/**/*.tsx
4
4
  alwaysApply: false
5
5
  ---
@@ -8,13 +8,13 @@ alwaysApply: false
8
8
 
9
9
  Some hubs expose **scoped navigation** (All / My / tree / filters) in a **nested panel** between the **icon rail** and **main content** — not as **primary nav child rows**.
10
10
 
11
- **Reference:** **Question bank** — **`lib/mock/navigation.tsx`** (`secondaryPanel: "question-bank"`), **`components/app-sidebar.tsx`**, **`components/secondary-panel.tsx`**, **`components/question-bank-panel-activator.tsx`**, **`components/question-bank-secondary-nav.tsx`**, **`lib/question-bank-nav.ts`** (URL scope).
11
+ **Reference:** **Library** — **`lib/mock/navigation.tsx`** (`secondaryPanel: "library"`), **`components/app-sidebar.tsx`**, **`components/secondary-panel.tsx`**, **`components/library-panel-activator.tsx`**, **`components/library-secondary-nav.tsx`**, **`lib/library-nav.ts`** (URL scope).
12
12
 
13
13
  ## MUST (wiring)
14
14
 
15
- 1. **`NavLinkItem`** — On the **primary** row that should drive the panel, set **`secondaryPanel`** to a **stable string id** (e.g. **`"question-bank"`**). Keep **`url`** pointing at the **hub route**.
15
+ 1. **`NavLinkItem`** — On the **primary** row that should drive the panel, set **`secondaryPanel`** to a **stable string id** (e.g. **`"library"`**). Keep **`url`** pointing at the **hub route**.
16
16
  2. **`SecondaryPanel` registry** — In **`components/secondary-panel.tsx`**, add **`PANELS[id]`** → component that renders **panel chrome** (title, optional search) + **your secondary nav** list. **MUST** keep ids in sync with **`NAV_PRIMARY`**.
17
- 3. **Auto-open on route** — The hub **`client`** (or layout slot) **MUST** mount **`*PanelActivator`** that calls **`useAutoPanel(id)`** with the **same id**, so deep links and first visit open the panel while the route is mounted (e.g. **`QuestionBankPanelActivator`** + **`beforeSiteHeader`** on **`QuestionBankClient`**).
17
+ 3. **Auto-open on route** — The hub **`client`** (or layout slot) **MUST** mount **`*PanelActivator`** that calls **`useAutoPanel(id)`** with the **same id**, so deep links and first visit open the panel while the route is mounted (e.g. **`LibraryPanelActivator`** + **`beforeSiteHeader`** on **`LibraryClient`**).
18
18
  4. **Same-route reopen** — **`AppSidebar`** already calls **`openPanel(secondaryPanel)`** when the user clicks the primary item **again** while already on that **`url`** (prevents no-op navigation). Secondary nav rows that stay on the same path **SHOULD** call **`openPanel`** on click where **Next.js `Link`** would not fire (same **`href`**).
19
19
 
20
20
  ## Surface elevation (brand chrome)
@@ -35,7 +35,7 @@ The nested panel sits **between** the icon rail and main content. **MUST** use t
35
35
 
36
36
  ## SHOULD
37
37
 
38
- - Drive **hub scope** from the **URL** (**`useSearchParams`** + helpers like **`parseQuestionBankNav`**) so **refresh**, **share link**, and **breadcrumbs** match the secondary list.
38
+ - Drive **hub scope** from the **URL** (**`useSearchParams`** + helpers like **`parseLibraryNav`**) so **refresh**, **share link**, and **breadcrumbs** match the secondary list.
39
39
  - Keep **panel content** **Font Awesome**-aligned with the rest of the app (**`.cursor/rules/exxat-fontawesome-icons.mdc`**).
40
40
 
41
41
  ## MUST NOT
@@ -47,6 +47,6 @@ The nested panel sits **between** the icon rail and main content. **MUST** use t
47
47
 
48
48
  - **`apps/web/AGENTS.md` §4.6** — handbook summary.
49
49
  - **`apps/web/docs/shell-surface-elevation-pattern.md`** — OKLCH tokens + product theme.
50
- - **`.cursor/rules/exxat-question-bank-hub-header.mdc`** — folder-scoped library header **⋯** → **Customize folder** + client-mounted sheet.
50
+ - **`.cursor/rules/exxat-library-hub-header.mdc`** — folder-scoped library header **⋯** → **Customize folder** + client-mounted sheet.
51
51
  - **`.cursor/rules/exxat-page-vs-drawer.mdc`** — when **drawer** vs **route**; secondary panel is **nav chrome**, not a workflow drawer.
52
52
  - **`.cursor/rules/exxat-kpi-flat-band.mdc`** — flat KPI strip (separate from panel fill).
@@ -8,7 +8,7 @@ alwaysApply: true
8
8
  ## MUST
9
9
 
10
10
  1. **Compose first** — Use existing **`components/ui/`**, **`components/data-views/`**, **`components/templates/`**, **`PageHeader`**, **`ListPageTemplate`**, **`DataTable`**, **`KeyMetrics`**, and patterns in **`AGENTS.md` §9** before writing new layout or interaction chrome.
11
- 2. **Search the codebase** — Grep or open the nearest hub (Placements, Team, Question bank) for the same UX (toolbar, drawer, metrics, board card, `ListPageViewFrame`, etc.).
11
+ 2. **Search the codebase** — Grep or open the nearest hub (Placements, Team, Library) for the same UX (toolbar, drawer, metrics, board card, `ListPageViewFrame`, etc.).
12
12
  3. **Extend in place** — Prefer adding a variant, slot, or prop to a shared component over a one-off duplicate under a single route.
13
13
 
14
14
  ## When the tool must ask the user
@@ -39,13 +39,13 @@ Import from **`@/components/data-views/list-page-board-card`**:
39
39
 
40
40
  Prefer **two-line blocks** for stacked **primary / secondary** facts so cards match Placements’ visual rhythm.
41
41
 
42
- ## Status badges (list hubs: Team, Compliance, Question bank, …)
42
+ ## Status badges (list hubs: Team, Compliance, Library, …)
43
43
 
44
44
  - **Maps (single source):** **`lib/list-status-badges.ts`** — per-entity `*_STATUS_LABEL`, `*_STATUS_BADGE_CLASS`, `*_STATUS_ICON`, plus semantic **`LIST_HUB_STATUS_TINT_*`** (success / warning / neutral / danger) for new domains.
45
45
  - **Component:** **`ListHubStatusBadge`** from **`@/components/list-hub-status-badge`** — **`surface="table"`** for **DataTable** cells and **list** rows; **`surface="board"`** inside **`ListPageBoardCardBadgeRow`**. Do not duplicate the shell classes on each page.
46
46
  - Use the **same** maps everywhere for that entity so copy and colors never drift.
47
47
  - **Do not** add **`uppercase`** or **`tracking-wide`** — **sentence / title case**, consistent with **`BoardStatusBadge`** on Placements (`placement-board-card.tsx`).
48
- - **Placements** lifecycle uses **`StatusBadge`** in **`placements-table-cells.tsx`** — thin wrapper over **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`** in **`list-status-badges.ts`** (same visuals as Team / Question bank).
48
+ - **Placements** lifecycle uses **`StatusBadge`** in **`placements-table-cells.tsx`** — thin wrapper over **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`** in **`list-status-badges.ts`** (same visuals as Team / Library).
49
49
 
50
50
  ## Avatar when mock has no `initials`
51
51
 
@@ -76,7 +76,7 @@ Goal: **one row model**, **one filtered bag** (`tableState.rows`), **one place f
76
76
 
77
77
  - **`components/placements-client.tsx`** + **`placements-table.tsx`** — Placements pattern.
78
78
  - **`components/team-client.tsx`** + **`team-table.tsx`**.
79
- - **`components/question-bank-table.tsx`** — multiple **`DataListViewType`** branches sharing **`tableState`** / **`folders`** / **`items`**.
79
+ - **`components/library-table.tsx`** — multiple **`DataListViewType`** branches sharing **`tableState`** / **`folders`** / **`items`**.
80
80
 
81
81
  ---
82
82
 
@@ -9,7 +9,7 @@ user-invocable: true
9
9
  **Handbook:** `apps/web/AGENTS.md` §4.7
10
10
  **Narrative:** `apps/web/docs/collaboration-access-pattern.md`
11
11
  **Cursor rule:** `.cursor/rules/exxat-collaboration-access.mdc`
12
- **Related (Question bank folder scope + ⋯ Customize folder):** `.cursor/rules/exxat-question-bank-hub-header.mdc` · `docs/question-bank-hub-header-pattern.md`
12
+ **Related (Library folder scope + ⋯ Customize folder):** `.cursor/rules/exxat-library-hub-header.mdc` · `docs/library-hub-header-pattern.md`
13
13
 
14
14
  ## Wiring checklist
15
15
 
@@ -20,7 +20,7 @@ user-invocable: true
20
20
  5. **Access maps** — `lib/collaborator-access.ts` for Owner / Editor / Commenter / Viewer, invite options, and **`COLLABORATION_HEADER_ADD_LABEL`**.
21
21
  6. **Header** — empty roster → outline **Add collaborator**; non-empty → face rail; both open the invite sheet.
22
22
  7. **Invite sheet** — `InviteCollaboratorsDrawer`: export-style **`Sheet`**, combined email + access menu, grouped roster (name → email → role tags → access badge).
23
- 8. **Question bank — folder URL scope** — When **`?scope=folder`**, **`QuestionBankPageHeader`** **⋯** also lists **Customize folder**; **`QuestionBankNewFolderSheet`** on **`QuestionBankClient`** — **`.cursor/rules/exxat-question-bank-hub-header.mdc`**, **`docs/question-bank-hub-header-pattern.md`**.
23
+ 8. **Library — folder URL scope** — When **`?scope=folder`**, **`LibraryPageHeader`** **⋯** also lists **Customize folder**; **`LibraryNewFolderSheet`** on **`LibraryClient`** — **`.cursor/rules/exxat-library-hub-header.mdc`**, **`docs/library-hub-header-pattern.md`**.
24
24
 
25
25
  ## MUST NOT
26
26
 
@@ -31,5 +31,5 @@ user-invocable: true
31
31
 
32
32
  ## Reference
33
33
 
34
- - `components/collaboration-access-flow.tsx`, `components/question-bank-page-header.tsx`, `components/question-bank-client.tsx`
34
+ - `components/collaboration-access-flow.tsx`, `components/library-page-header.tsx`, `components/library-client.tsx`
35
35
  - `components/invite-collaborators-drawer.tsx`, `components/export-drawer.tsx`
@@ -37,8 +37,8 @@ description: >-
37
37
  | Recents list | `components/dedicated-search-recents.tsx` |
38
38
  | Recents storage factory | `lib/dedicated-search-recents.ts` |
39
39
  | Optional default `q` patcher | `lib/dedicated-search-url.ts` |
40
- | Question bank adapter (placeholders + patch) | `lib/question-bank-dedicated-search.ts` |
41
- | Question bank wiring | `components/question-bank-client.tsx` |
40
+ | Library adapter (placeholders + patch) | `lib/library-dedicated-search.ts` |
41
+ | Library wiring | `components/library-client.tsx` |
42
42
 
43
43
  ## Cursor rule
44
44