@exxatdesignux/ui 0.5.10 → 0.5.12

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 (478) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/cli.mjs +70 -1
  3. package/bin/init.mjs +18 -4
  4. package/bin/sync-extras.mjs +28 -4
  5. package/consumer-extras/README.md +41 -5
  6. package/consumer-extras/cursor-rules/exxat-accessibility.mdc +2 -1
  7. package/consumer-extras/cursor-rules/exxat-board-cards.mdc +5 -3
  8. package/consumer-extras/cursor-rules/exxat-breadcrumbs-no-back.mdc +1 -0
  9. package/consumer-extras/cursor-rules/exxat-card-vs-list-rows.mdc +1 -0
  10. package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +4 -2
  11. package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -0
  12. package/consumer-extras/cursor-rules/exxat-command-menu.mdc +3 -2
  13. package/consumer-extras/cursor-rules/exxat-data-tables.mdc +5 -3
  14. package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +7 -0
  15. package/consumer-extras/cursor-rules/exxat-drawer-vs-dialog.mdc +2 -1
  16. package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +13 -2
  17. package/consumer-extras/cursor-rules/exxat-fontawesome-icons.mdc +1 -0
  18. package/consumer-extras/cursor-rules/exxat-hub-supported-views.mdc +6 -4
  19. package/consumer-extras/cursor-rules/exxat-kbd-shortcuts.mdc +6 -5
  20. package/consumer-extras/cursor-rules/exxat-kpi-flat-band.mdc +1 -0
  21. package/consumer-extras/cursor-rules/exxat-kpi-max-four.mdc +1 -0
  22. package/consumer-extras/cursor-rules/exxat-kpi-trends.mdc +1 -0
  23. package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +2 -1
  24. package/consumer-extras/cursor-rules/exxat-list-page-connected-views.mdc +6 -2
  25. package/consumer-extras/cursor-rules/exxat-list-page-view-shells.mdc +2 -1
  26. package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +2 -1
  27. package/consumer-extras/cursor-rules/exxat-nav-single-active.mdc +4 -3
  28. package/consumer-extras/cursor-rules/exxat-no-image-pixel-copy.mdc +25 -14
  29. package/consumer-extras/cursor-rules/exxat-no-slds-leakage.mdc +8 -2
  30. package/consumer-extras/cursor-rules/exxat-no-toast.mdc +1 -0
  31. package/consumer-extras/cursor-rules/exxat-no-vaul.mdc +2 -1
  32. package/consumer-extras/cursor-rules/exxat-page-header-actions.mdc +6 -4
  33. package/consumer-extras/cursor-rules/exxat-page-vs-drawer.mdc +2 -1
  34. package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -0
  35. package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +2 -1
  36. package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -0
  37. package/consumer-extras/cursor-rules/exxat-sidebar-shell.mdc +13 -7
  38. package/consumer-extras/cursor-rules/exxat-table-properties-drawer.mdc +5 -3
  39. package/consumer-extras/cursor-rules/exxat-table-row-preview.mdc +1 -0
  40. package/consumer-extras/cursor-rules/exxat-tabs-chrome.mdc +6 -4
  41. package/consumer-extras/cursor-rules/exxat-token-discipline.mdc +6 -0
  42. package/consumer-extras/cursor-rules/exxat-ux-discovery-protocol.mdc +92 -12
  43. package/consumer-extras/cursor-rules/exxat-ux-principles.mdc +1 -0
  44. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +1 -1
  45. package/consumer-extras/cursor-skills/exxat-kpi-trends/SKILL.md +5 -3
  46. package/consumer-extras/cursor-skills/exxat-senior-ux/SKILL.md +70 -17
  47. package/consumer-extras/patterns/command-menu-pattern.md +2 -2
  48. package/consumer-extras/patterns/consumer-upgrade-checklist.md +1 -1
  49. package/consumer-extras/patterns/jobs/README.md +1 -1
  50. package/consumer-extras/patterns/perf-memory-pattern.md +115 -150
  51. package/consumer-extras/scripts/dev-guard.mjs +156 -0
  52. package/consumer-extras/templates/README.md +23 -0
  53. package/consumer-extras/templates/handoff.md +190 -0
  54. package/dist/hooks/use-app-theme.d.ts +1 -1
  55. package/package.json +2 -2
  56. package/{template → template-vite}/.claude/skills/exxat-ds-skill/SKILL.md +184 -23
  57. package/template-vite/.cursor/rules/exxat-accessibility.mdc +40 -0
  58. package/template-vite/.cursor/rules/exxat-board-cards.mdc +28 -0
  59. package/template-vite/.cursor/rules/exxat-breadcrumbs-no-back.mdc +22 -0
  60. package/template-vite/.cursor/rules/exxat-card-vs-list-rows.mdc +22 -0
  61. package/template-vite/.cursor/rules/exxat-centralized-list-dataset.mdc +46 -0
  62. package/template-vite/.cursor/rules/exxat-collaboration-access.mdc +33 -0
  63. package/{template → template-vite}/.cursor/rules/exxat-command-menu.mdc +5 -5
  64. package/template-vite/.cursor/rules/exxat-data-tables.mdc +47 -0
  65. package/template-vite/.cursor/rules/exxat-dedicated-search-surfaces.mdc +32 -0
  66. package/template-vite/.cursor/rules/exxat-drawer-vs-dialog.mdc +23 -0
  67. package/template-vite/.cursor/rules/exxat-ds-agents.mdc +87 -0
  68. package/template-vite/.cursor/rules/exxat-fontawesome-icons.mdc +32 -0
  69. package/template-vite/.cursor/rules/exxat-hub-supported-views.mdc +56 -0
  70. package/{template → template-vite}/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -0
  71. package/template-vite/.cursor/rules/exxat-kpi-flat-band.mdc +29 -0
  72. package/template-vite/.cursor/rules/exxat-kpi-max-four.mdc +22 -0
  73. package/template-vite/.cursor/rules/exxat-kpi-trends.mdc +32 -0
  74. package/template-vite/.cursor/rules/exxat-library-hub-header.mdc +29 -0
  75. package/template-vite/.cursor/rules/exxat-list-page-connected-views.mdc +28 -0
  76. package/template-vite/.cursor/rules/exxat-list-page-view-shells.mdc +32 -0
  77. package/{template → template-vite}/.cursor/rules/exxat-mono-ids.mdc +1 -0
  78. package/template-vite/.cursor/rules/exxat-nav-single-active.mdc +32 -0
  79. package/template-vite/.cursor/rules/exxat-no-image-pixel-copy.mdc +46 -0
  80. package/template-vite/.cursor/rules/exxat-no-slds-leakage.mdc +84 -0
  81. package/{template → template-vite}/.cursor/rules/exxat-no-toast.mdc +2 -2
  82. package/template-vite/.cursor/rules/exxat-no-vaul.mdc +26 -0
  83. package/template-vite/.cursor/rules/exxat-page-header-actions.mdc +33 -0
  84. package/{template → template-vite}/.cursor/rules/exxat-page-vs-drawer.mdc +5 -3
  85. package/template-vite/.cursor/rules/exxat-person-identity-display.mdc +48 -0
  86. package/template-vite/.cursor/rules/exxat-primary-nav-secondary-panel.mdc +53 -0
  87. package/template-vite/.cursor/rules/exxat-reuse-before-custom.mdc +37 -0
  88. package/template-vite/.cursor/rules/exxat-sidebar-shell.mdc +41 -0
  89. package/template-vite/.cursor/rules/exxat-table-properties-drawer.mdc +79 -0
  90. package/template-vite/.cursor/rules/exxat-table-row-preview.mdc +25 -0
  91. package/template-vite/.cursor/rules/exxat-tabs-chrome.mdc +33 -0
  92. package/template-vite/.cursor/rules/exxat-token-discipline.mdc +109 -0
  93. package/template-vite/.cursor/rules/exxat-ux-discovery-protocol.mdc +202 -0
  94. package/template-vite/.cursor/rules/exxat-ux-principles.mdc +187 -0
  95. package/template-vite/.cursor/skills/exxat-accessibility/SKILL.md +282 -0
  96. package/template-vite/.cursor/skills/exxat-board-cards/SKILL.md +68 -0
  97. package/template-vite/.cursor/skills/exxat-card-vs-list-rows/SKILL.md +20 -0
  98. package/template-vite/.cursor/skills/exxat-centralized-list-dataset/SKILL.md +99 -0
  99. package/template-vite/.cursor/skills/exxat-collaboration-access/SKILL.md +35 -0
  100. package/template-vite/.cursor/skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
  101. package/template-vite/.cursor/skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
  102. package/template-vite/.cursor/skills/exxat-ds-skill/SKILL.md +893 -0
  103. package/template-vite/.cursor/skills/exxat-ds-skill/references/accessibility.md +142 -0
  104. package/template-vite/.cursor/skills/exxat-ds-skill/references/coach-marks.md +169 -0
  105. package/template-vite/.cursor/skills/exxat-ds-skill/references/data-table-pattern.md +392 -0
  106. package/template-vite/.cursor/skills/exxat-fontawesome-icons/SKILL.md +31 -0
  107. package/template-vite/.cursor/skills/exxat-kpi-flat-band/SKILL.md +38 -0
  108. package/template-vite/.cursor/skills/exxat-kpi-max-four/SKILL.md +19 -0
  109. package/template-vite/.cursor/skills/exxat-kpi-trends/SKILL.md +29 -0
  110. package/template-vite/.cursor/skills/exxat-list-page-view-shells/SKILL.md +36 -0
  111. package/template-vite/.cursor/skills/exxat-mono-ids/SKILL.md +56 -0
  112. package/template-vite/.cursor/skills/exxat-primary-nav-secondary-panel/SKILL.md +49 -0
  113. package/template-vite/.cursor/skills/exxat-senior-ux/SKILL.md +198 -0
  114. package/template-vite/.cursor/skills/exxat-token-economy/SKILL.md +287 -0
  115. package/template-vite/.cursor/skills/exxat-ux-audit/SKILL.md +303 -0
  116. package/{template → template-vite}/components/ask-leo-sidebar.tsx +10 -8
  117. package/{template → template-vite}/components/command-menu.tsx +1 -1
  118. package/{template → template-vite}/components/data-views/library-folder-tree-branch.tsx +1 -1
  119. package/{template → template-vite}/components/dedicated-search-recents.tsx +1 -1
  120. package/{template → template-vite}/components/dedicated-search-url-composer.tsx +1 -1
  121. package/{template → template-vite}/components/library-client.tsx +1 -1
  122. package/{template → template-vite}/components/library-hub-client.tsx +2 -2
  123. package/{template → template-vite}/components/library-secondary-nav.tsx +2 -2
  124. package/{template → template-vite}/components/library-table.tsx +35 -27
  125. package/{template → template-vite}/components/new-library-item-form.tsx +1 -1
  126. package/{template → template-vite}/components/page-breadcrumb-trail.tsx +1 -1
  127. package/{template → template-vite}/components/settings-client.tsx +1 -1
  128. package/{template → template-vite}/components/sidebar/app-sidebar.tsx +2 -2
  129. package/{template → template-vite}/components/sidebar/nav-main.tsx +1 -1
  130. package/{template → template-vite}/components/sidebar/nav-user.tsx +1 -1
  131. package/{template → template-vite}/components/sidebar/secondary-nav.tsx +1 -1
  132. package/{template → template-vite}/components/system-banner-slot.tsx +1 -1
  133. package/{template → template-vite}/components/templates/discovery-hub-template.tsx +2 -2
  134. package/{template → template-vite}/components/templates/new-focus-template.tsx +1 -1
  135. package/{template → template-vite}/components/tokens-secondary-nav.tsx +2 -2
  136. package/{template → template-vite}/components/tokens-themes-client.tsx +1 -1
  137. package/{template → template-vite}/hooks/use-secondary-panel-hub-nav.ts +1 -1
  138. package/template-vite/index.html +49 -0
  139. package/template-vite/lib/next-compat.tsx +98 -0
  140. package/{template → template-vite}/package.json +15 -27
  141. package/template-vite/scripts/port-next-imports.mjs +70 -0
  142. package/template-vite/src/App.tsx +103 -0
  143. package/template-vite/src/main.tsx +50 -0
  144. package/{template/app/(app)/error.tsx → template-vite/src/pages/_error.tsx} +12 -24
  145. package/{template/app/(app)/loading.tsx → template-vite/src/pages/_loading.tsx} +4 -2
  146. package/template-vite/src/pages/_not-found.tsx +17 -0
  147. package/template-vite/src/pages/dashboard.tsx +48 -0
  148. package/{template/app/(app)/help/page.tsx → template-vite/src/pages/help.tsx} +3 -2
  149. package/{template/app/(app)/library/layout.tsx → template-vite/src/pages/library/_layout.tsx} +18 -16
  150. package/{template/app/(app)/library/all/page.tsx → template-vite/src/pages/library/all.tsx} +1 -1
  151. package/{template/app/(app)/library/new/page.tsx → template-vite/src/pages/library/new.tsx} +12 -18
  152. package/template-vite/src/routes.tsx +72 -0
  153. package/{template/app → template-vite/src/styles}/globals.css +6 -2
  154. package/{template → template-vite}/tsconfig.json +5 -14
  155. package/template-vite/vite.config.ts +52 -0
  156. package/consumer-extras/cursor-rules/exxat-dashboard-view-charts.mdc +0 -53
  157. package/template/.agents/skills/shadcn/SKILL.md +0 -242
  158. package/template/.agents/skills/shadcn/agents/openai.yml +0 -5
  159. package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  160. package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
  161. package/template/.agents/skills/shadcn/cli.md +0 -257
  162. package/template/.agents/skills/shadcn/customization.md +0 -202
  163. package/template/.agents/skills/shadcn/evals/evals.json +0 -47
  164. package/template/.agents/skills/shadcn/mcp.md +0 -94
  165. package/template/.agents/skills/shadcn/rules/base-vs-radix.md +0 -306
  166. package/template/.agents/skills/shadcn/rules/composition.md +0 -195
  167. package/template/.agents/skills/shadcn/rules/forms.md +0 -192
  168. package/template/.agents/skills/shadcn/rules/icons.md +0 -101
  169. package/template/.agents/skills/shadcn/rules/styling.md +0 -162
  170. package/template/.cursor/rules/exxat-accessibility.mdc +0 -33
  171. package/template/.cursor/rules/exxat-data-tables.mdc +0 -32
  172. package/template/.cursor/rules/exxat-ds-agents.mdc +0 -26
  173. package/template/.cursor/rules/exxat-list-page-connected-views.mdc +0 -16
  174. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +0 -40
  175. package/template/.nvmrc +0 -1
  176. package/template/.prettierignore +0 -7
  177. package/template/Logo/Exxat_Prism.svg +0 -39
  178. package/template/Logo/Exxat_one.svg +0 -36
  179. package/template/app/(app)/dashboard/loading.tsx +0 -18
  180. package/template/app/(app)/dashboard/page.tsx +0 -36
  181. package/template/app/(app)/layout.tsx +0 -77
  182. package/template/app/global-error.tsx +0 -63
  183. package/template/app/layout.tsx +0 -133
  184. package/template/app/page.tsx +0 -9
  185. package/template/docs/HANDBOOK.md +0 -187
  186. package/template/docs/blueprints/README.md +0 -86
  187. package/template/docs/blueprints/_template.md +0 -91
  188. package/template/docs/blueprints/board-card.md +0 -123
  189. package/template/docs/blueprints/data-table.md +0 -139
  190. package/template/docs/blueprints/key-metrics.md +0 -128
  191. package/template/docs/blueprints/list-page-template.md +0 -123
  192. package/template/docs/blueprints/page-header.md +0 -130
  193. package/template/docs/card-vs-rows-pattern.md +0 -36
  194. package/template/docs/collaboration-access-pattern.md +0 -116
  195. package/template/docs/command-menu-pattern.md +0 -45
  196. package/template/docs/component-selection-guide.md +0 -224
  197. package/template/docs/components-audit-2026-05.md +0 -158
  198. package/template/docs/consumer-upgrade-checklist.md +0 -52
  199. package/template/docs/data-views-pattern.md +0 -185
  200. package/template/docs/drawer-vs-dialog-pattern.md +0 -50
  201. package/template/docs/glossary.md +0 -59
  202. package/template/docs/hub-supported-views-pattern.md +0 -53
  203. package/template/docs/jobs/README.md +0 -59
  204. package/template/docs/jobs/record-detail.md +0 -177
  205. package/template/docs/kpi-flat-band-pattern.md +0 -57
  206. package/template/docs/kpi-strip-max-four-pattern.md +0 -30
  207. package/template/docs/kpi-trend-pattern.md +0 -58
  208. package/template/docs/large-dataset-strategy.md +0 -155
  209. package/template/docs/library-hub-header-pattern.md +0 -25
  210. package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +0 -95
  211. package/template/docs/migrations/0002-exxat-token-namespace.md +0 -154
  212. package/template/docs/migrations/0003-globals-css-canonical.md +0 -110
  213. package/template/docs/migrations/README.md +0 -100
  214. package/template/docs/migrations/_template.md +0 -64
  215. package/template/docs/modern-saas-patterns.md +0 -165
  216. package/template/docs/perf-memory-pattern.md +0 -206
  217. package/template/docs/reference-implementations.md +0 -153
  218. package/template/docs/shell-surface-elevation-pattern.md +0 -52
  219. package/template/docs/token-taxonomy.md +0 -416
  220. package/template/docs/voice-and-tone.md +0 -262
  221. package/template/ecosystem.config.cjs +0 -32
  222. package/template/next.config.mjs +0 -216
  223. package/template/postcss.config.mjs +0 -8
  224. package/template/public/favicon/favicon.ico +0 -0
  225. package/template/tests/setup.ts +0 -26
  226. package/template/vitest.config.ts +0 -18
  227. /package/{template → template-vite}/.cursor/rules/exxat-dashboard-view-charts.mdc +0 -0
  228. /package/{template → template-vite}/.prettierrc +0 -0
  229. /package/{template → template-vite}/AGENTS.md +0 -0
  230. /package/{template → template-vite}/README.md +0 -0
  231. /package/{template → template-vite}/components/.gitkeep +0 -0
  232. /package/{template → template-vite}/components/ask-leo-composer.tsx +0 -0
  233. /package/{template → template-vite}/components/brand-color-picker.tsx +0 -0
  234. /package/{template → template-vite}/components/chart-area-interactive.tsx +0 -0
  235. /package/{template → template-vite}/components/charts-overview.tsx +0 -0
  236. /package/{template → template-vite}/components/collaboration-access-flow.tsx +0 -0
  237. /package/{template → template-vite}/components/columns-client.tsx +0 -0
  238. /package/{template → template-vite}/components/columns-showcase.tsx +0 -0
  239. /package/{template → template-vite}/components/dashboard-promo-banner.tsx +0 -0
  240. /package/{template → template-vite}/components/dashboard-quota-progress-card.tsx +0 -0
  241. /package/{template → template-vite}/components/dashboard-report-charts.tsx +0 -0
  242. /package/{template → template-vite}/components/dashboard-section-heading.tsx +0 -0
  243. /package/{template → template-vite}/components/dashboard-tabs.tsx +0 -0
  244. /package/{template → template-vite}/components/data-table/filter-date-calendar.tsx +0 -0
  245. /package/{template → template-vite}/components/data-table/filter-text-value-input.tsx +0 -0
  246. /package/{template → template-vite}/components/data-table/index.tsx +0 -0
  247. /package/{template → template-vite}/components/data-table/pagination.tsx +0 -0
  248. /package/{template → template-vite}/components/data-table/types.ts +0 -0
  249. /package/{template → template-vite}/components/data-table/use-table-state.test.ts +0 -0
  250. /package/{template → template-vite}/components/data-table/use-table-state.ts +0 -0
  251. /package/{template → template-vite}/components/data-views/board-card-primitives.tsx +0 -0
  252. /package/{template → template-vite}/components/data-views/data-row-list.tsx +0 -0
  253. /package/{template → template-vite}/components/data-views/finder-panel-view.tsx +0 -0
  254. /package/{template → template-vite}/components/data-views/folder-grid-view.tsx +0 -0
  255. /package/{template → template-vite}/components/data-views/hub-table.tsx +0 -0
  256. /package/{template → template-vite}/components/data-views/index.ts +0 -0
  257. /package/{template → template-vite}/components/data-views/list-page-board-card.tsx +0 -0
  258. /package/{template → template-vite}/components/data-views/list-page-board-template.tsx +0 -0
  259. /package/{template → template-vite}/components/data-views/list-page-connected-view-body.tsx +0 -0
  260. /package/{template → template-vite}/components/data-views/list-page-split-details-placeholder.tsx +0 -0
  261. /package/{template → template-vite}/components/data-views/list-page-split-hub-chrome.tsx +0 -0
  262. /package/{template → template-vite}/components/data-views/list-page-split-hub-tokens.ts +0 -0
  263. /package/{template → template-vite}/components/data-views/list-page-tree-column-header.tsx +0 -0
  264. /package/{template → template-vite}/components/data-views/list-page-tree-panel-shell.tsx +0 -0
  265. /package/{template → template-vite}/components/data-views/list-page-view-frame.tsx +0 -0
  266. /package/{template → template-vite}/components/data-views/os-folder-glyph.tsx +0 -0
  267. /package/{template → template-vite}/components/data-views/outline-tree-menu.tsx +0 -0
  268. /package/{template → template-vite}/components/data-views/table-cells.tsx +0 -0
  269. /package/{template → template-vite}/components/dev-chunk-load-recovery.tsx +0 -0
  270. /package/{template → template-vite}/components/export-drawer.test.tsx +0 -0
  271. /package/{template → template-vite}/components/export-drawer.tsx +0 -0
  272. /package/{template → template-vite}/components/exxat-product-logo.tsx +0 -0
  273. /package/{template → template-vite}/components/folder-details-shell.tsx +0 -0
  274. /package/{template → template-vite}/components/form-layout-01.tsx +0 -0
  275. /package/{template → template-vite}/components/hub-tree-panel-view.tsx +0 -0
  276. /package/{template → template-vite}/components/invite-collaborators-drawer.tsx +0 -0
  277. /package/{template → template-vite}/components/key-metrics-ask-leo-bridge.tsx +0 -0
  278. /package/{template → template-vite}/components/key-metrics.tsx +0 -0
  279. /package/{template → template-vite}/components/leo-insight-indicator.tsx +0 -0
  280. /package/{template → template-vite}/components/leo-typing-dots.tsx +0 -0
  281. /package/{template → template-vite}/components/library-board-view.tsx +0 -0
  282. /package/{template → template-vite}/components/library-dashboard-charts.tsx +0 -0
  283. /package/{template → template-vite}/components/library-favorite-button.tsx +0 -0
  284. /package/{template → template-vite}/components/library-new-folder-sheet.tsx +0 -0
  285. /package/{template → template-vite}/components/library-os-folder-view.tsx +0 -0
  286. /package/{template → template-vite}/components/library-page-header.tsx +0 -0
  287. /package/{template → template-vite}/components/library-panel-activator.tsx +0 -0
  288. /package/{template → template-vite}/components/list-hub-status-badge.tsx +0 -0
  289. /package/{template → template-vite}/components/list-page-dashboard-charts.tsx +0 -0
  290. /package/{template → template-vite}/components/onboarding/getting-started.tsx +0 -0
  291. /package/{template → template-vite}/components/onboarding/index.ts +0 -0
  292. /package/{template → template-vite}/components/onboarding/onboarding-01.tsx +0 -0
  293. /package/{template → template-vite}/components/onboarding/onboarding-02.tsx +0 -0
  294. /package/{template → template-vite}/components/onboarding/onboarding-03.tsx +0 -0
  295. /package/{template → template-vite}/components/onboarding/onboarding-04.tsx +0 -0
  296. /package/{template → template-vite}/components/page-header.tsx +0 -0
  297. /package/{template → template-vite}/components/product-switcher.tsx +0 -0
  298. /package/{template → template-vite}/components/product-wordmark.tsx +0 -0
  299. /package/{template → template-vite}/components/settings-appearance-card.tsx +0 -0
  300. /package/{template → template-vite}/components/settings-form-row.tsx +0 -0
  301. /package/{template → template-vite}/components/sidebar/app-sidebar-dynamic.tsx +0 -0
  302. /package/{template → template-vite}/components/sidebar/index.ts +0 -0
  303. /package/{template → template-vite}/components/sidebar/nav-documents.tsx +0 -0
  304. /package/{template → template-vite}/components/sidebar/nav-secondary.tsx +0 -0
  305. /package/{template → template-vite}/components/sidebar/secondary-panel.tsx +0 -0
  306. /package/{template → template-vite}/components/sidebar/sidebar-auto-collapse.tsx +0 -0
  307. /package/{template → template-vite}/components/sidebar/sidebar-auto-open.tsx +0 -0
  308. /package/{template → template-vite}/components/sidebar/sidebar-shell.tsx +0 -0
  309. /package/{template → template-vite}/components/site-header.tsx +0 -0
  310. /package/{template → template-vite}/components/table-properties/column-row.tsx +0 -0
  311. /package/{template → template-vite}/components/table-properties/draggable-list.ts +0 -0
  312. /package/{template → template-vite}/components/table-properties/drawer-button.tsx +0 -0
  313. /package/{template → template-vite}/components/table-properties/drawer.tsx +0 -0
  314. /package/{template → template-vite}/components/table-properties/filter-card.tsx +0 -0
  315. /package/{template → template-vite}/components/table-properties/index.ts +0 -0
  316. /package/{template → template-vite}/components/table-properties/sort-card.tsx +0 -0
  317. /package/{template → template-vite}/components/table-properties/types.ts +0 -0
  318. /package/{template → template-vite}/components/task-list-panel.tsx +0 -0
  319. /package/{template → template-vite}/components/task-priority-badge.tsx +0 -0
  320. /package/{template → template-vite}/components/templates/dedicated-search-landing-template.tsx +0 -0
  321. /package/{template → template-vite}/components/templates/dedicated-search-results-template.tsx +0 -0
  322. /package/{template → template-vite}/components/templates/list-page.tsx +0 -0
  323. /package/{template → template-vite}/components/templates/nested-secondary-panel-shell.tsx +0 -0
  324. /package/{template → template-vite}/components/templates/primary-page-template.tsx +0 -0
  325. /package/{template → template-vite}/components/templates/secondary-panel-hub-template.tsx +0 -0
  326. /package/{template → template-vite}/components/theme-color-sync.tsx +0 -0
  327. /package/{template → template-vite}/components/theme-provider.tsx +0 -0
  328. /package/{template → template-vite}/components/tinted-icon-disc.tsx +0 -0
  329. /package/{template → template-vite}/components/tokens-hub-auxiliary-views.tsx +0 -0
  330. /package/{template → template-vite}/components/tokens-themes-section.tsx +0 -0
  331. /package/{template → template-vite}/components/ui/accordion.tsx +0 -0
  332. /package/{template → template-vite}/components/ui/ai-thinking-surface.tsx +0 -0
  333. /package/{template → template-vite}/components/ui/alert-dialog.tsx +0 -0
  334. /package/{template → template-vite}/components/ui/avatar.tsx +0 -0
  335. /package/{template → template-vite}/components/ui/badge.tsx +0 -0
  336. /package/{template → template-vite}/components/ui/banner.tsx +0 -0
  337. /package/{template → template-vite}/components/ui/breadcrumb.tsx +0 -0
  338. /package/{template → template-vite}/components/ui/button.tsx +0 -0
  339. /package/{template → template-vite}/components/ui/calendar.tsx +0 -0
  340. /package/{template → template-vite}/components/ui/card.tsx +0 -0
  341. /package/{template → template-vite}/components/ui/chart.tsx +0 -0
  342. /package/{template → template-vite}/components/ui/checkbox.tsx +0 -0
  343. /package/{template → template-vite}/components/ui/coach-mark.tsx +0 -0
  344. /package/{template → template-vite}/components/ui/collapsible.tsx +0 -0
  345. /package/{template → template-vite}/components/ui/command.tsx +0 -0
  346. /package/{template → template-vite}/components/ui/context-menu.tsx +0 -0
  347. /package/{template → template-vite}/components/ui/date-picker-field.tsx +0 -0
  348. /package/{template → template-vite}/components/ui/dialog.tsx +0 -0
  349. /package/{template → template-vite}/components/ui/dot-pattern.tsx +0 -0
  350. /package/{template → template-vite}/components/ui/drag-handle-grip.tsx +0 -0
  351. /package/{template → template-vite}/components/ui/dropdown-menu.tsx +0 -0
  352. /package/{template → template-vite}/components/ui/field.tsx +0 -0
  353. /package/{template → template-vite}/components/ui/form.tsx +0 -0
  354. /package/{template → template-vite}/components/ui/hover-card.tsx +0 -0
  355. /package/{template → template-vite}/components/ui/input-group.tsx +0 -0
  356. /package/{template → template-vite}/components/ui/input-mask.tsx +0 -0
  357. /package/{template → template-vite}/components/ui/input.tsx +0 -0
  358. /package/{template → template-vite}/components/ui/kbd.tsx +0 -0
  359. /package/{template → template-vite}/components/ui/label.tsx +0 -0
  360. /package/{template → template-vite}/components/ui/leo-icon.tsx +0 -0
  361. /package/{template → template-vite}/components/ui/payment-card-fields.tsx +0 -0
  362. /package/{template → template-vite}/components/ui/popover.tsx +0 -0
  363. /package/{template → template-vite}/components/ui/radio-group.tsx +0 -0
  364. /package/{template → template-vite}/components/ui/resizable.tsx +0 -0
  365. /package/{template → template-vite}/components/ui/scroll-area.tsx +0 -0
  366. /package/{template → template-vite}/components/ui/select.tsx +0 -0
  367. /package/{template → template-vite}/components/ui/selection-tile-grid.tsx +0 -0
  368. /package/{template → template-vite}/components/ui/separator.tsx +0 -0
  369. /package/{template → template-vite}/components/ui/sheet.tsx +0 -0
  370. /package/{template → template-vite}/components/ui/sidebar.tsx +0 -0
  371. /package/{template → template-vite}/components/ui/skeleton.tsx +0 -0
  372. /package/{template → template-vite}/components/ui/slider.tsx +0 -0
  373. /package/{template → template-vite}/components/ui/sonner.tsx +0 -0
  374. /package/{template → template-vite}/components/ui/status-badge.tsx +0 -0
  375. /package/{template → template-vite}/components/ui/table.tsx +0 -0
  376. /package/{template → template-vite}/components/ui/tabs.tsx +0 -0
  377. /package/{template → template-vite}/components/ui/textarea.tsx +0 -0
  378. /package/{template → template-vite}/components/ui/tip.tsx +0 -0
  379. /package/{template → template-vite}/components/ui/toggle-group.tsx +0 -0
  380. /package/{template → template-vite}/components/ui/toggle-switch.tsx +0 -0
  381. /package/{template → template-vite}/components/ui/toggle.tsx +0 -0
  382. /package/{template → template-vite}/components/ui/tooltip.tsx +0 -0
  383. /package/{template → template-vite}/components/ui/view-segmented-control.tsx +0 -0
  384. /package/{template → template-vite}/components.json +0 -0
  385. /package/{template → template-vite}/contexts/chart-variant-context.tsx +0 -0
  386. /package/{template → template-vite}/contexts/command-menu-context.tsx +0 -0
  387. /package/{template → template-vite}/contexts/dashboard-view-context.tsx +0 -0
  388. /package/{template → template-vite}/contexts/product-context.tsx +0 -0
  389. /package/{template → template-vite}/contexts/system-banner-context.tsx +0 -0
  390. /package/{template → template-vite}/eslint.config.mjs +0 -0
  391. /package/{template → template-vite}/fontawesome-subset.manifest.json +0 -0
  392. /package/{template → template-vite}/hooks/.gitkeep +0 -0
  393. /package/{template → template-vite}/hooks/use-app-theme.ts +0 -0
  394. /package/{template → template-vite}/hooks/use-coach-mark.ts +0 -0
  395. /package/{template → template-vite}/hooks/use-location-hash.ts +0 -0
  396. /package/{template → template-vite}/hooks/use-mobile.ts +0 -0
  397. /package/{template → template-vite}/hooks/use-mod-key-label.ts +0 -0
  398. /package/{template → template-vite}/hooks/use-sidebar-reflow-zoom.ts +0 -0
  399. /package/{template → template-vite}/lib/.gitkeep +0 -0
  400. /package/{template → template-vite}/lib/ask-leo-route-context.ts +0 -0
  401. /package/{template → template-vite}/lib/chart-keyboard-selection.test.ts +0 -0
  402. /package/{template → template-vite}/lib/chart-keyboard-selection.ts +0 -0
  403. /package/{template → template-vite}/lib/chart-line-dash.ts +0 -0
  404. /package/{template → template-vite}/lib/chunk-load-error.ts +0 -0
  405. /package/{template → template-vite}/lib/coach-mark-registry.ts +0 -0
  406. /package/{template → template-vite}/lib/collaborator-access.ts +0 -0
  407. /package/{template → template-vite}/lib/command-menu-config.ts +0 -0
  408. /package/{template → template-vite}/lib/command-menu-search-data.ts +0 -0
  409. /package/{template → template-vite}/lib/conditional-rule-match.ts +0 -0
  410. /package/{template → template-vite}/lib/dashboard-customize-coach-mark.ts +0 -0
  411. /package/{template → template-vite}/lib/dashboard-layout-merge.ts +0 -0
  412. /package/{template → template-vite}/lib/data-list-display-options.ts +0 -0
  413. /package/{template → template-vite}/lib/data-list-persistence.ts +0 -0
  414. /package/{template → template-vite}/lib/data-list-view-registry.ts +0 -0
  415. /package/{template → template-vite}/lib/data-list-view-surface.ts +0 -0
  416. /package/{template → template-vite}/lib/data-list-view.ts +0 -0
  417. /package/{template → template-vite}/lib/data-view-dashboard-storage.ts +0 -0
  418. /package/{template → template-vite}/lib/date-filter.ts +0 -0
  419. /package/{template → template-vite}/lib/dedicated-search-recents.ts +0 -0
  420. /package/{template → template-vite}/lib/dedicated-search-url.ts +0 -0
  421. /package/{template → template-vite}/lib/dev-log.test.ts +0 -0
  422. /package/{template → template-vite}/lib/dev-log.ts +0 -0
  423. /package/{template → template-vite}/lib/discovery-hub.ts +0 -0
  424. /package/{template → template-vite}/lib/editable-target.ts +0 -0
  425. /package/{template → template-vite}/lib/exxat-palette.json +0 -0
  426. /package/{template → template-vite}/lib/exxat-palette.ts +0 -0
  427. /package/{template → template-vite}/lib/floating-sheet-panel.ts +0 -0
  428. /package/{template → template-vite}/lib/full-hub-supported-views.ts +0 -0
  429. /package/{template → template-vite}/lib/hub-connected-view-renderers.ts +0 -0
  430. /package/{template → template-vite}/lib/initials-from-name.ts +0 -0
  431. /package/{template → template-vite}/lib/library-authoring.ts +0 -0
  432. /package/{template → template-vite}/lib/library-dedicated-search.ts +0 -0
  433. /package/{template → template-vite}/lib/library-hub-search.ts +0 -0
  434. /package/{template → template-vite}/lib/library-nav.ts +0 -0
  435. /package/{template → template-vite}/lib/library-recent-searches.ts +0 -0
  436. /package/{template → template-vite}/lib/library-supported-views.ts +0 -0
  437. /package/{template → template-vite}/lib/list-hub-supported-views.ts +0 -0
  438. /package/{template → template-vite}/lib/list-page-table-properties.ts +0 -0
  439. /package/{template → template-vite}/lib/list-status-badges.ts +0 -0
  440. /package/{template → template-vite}/lib/logo-dev.ts +0 -0
  441. /package/{template → template-vite}/lib/mailto.ts +0 -0
  442. /package/{template → template-vite}/lib/mock/dashboard.ts +0 -0
  443. /package/{template → template-vite}/lib/mock/library-folders.ts +0 -0
  444. /package/{template → template-vite}/lib/mock/library-header-collaborators.ts +0 -0
  445. /package/{template → template-vite}/lib/mock/library-inspector.ts +0 -0
  446. /package/{template → template-vite}/lib/mock/library-kpi.ts +0 -0
  447. /package/{template → template-vite}/lib/mock/library.ts +0 -0
  448. /package/{template → template-vite}/lib/mock/navigation.tsx +0 -0
  449. /package/{template → template-vite}/lib/motion-ui.ts +0 -0
  450. /package/{template → template-vite}/lib/product-brand.ts +0 -0
  451. /package/{template → template-vite}/lib/raf-throttle.ts +0 -0
  452. /package/{template → template-vite}/lib/row-height.ts +0 -0
  453. /package/{template → template-vite}/lib/sidebar-state-cookie.ts +0 -0
  454. /package/{template → template-vite}/lib/stock-portrait.ts +0 -0
  455. /package/{template → template-vite}/lib/table-state-lifecycle.ts +0 -0
  456. /package/{template → template-vite}/lib/utils.test.ts +0 -0
  457. /package/{template → template-vite}/lib/utils.ts +0 -0
  458. /package/{template → template-vite}/public/.gitkeep +0 -0
  459. /package/{template → template-vite}/public/Illustration/Rotation.svg +0 -0
  460. /package/{template → template-vite}/public/avatars/user.svg +0 -0
  461. /package/{template/public → template-vite/public/favicon}/favicon.ico +0 -0
  462. /package/{template/app → template-vite/public}/favicon.ico +0 -0
  463. /package/{template → template-vite}/public/folders/icons8-folder-windows-11.svg +0 -0
  464. /package/{template → template-vite}/public/logos/exxat-one.svg +0 -0
  465. /package/{template → template-vite}/public/logos/exxat-prism.svg +0 -0
  466. /package/{template → template-vite}/public/mock-schools/emory.svg +0 -0
  467. /package/{template → template-vite}/public/mock-schools/rush.svg +0 -0
  468. /package/{template → template-vite}/scripts/fontawesome-subset-audit.mjs +0 -0
  469. /package/{template → template-vite}/scripts/pm2-startup-macos.sh +0 -0
  470. /package/{template → template-vite}/skills-lock.json +0 -0
  471. /package/{template/app/(app)/columns/page.tsx → template-vite/src/pages/columns.tsx} +0 -0
  472. /package/{template/app/(app)/library/find/page.tsx → template-vite/src/pages/library/find.tsx} +0 -0
  473. /package/{template/app/(app)/library/page.tsx → template-vite/src/pages/library/index.tsx} +0 -0
  474. /package/{template/app/(app)/library/list/page.tsx → template-vite/src/pages/library/list.tsx} +0 -0
  475. /package/{template/app/(app)/settings/page.tsx → template-vite/src/pages/settings.tsx} +0 -0
  476. /package/{template/app/(app)/tokens-themes/page.tsx → template-vite/src/pages/tokens-themes.tsx} +0 -0
  477. /package/{template → template-vite}/stores/app-store.ts +0 -0
  478. /package/{template → template-vite}/types/react-payment-inputs.d.ts +0 -0
@@ -0,0 +1,392 @@
1
+ # Data Table Pattern — Full Implementation Guide
2
+
3
+ **Primary hub stack:** `ListPageTemplate` → **`HubTable`** (not raw `DataTable` inside hubs).
4
+
5
+ **Add view parity:** `FULL_HUB_SUPPORTED_VIEWS` on **`ListPageTemplate`** + **`HubTable`**; implement every allowed view — **`.cursor/rules/exxat-hub-supported-views.mdc`**, **`apps/web/docs/hub-supported-views-pattern.md`**.
6
+
7
+ **Reference implementations:**
8
+ - `components/library-table.tsx` + `library-client.tsx` — canonical seven-view hub
9
+ - `components/columns-showcase.tsx` — `LibraryTable` + custom `columnDefs`
10
+ - `components/tokens-themes-client.tsx` + `tokens-hub-auxiliary-views.tsx`
11
+ - `components/team-table.tsx`, `components/placements-table.tsx` — entity hubs
12
+
13
+ ---
14
+
15
+ ## Stack Summary
16
+
17
+ ```
18
+ HubTable (inside ListPageTemplate) ← canonical hub wrapper
19
+ └── DataTable ← base table component
20
+ └── useTableState ← sort/filter/column/group state
21
+ └── toolbarSlot ← properties button + filter chips + search
22
+ └── TablePropertiesDrawer
23
+ └── renderListRow / renderers ← list, board, dashboard, folder, panel, tree
24
+ ```
25
+
26
+ All imports:
27
+ ```ts
28
+ import { DataTable } from "@/components/data-table"
29
+ import type { ColumnDef } from "@/components/data-table/types"
30
+ import { useTableState } from "@/components/data-table/use-table-state"
31
+ import { TablePropertiesDrawer } from "@/components/table-properties"
32
+ import type {
33
+ ConditionalRule,
34
+ FilterFieldDef,
35
+ FilterOperator,
36
+ } from "@/components/table-properties/types"
37
+ import {
38
+ DEFAULT_DATA_LIST_DISPLAY_OPTIONS,
39
+ type DataListDisplayOptions,
40
+ } from "@/lib/data-list-display-options"
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Column Definition Patterns
46
+
47
+ ### Text column with filter
48
+ ```ts
49
+ {
50
+ key: "name",
51
+ label: "Name",
52
+ width: 240,
53
+ minWidth: 160,
54
+ sortable: true,
55
+ sortKey: "name",
56
+ defaultPin: "left", // pin to left (optional)
57
+ filter: {
58
+ type: "text",
59
+ icon: "fa-user",
60
+ operators: ["contains", "not_contains"],
61
+ },
62
+ cell: row => (
63
+ <span className="text-sm font-medium text-foreground truncate">{row.name}</span>
64
+ ),
65
+ }
66
+ ```
67
+
68
+ ### Select (enum) column with filter
69
+ ```ts
70
+ {
71
+ key: "status",
72
+ label: "Status",
73
+ width: 120,
74
+ minWidth: 100,
75
+ sortable: true,
76
+ sortKey: "status",
77
+ filter: {
78
+ type: "select",
79
+ icon: "fa-circle-dot",
80
+ operators: ["is", "is_not"],
81
+ options: [
82
+ { value: "active", label: "Active" },
83
+ { value: "inactive", label: "Inactive" },
84
+ ],
85
+ },
86
+ cell: row => (
87
+ <Badge variant="outline" className={cn("text-[10px] font-medium uppercase tracking-wide", STATUS_BADGE[row.status])}>
88
+ {STATUS_LABEL[row.status]}
89
+ </Badge>
90
+ ),
91
+ }
92
+ ```
93
+
94
+ ### Select column (pinned right, no filter)
95
+ ```ts
96
+ {
97
+ key: "select",
98
+ label: "",
99
+ width: 40,
100
+ minWidth: 40,
101
+ defaultPin: "left",
102
+ lockPin: true,
103
+ }
104
+ ```
105
+
106
+ ### Actions column (pinned right)
107
+ ```ts
108
+ {
109
+ key: "actions",
110
+ label: "",
111
+ width: 48,
112
+ minWidth: 48,
113
+ defaultPin: "right",
114
+ lockPin: true,
115
+ cell: row => (
116
+ <div className="flex items-center justify-center">
117
+ <DropdownMenu>
118
+ <DropdownMenuTrigger asChild>
119
+ <Button size="icon-sm" variant="ghost" aria-label={`Actions for ${row.name}`}>
120
+ <i className="fa-light fa-ellipsis text-sm" aria-hidden="true" />
121
+ </Button>
122
+ </DropdownMenuTrigger>
123
+ <DropdownMenuContent align="end" className="w-40">
124
+ <DropdownMenuItem disabled>
125
+ <i className="fa-light fa-pen" aria-hidden="true" />
126
+ Edit
127
+ </DropdownMenuItem>
128
+ </DropdownMenuContent>
129
+ </DropdownMenu>
130
+ </div>
131
+ ),
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Status Badge Pattern
138
+
139
+ ```ts
140
+ const STATUS_LABEL: Record<MyType["status"], string> = {
141
+ active: "Active",
142
+ inactive: "Inactive",
143
+ pending: "Pending",
144
+ }
145
+
146
+ const STATUS_BADGE: Record<MyType["status"], string> = {
147
+ active: "bg-emerald-500/15 text-emerald-800 dark:text-emerald-200 border-emerald-500/20",
148
+ inactive: "bg-slate-500/10 text-slate-700 dark:text-slate-200 border-border",
149
+ pending: "bg-amber-500/15 text-amber-900 dark:text-amber-100 border-amber-500/20",
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Filter Field Derivation
156
+
157
+ Convert column defs to filter field defs for the drawer:
158
+
159
+ ```ts
160
+ function columnToFilterFieldDef(c: ColumnDef<T>): FilterFieldDef | null {
161
+ if (!c.filter) return null
162
+ const f = c.filter
163
+ const defaultOps: FilterOperator[] =
164
+ f.type === "select" || f.type === "date" ? ["is", "is_not"] : ["contains", "not_contains"]
165
+ return {
166
+ key: c.key,
167
+ label: c.label,
168
+ icon: f.icon ?? "fa-filter",
169
+ type: f.type,
170
+ operators: (f.operators ?? defaultOps) as FilterOperator[],
171
+ options: f.options,
172
+ }
173
+ }
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Drawer Toolbar Component
179
+
180
+ The `toolbarSlot` renders the Properties button and the drawer. Full pattern:
181
+
182
+ ```tsx
183
+ function FooDrawerToolbar({ state, totalRows, filterFields, fieldDefinitionsForDrawer, resolveColumnLabel, displayOptions, onDisplayOptionsChange, conditionalRules, onAddConditionalRule, onRemoveConditionalRule, onUpdateConditionalRule }) {
184
+ const { sheetOpen, setSheetOpen, showGridlines, setShowGridlines, rowHeight, setRowHeight,
185
+ activeFilters, addFilter, updateFilter, removeFilter, getConnector, toggleConnector,
186
+ filterBarVisible, setFilterBarVisible, drawerExpandedFilters, setDrawerExpandedFilters,
187
+ rows, sortRules, setSortRules, addSortRule, removeSortRule, toggleSortDir,
188
+ colOrder, setColOrder, hiddenCols, toggleColVisibility, moveCol, groupBy, setGroupBy, sortKey,
189
+ } = state
190
+
191
+ return (
192
+ <>
193
+ <TooltipProvider>
194
+ <Tooltip>
195
+ <TooltipTrigger asChild>
196
+ <Button
197
+ type="button" variant="ghost" size="icon-sm" aria-label="Properties"
198
+ onClick={() => setSheetOpen(true)}
199
+ className={cn(sheetOpen ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover")}
200
+ >
201
+ <i className="fa-light fa-sliders text-[13px]" aria-hidden="true" />
202
+ </Button>
203
+ </TooltipTrigger>
204
+ <TooltipContent side="bottom">Properties</TooltipContent>
205
+ </Tooltip>
206
+ </TooltipProvider>
207
+
208
+ <TablePropertiesDrawer
209
+ open={sheetOpen} onOpenChange={setSheetOpen}
210
+ showGridlines={showGridlines} onShowGridlinesChange={setShowGridlines}
211
+ rowHeight={rowHeight} onRowHeightChange={setRowHeight}
212
+ pagination={false} onPaginationChange={() => {}}
213
+ activeFilters={activeFilters} onAddFilter={k => addFilter(k, true)}
214
+ onUpdateFilter={updateFilter} onRemoveFilter={removeFilter}
215
+ getFilterConnector={getConnector} onToggleFilterConnector={toggleConnector}
216
+ filterBarVisible={filterBarVisible} onFilterBarVisibleChange={setFilterBarVisible}
217
+ drawerExpandedFilters={drawerExpandedFilters} onDrawerExpandedFiltersChange={setDrawerExpandedFilters}
218
+ totalRows={totalRows} filteredRows={rows.length}
219
+ sortRules={sortRules} onSortRulesChange={setSortRules}
220
+ onAddSortRule={addSortRule} onRemoveSortRule={removeSortRule} onToggleSortDir={toggleSortDir}
221
+ colOrder={colOrder} onColOrderChange={setColOrder}
222
+ hiddenCols={hiddenCols} onToggleColVisibility={toggleColVisibility} onMoveCol={moveCol}
223
+ groupBy={groupBy} onGroupByChange={setGroupBy} primarySortKey={sortKey}
224
+ conditionalRules={conditionalRules}
225
+ onAddConditionalRule={onAddConditionalRule}
226
+ onRemoveConditionalRule={onRemoveConditionalRule}
227
+ onUpdateConditionalRule={onUpdateConditionalRule}
228
+ filterFields={filterFields}
229
+ lifecycleTabLabel="Foo" // ← change to entity name
230
+ fieldDefinitions={fieldDefinitionsForDrawer}
231
+ resolveColumnLabel={resolveColumnLabel}
232
+ displayOptions={displayOptions}
233
+ onDisplayOptionsChange={onDisplayOptionsChange}
234
+ />
235
+ </>
236
+ )
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Full Table Component Skeleton
243
+
244
+ ```tsx
245
+ "use client"
246
+
247
+ import * as React from "react"
248
+ // ... all imports above
249
+
250
+ export interface FooTableHandle {
251
+ openPropertiesDrawer: () => void
252
+ }
253
+
254
+ export const FooTable = React.forwardRef<FooTableHandle, { items: Foo[] }>(
255
+ function FooTable({ items }, ref) {
256
+ const columns = React.useMemo(() => buildFooColumns(), [])
257
+ const filterFields = React.useMemo(() => columnsToFilterFields(columns), [columns])
258
+ const fieldDefinitionsForDrawer = React.useMemo(
259
+ () => columns.filter(c => c.key !== "select" && c.key !== "actions")
260
+ .map(c => ({ key: c.key, label: c.label, sortable: !!(c.sortable && (c.sortKey ?? c.key)) })),
261
+ [columns],
262
+ )
263
+ const resolveColumnLabel = React.useCallback((key: string) => columns.find(c => c.key === key)?.label ?? key, [columns])
264
+
265
+ const [displayOptions, setDisplayOptions] = React.useState<DataListDisplayOptions>(DEFAULT_DATA_LIST_DISPLAY_OPTIONS)
266
+ const patchDisplay = React.useCallback((patch: Partial<DataListDisplayOptions>) => setDisplayOptions(prev => ({ ...prev, ...patch })), [])
267
+
268
+ const [conditionalRules, setConditionalRules] = React.useState<ConditionalRule[]>([])
269
+ const addConditionalRule = React.useCallback((rule: Omit<ConditionalRule, "id">) => setConditionalRules(prev => [...prev, { ...rule, id: `cr-${Date.now()}` }]), [])
270
+ const removeConditionalRule = React.useCallback((id: string) => setConditionalRules(prev => prev.filter(r => r.id !== id)), [])
271
+ const updateConditionalRule = React.useCallback((id: string, patch: Partial<ConditionalRule>) => setConditionalRules(prev => prev.map(r => r.id === id ? { ...r, ...patch } : r)), [])
272
+
273
+ const tableState = useTableState(items, columns, { key: "name", dir: "asc" })
274
+
275
+ React.useImperativeHandle(ref, () => ({
276
+ openPropertiesDrawer: () => tableState.setSheetOpen(true),
277
+ }), [tableState.setSheetOpen])
278
+
279
+ return (
280
+ <div className="pb-6">
281
+ <DataTable<Foo>
282
+ data={items}
283
+ columns={columns}
284
+ getRowId={row => row.id}
285
+ getRowSelectionLabel={row => row.name}
286
+ selectable
287
+ searchable={displayOptions.showToolbarSearch}
288
+ showColumnHeaders={displayOptions.showColumnLabels}
289
+ groupable
290
+ defaultSort={{ key: "name", dir: "asc" as const }}
291
+ emptyState={<p className="text-sm text-muted-foreground">No items found.</p>}
292
+ conditionalRules={conditionalRules}
293
+ state={tableState}
294
+ toolbarSlot={s => (
295
+ <FooDrawerToolbar
296
+ state={s} totalRows={items.length} filterFields={filterFields}
297
+ fieldDefinitionsForDrawer={fieldDefinitionsForDrawer} resolveColumnLabel={resolveColumnLabel}
298
+ displayOptions={displayOptions} onDisplayOptionsChange={patchDisplay}
299
+ conditionalRules={conditionalRules} onAddConditionalRule={addConditionalRule}
300
+ onRemoveConditionalRule={removeConditionalRule} onUpdateConditionalRule={updateConditionalRule}
301
+ />
302
+ )}
303
+ bulkActionsSlot={selected => {
304
+ if (selected.size === 0) return null
305
+ return (
306
+ <>
307
+ <span className="sr-only">{selected.size} selected</span>
308
+ <Tip label="Export selection (demo)">
309
+ <Button size="sm" variant="outline" type="button">
310
+ <i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
311
+ Export
312
+ </Button>
313
+ </Tip>
314
+ </>
315
+ )
316
+ }}
317
+ />
318
+ </div>
319
+ )
320
+ }
321
+ )
322
+
323
+ FooTable.displayName = "FooTable"
324
+ ```
325
+
326
+ ---
327
+
328
+ ## ListPageTemplate Client Skeleton
329
+
330
+ ```tsx
331
+ "use client"
332
+
333
+ import * as React from "react"
334
+ import { ListPageTemplate, type ViewTab } from "@/components/templates/list-page"
335
+ import { FooPageHeader } from "@/components/foo-page-header"
336
+ import { FooTable, type FooTableHandle } from "@/components/foo-table"
337
+ import { KeyMetrics } from "@/components/key-metrics"
338
+ import { FOO_ITEMS } from "@/lib/mock/foo"
339
+ import { fooKpiInsight, fooKpiMetrics } from "@/lib/mock/foo-kpi"
340
+ import { dataListViewIcon } from "@/lib/data-list-view"
341
+ import { Button } from "@/components/ui/button"
342
+
343
+ const DEFAULT_TABS: ViewTab[] = [
344
+ { id: "all", label: "All Foos", viewType: "table", icon: "fa-table", filterId: "all" },
345
+ ]
346
+
347
+ export function FooClient() {
348
+ const [exportOpen, setExportOpen] = React.useState(false)
349
+ const [showMetrics, setShowMetrics] = React.useState(true)
350
+ const tableRef = React.useRef<FooTableHandle>(null)
351
+
352
+ const metrics = React.useMemo(() => fooKpiMetrics(FOO_ITEMS), [])
353
+ const insight = React.useMemo(() => fooKpiInsight(FOO_ITEMS), [])
354
+
355
+ return (
356
+ <ListPageTemplate
357
+ defaultTabs={DEFAULT_TABS}
358
+ getTabCount={() => FOO_ITEMS.length}
359
+ onEditView={(tab, { updateTab }) => {
360
+ const mustSwitch = tab.viewType !== "table" && tab.viewType !== "list" && tab.viewType !== "board"
361
+ if (mustSwitch) updateTab({ viewType: "table", icon: dataListViewIcon("table") })
362
+ window.setTimeout(() => tableRef.current?.openPropertiesDrawer(), mustSwitch ? 160 : 0)
363
+ }}
364
+ header={
365
+ <FooPageHeader
366
+ itemCount={FOO_ITEMS.length}
367
+ onAdd={() => {}}
368
+ onExport={() => setExportOpen(true)}
369
+ showMetrics={showMetrics}
370
+ onToggleMetrics={() => setShowMetrics(v => !v)}
371
+ />
372
+ }
373
+ metrics={<KeyMetrics variant="flat" metrics={metrics} insight={insight} showHeader={false} metricsSingleRow />}
374
+ showMetrics={showMetrics}
375
+ exportOpen={exportOpen}
376
+ onExportOpenChange={setExportOpen}
377
+ exportTotalRows={FOO_ITEMS.length}
378
+ renderContent={(tab, updateTab) => {
379
+ if (tab.viewType === "table") return <FooTable key={tab.id} ref={tableRef} items={FOO_ITEMS} />
380
+ return (
381
+ <div className="px-4 py-12 text-center lg:px-6">
382
+ <p className="text-sm font-medium text-foreground">{tab.viewType === "list" ? "List" : "Board"} view is not wired in this demo.</p>
383
+ <Button type="button" className="mt-4" size="sm" onClick={() => updateTab({ viewType: "table", icon: dataListViewIcon("table") })}>
384
+ Switch to table
385
+ </Button>
386
+ </div>
387
+ )
388
+ }}
389
+ />
390
+ )
391
+ }
392
+ ```
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: exxat-fontawesome-icons
3
+ description: Font Awesome Pro in Exxat DS — Kit script, fa-light/fa-solid, subset audit (fa:subset-audit), aria-hidden on decorative icons, no parallel icon libraries for product chrome. Use when adding or changing icons, nav glyphs, table toolbar icons, or debugging missing kit glyphs.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Exxat DS — Font Awesome icons
8
+
9
+ **Cursor rule:** `.cursor/rules/exxat-fontawesome-icons.mdc`
10
+ **Handbook:** `apps/web/AGENTS.md` §1 (item 8), §8 accessibility for icon-only / informational cases.
11
+
12
+ ## Stack
13
+
14
+ - **Kit:** `apps/web/app/layout.tsx` loads the Font Awesome Pro kit (subset via `fontawesome-subset.manifest.json`).
15
+ - **Audit:** `pnpm --filter web fa:subset-audit` — refresh kit icon selection when adding new glyph names.
16
+
17
+ ## MUST
18
+
19
+ 1. **`<i className="fa-light fa-…" aria-hidden="true" />`** when a parent provides the accessible name (`Button` + `aria-label`, `Tip`, adjacent visible text).
20
+ 2. **`fa-light`** for default; **`fa-solid`** for active/selected where the app already does (sidebar rows, tabs).
21
+ 3. **Static** `className` strings where possible so the audit script can find class names.
22
+
23
+ ## MUST NOT
24
+
25
+ - Introduce a **second** icon library for the same surfaces (duplicate Lucide/Heroicons for nav, hubs, toolbars).
26
+ - Put the **only** accessible name on `<i>` without parent labelling — see **exxat-accessibility** Case B/C.
27
+
28
+ ## See also
29
+
30
+ - `.cursor/rules/exxat-accessibility.mdc`
31
+ - `.cursor/skills/exxat-accessibility/SKILL.md`
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: exxat-kpi-flat-band
3
+ description: KeyMetrics variant flat — transparent KPI strip, OKLCH brand glow only, cell-border hairlines (no grid surface). Use when wiring ListPageTemplate metrics, dashboard mix KPIs, or fixing flat band looking like a grey box.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Exxat DS — KPI flat band
8
+
9
+ **Rule:** `.cursor/rules/exxat-kpi-flat-band.mdc`
10
+ **Doc:** `apps/web/docs/kpi-flat-band-pattern.md`
11
+
12
+ ## Checklist
13
+
14
+ - [ ] `variant="flat"` on hub / mix view — not `card` for list-page strip.
15
+ - [ ] `flatBandStyle` = **only** `var(--key-metrics-flat-band-radial)`; shadow **`none`**.
16
+ - [ ] No `--key-metrics-flat-band-linear` in component or hub inline `style`.
17
+ - [ ] Cells **`bg-transparent`**; grid uses **`flatMetricsHairlineClass(count, halfLayout)`** — borders only, **no** `gap-px` fill.
18
+ - [ ] **4 KPIs:** verticals between 1|2|3|4 when wide; 2×2 dividers only below `@[max-width:29.99rem]` container.
19
+ - [ ] Divider + glow tokens stay **OKLCH** (`--key-metrics-flat-divider`, `color-mix(in oklch, var(--brand-color) …)`).
20
+ - [ ] **≤ 4** `MetricItem` — `docs/kpi-strip-max-four-pattern.md`.
21
+ - [ ] KPI helpers use **`tableState.rows`** on connected hubs.
22
+
23
+ ## MUST NOT
24
+
25
+ - Grey/lavender **panel** behind metrics (removed linear wash + gap fill).
26
+ - Duplicate KPI **`Card`** wall for same numbers.
27
+ - Mute product suffix to grey in dark (`mutedSuffix` does **not** change `wordmarkColor`).
28
+
29
+ ## Code pointers
30
+
31
+ - `apps/web/components/key-metrics.tsx` — `flatMetricsHairlineClass`, `flatBandStyle`
32
+ - `apps/web/app/globals.css` — `--key-metrics-flat-*`
33
+ - `library-client.tsx`, `dashboard-tabs.tsx` — reference usage
34
+
35
+ ## Pair with
36
+
37
+ - `exxat-kpi-max-four`, `exxat-kpi-trends`, `exxat-list-page-connected-views`
38
+ - `docs/shell-surface-elevation-pattern.md` — sidebar vs page (not the KPI band)
@@ -0,0 +1,19 @@
1
+ # Exxat DS — KPI strip (max four)
2
+
3
+ Use when building **`KeyMetrics`** on **`ListPageTemplate`** or **Data tab** key-metrics cards.
4
+
5
+ ## Read first
6
+
7
+ - **`docs/kpi-strip-max-four-pattern.md`**
8
+ - **`lib/dashboard-layout-merge.ts`** — `KEY_METRICS_KPI_COUNT_MAX`, `clampKeyMetricsKpiCount`
9
+ - **`.cursor/rules/exxat-kpi-max-four.mdc`**
10
+
11
+ ## Checklist
12
+
13
+ 1. **`entityKpiMetrics`** returns **at most four** `MetricItem` for those surfaces (or `.slice(0, 4)` after priority sort).
14
+ 2. **Extra metrics** → `MetricInsight` description, a chart, or another card — not a fifth tile.
15
+ 3. **Dashboard persistence** — Respect clamped `keyMetricsKpiCount` (1–4); never bump max without design approval.
16
+
17
+ ## Related
18
+
19
+ - **`docs/kpi-trend-pattern.md`** — deltas and `trendPolarity`.
@@ -0,0 +1,29 @@
1
+ # Exxat DS — KPI trend arrows and polarity
2
+
3
+ Use when adding or reviewing **`KeyMetrics`**, **`lib/mock/*-kpi.ts`** helpers, or **`ChartCard`** **`miniMetrics`** / **`kpi-chart`** trends.
4
+
5
+ ## Authoritative references
6
+
7
+ - **`apps/web/docs/kpi-trend-pattern.md`** — product table + psychometrics example (PBI).
8
+ - **`.cursor/rules/exxat-kpi-trends.mdc`** — binding MUST/MUST NOT.
9
+ - **`apps/web/components/key-metrics.tsx`** — `MetricTrendPolarity`, `metricTrendTone`, `metricTrendAriaQualifier`, `MetricCell` rendering.
10
+
11
+ ## Checklist (new or changed KPI)
12
+
13
+ 1. **Delta honest?** `trend` follows the sign of the change vs the comparison period.
14
+ 2. **Polarity correct?** If “more” is bad → **`trendPolarity: "lower_is_better"`**. If no value judgment → **`"informational"`** (muted tints).
15
+ 3. **Delta is a count, not prose.** **`delta`** is `"+5"`, `"-3"`, `"+12%"`. Captions like `"left + right"`, `"vs last week"`, `"scheduled for removal"` go in **`description`** (renders **below** the value, muted).
16
+ 4. **No empty `—` chip.** If there’s no direction *and* no count, leave **`delta: ""`** + **`trend: "neutral"`** — **`KeyMetrics`** hides the chip. Don’t hand-roll a placeholder.
17
+ 5. **Copy contextual?** Label + value + (delta when present) + description tell users *what* moved, not only *how much*.
18
+ 6. **Screen readers** — Chip keeps icon + visible delta; `aria-label` reflects favorable / unfavorable wording automatically when polarity is set.
19
+ 7. **Chart cards** — When using **`ChartCard`** `miniMetrics`, pass **`trendPolarity`** the same way as on **`MetricItem`**.
20
+
21
+ ## Quick examples
22
+
23
+ | Metric | More is… | `trendPolarity` |
24
+ | --- | --- | --- |
25
+ | Total placements | Better (capacity) | default / `higher_is_better` |
26
+ | Pass rate | Better | `higher_is_better` |
27
+ | Open incidents | Worse | `lower_is_better` |
28
+ | Low PBI / review flags | Worse | `lower_is_better` |
29
+ | Items by type (mix %) | Neutral | `informational` |
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: exxat-list-page-view-shells
3
+ description: >-
4
+ Centered, reusable layout for ListPageTemplate view bodies — ListPageViewFrame,
5
+ data-views/ primitives, and rules to avoid page-tied view markup. Use when adding
6
+ folder/panel/custom views, fixing wide-viewport layout, or extracting view UI from a single hub.
7
+ ---
8
+
9
+ # List-page view shells (Exxat DS)
10
+
11
+ ## When to use this skill
12
+
13
+ - Adding or changing **`viewType`** surfaces: **folder**, **panel**, list/board chrome, icon grids, OS-style explorers.
14
+ - User or PR asks for **“centered”**, **“reusable view”**, or **“not tied to one page”** layout.
15
+ - You see **duplicated** `mx-4 lg:mx-6`, `mx-auto max-w-6xl`, or similar wrappers in multiple `*-table.tsx` files.
16
+
17
+ ## Instructions
18
+
19
+ 1. **Gutter + optional max width** — Import **`ListPageViewFrame`** from **`@/components/data-views/list-page-view-frame`** (or **`@/components/data-views`**). Wrap the view body (usually below **`DataTableToolbar`** when the view shares the table toolbar).
20
+ - Pass **`maxWidthClassName={LIST_PAGE_VIEW_FRAME_MAX_ICON_GRID}`** for dense tile grids.
21
+ - Pass **`maxWidthClassName={LIST_PAGE_VIEW_FRAME_MAX_WIDE}`** when the view includes toolbar rows + breadcrumbs + grid.
22
+ 2. **Do not double-gutter** — If **`DataTable`** already provides horizontal inset for the **table** view, do **not** wrap that branch in **`ListPageViewFrame`**. Use the frame on **sibling** view branches (folder, panel, etc.) only.
23
+ 3. **Reuse before inventing** — Prefer **`FolderGridView`**, **`FinderPanelView`**, **`ListPageBoardTemplate`**, **`ListPageViewFrame`**. If a new pattern appears twice, **promote** it to **`components/data-views/`** with domain-agnostic props.
24
+ 4. **Entity-specific → generic** — If logic is “any tree + any row type”, build **`components/data-views/<generic-name>.tsx`** and keep **`library-*`** (or similar) as a thin composition + mock types.
25
+
26
+ ## Checklist
27
+
28
+ - [ ] View body uses **`ListPageViewFrame`** (or a component that uses it internally, e.g. **`FolderGridView`**).
29
+ - [ ] No **extra** horizontal padding around **`DataTable`** vs **`AGENTS.md` §5**.
30
+ - [ ] New grid/shell lives under **`components/data-views/`** when a second hub could reuse it.
31
+
32
+ ## References
33
+
34
+ - **`apps/web/components/data-views/list-page-view-frame.tsx`** — implementation + exported constants.
35
+ - **`apps/web/AGENTS.md` §4.5** — MUST/MUST NOT.
36
+ - **`.cursor/rules/exxat-list-page-view-shells.mdc`**.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: exxat-mono-ids
3
+ description: Monospace typography for Exxat DS record IDs, question IDs, and system identifiers — font-mono tabular-nums, mixed subtitle lines, table meta. Use when adding or changing questionId, record id columns, header subtitles with IDs, or any copy-pasteable system key in apps/web UI.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Exxat DS — monospace IDs
8
+
9
+ **Cursor rule:** `.cursor/rules/exxat-mono-ids.mdc`
10
+ **Handbook:** `apps/web/AGENTS.md` (§1, §13 checklist)
11
+
12
+ ## Standard classes
13
+
14
+ ```tsx
15
+ // Default secondary ID (table meta, list subline, inspector)
16
+ <span className="font-mono tabular-nums text-xs text-muted-foreground">{id}</span>
17
+
18
+ // ID in a page subtitle next to sans prose
19
+ <>
20
+ <span className="font-mono tabular-nums">{questionId}</span>
21
+ {" · V1 · Last updated just now"}
22
+ </>
23
+ ```
24
+
25
+ Always include **`tabular-nums`** with **`font-mono`** so fixed-width digits align in tables and subtitles.
26
+
27
+ ## MUST
28
+
29
+ 1. **Every visible system ID** in product UI uses **`font-mono tabular-nums`** (plus contextual size/color).
30
+ 2. **Mixed lines** — mono only on the identifier substring, not the whole sentence.
31
+ 3. **Tables / lists / boards** — ID column or subline under a title: mono + usually **`text-xs text-muted-foreground`**.
32
+
33
+ ## MUST NOT
34
+
35
+ - Mono on **display names**, **emails** (unless the column is explicitly “Principal ID”), **dates**, **KPI values**, or **status text**.
36
+ - Mono on **UI chrome** that is not an identifier (badges, buttons, nav labels).
37
+
38
+ ## Reference implementations
39
+
40
+ | Surface | File |
41
+ |---------|------|
42
+ | Library table | `components/library-table.tsx` — `row.questionId` |
43
+ | Library list | `components/library-list-view.tsx` |
44
+ | New question subtitle | `components/new-library-item-form.tsx` — `questionId` in `PageHeader` subtitle |
45
+ | Sites record id | `components/sites-table.tsx` — `row.id` |
46
+ | OS folder tiles | `components/library-os-folder-view.tsx` |
47
+
48
+ ## Review checklist
49
+
50
+ - [ ] New or changed **ID** fields use **`font-mono tabular-nums`**.
51
+ - [ ] Subtitle / description lines mono-wrap **only** the ID token.
52
+ - [ ] No mono applied to names, statuses, or numeric metrics that are not identifiers.
53
+
54
+ ## See also
55
+
56
+ - `.cursor/rules/exxat-person-identity-display.mdc` — name + email (sans, not mono for email unless ID column)
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: exxat-primary-nav-secondary-panel
3
+ description: Exxat DS pattern — one primary sidebar row opens a nested SecondaryPanel (Library). NavLinkItem.secondaryPanel id, PANELS registry, useAutoPanel on hub, URL scope + same useTableState rows. Use when adding hub scoped nav (All/My/tree) beside content.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Exxat DS — primary nav → secondary panel
8
+
9
+ **Cursor rule:** `.cursor/rules/exxat-primary-nav-secondary-panel.mdc`
10
+ **Handbook:** `apps/web/AGENTS.md` §4.6
11
+
12
+ ## Wiring checklist
13
+
14
+ 1. **`lib/mock/navigation.tsx`** — set **`secondaryPanel: "<id>"`** on the primary **`NavLinkItem`**; **`url`** = hub route.
15
+ 2. **`components/secondary-panel.tsx`** — add **`PANELS["<id>"]`** → panel shell (title, optional search) + secondary nav component.
16
+ 3. **Hub client** — mount **`*PanelActivator`** with **`useAutoPanel("<id>")`** (same id) for the lifetime of the route (e.g. `LibraryPanelActivator`).
17
+ 4. **Data** — keep **one** **`useTableState`** / **`tableState.rows`**; drive scope from **URL** + small helpers (see **`lib/library-nav.ts`**).
18
+ 5. **Folder-scoped hub header (Library library)** — When **`scope === "folder"`** in the URL, **`LibraryPageHeader`** **⋯ More** includes **Customize folder**; mount **`LibraryNewFolderSheet`** on **`LibraryClient`** so it works on **all** **`ListPageTemplate`** view tabs — **`.cursor/rules/exxat-library-hub-header.mdc`**, **`docs/library-hub-header-pattern.md`**.
19
+ 6. **Collapse control** — the nested rail header uses **`collapseActiveSecondaryPanel()`** (angles-left icon), not “close”, so the panel stays dismissed until **`openPanel`** runs again (nav, scope hook, or hub re-entry). Layout effects that auto-call **`openPanel`** must respect **`secondaryPanelAutoReopenSuppressed`** (see **`app/(app)/library/layout.tsx`** + **`SecondaryPanelProvider`**).
20
+ 7. **Surface elevation** — secondary panel = **level 1** (lighter than sidebar, darker than page). Use **`--secondary-panel-bg`** on **`NestedSecondaryPanelShell`**; derive from **`--brand-tint*`** per active product (**One** indigo, **Prism** rose). See **`docs/shell-surface-elevation-pattern.md`**.
21
+
22
+ ## MUST NOT
23
+
24
+ - Set **`secondaryPanel`** without **`PANELS[id]`** + **`useAutoPanel`** — broken empty rail.
25
+ - Use this for full product areas that belong as **primary** or **collapsible child** rows.
26
+ - Invent a parallel zoom / reflow hook for the secondary rail — reuse **`useSidebarReflowZoom`** (the provider already wires it; see §"High-zoom auto-collapse" below).
27
+ - Set secondary panel **`bg-sidebar`** or a fixed rose mix for every product — breaks **One** indigo chrome.
28
+
29
+ ## High-zoom auto-collapse
30
+
31
+ `SecondaryPanelProvider` reads **`useSidebarReflowZoom()`** (zoom ≥ 200% or very short viewport — same WCAG 1.4.10 signal the primary sidebar uses). On entering high zoom it sets `secondaryPanelCompact = true` so the 16 rem rail drops to the 3 rem icon variant and frees up content space. The user can still re-expand manually (via the icon rail's "Show labels" affordance or any `openPanel` trigger); the next zoom-out → zoom-in cycle re-collapses it. `openPanel` itself opens directly in compact mode when high zoom is already active so newly-navigated panels don't flash expanded.
32
+
33
+ Custom panel content (anything you register under `PANELS[id]`) should **read `secondaryPanelCompact` from the provider context** and render an icon-only layout in that branch — `LibraryPanel` / `LibrarySecondaryNav` are the reference.
34
+
35
+ ## Pair with
36
+
37
+ - **Collapsible parent ↔ children sidebar pattern** (§3.2 of `exxat-ds-skill`) — when the same primary row also lists sub-routes inline.
38
+
39
+ ## Reference
40
+
41
+ - `components/app-sidebar.tsx` — `openPanel` on same-route primary click.
42
+ - `components/secondary-panel.tsx` — `SecondaryPanelProvider`, `PANELS` registry, **high-zoom auto-collapse**.
43
+ - `hooks/use-sidebar-reflow-zoom.ts` — shared zoom / reflow signal.
44
+ - `components/templates/nested-secondary-panel-shell.tsx` — expanded vs `compact` (icon rail) widths; **`bg-[var(--secondary-panel-bg)]`**.
45
+ - `app/globals.css` — `--secondary-panel-bg`, `--sidebar`, product **`theme-one`** / **`theme-prism`** blocks.
46
+ - `contexts/product-context.tsx` — `accentOverrideActive`, theme class on `<html>`.
47
+ - **`docs/shell-surface-elevation-pattern.md`**
48
+ - `components/library-secondary-nav.tsx` + `lib/library-nav.ts`.
49
+ - **Folder-scoped header customize:** `components/library-page-header.tsx`, `components/library-client.tsx` — **`docs/library-hub-header-pattern.md`**, **`.cursor/rules/exxat-library-hub-header.mdc`**.