@exxatdesignux/ui 0.5.11 → 0.5.13

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 (477) hide show
  1. package/CHANGELOG.md +45 -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 +1 -0
  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 +28 -0
  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/patterns/command-menu-pattern.md +2 -2
  47. package/consumer-extras/patterns/consumer-upgrade-checklist.md +1 -1
  48. package/consumer-extras/patterns/jobs/README.md +1 -1
  49. package/consumer-extras/patterns/perf-memory-pattern.md +115 -150
  50. package/consumer-extras/scripts/dev-guard.mjs +156 -0
  51. package/consumer-extras/templates/README.md +23 -0
  52. package/consumer-extras/templates/handoff.md +190 -0
  53. package/package.json +2 -3
  54. package/{template → template-vite}/.claude/skills/exxat-ds-skill/SKILL.md +184 -23
  55. package/template-vite/.cursor/rules/exxat-accessibility.mdc +40 -0
  56. package/template-vite/.cursor/rules/exxat-board-cards.mdc +28 -0
  57. package/template-vite/.cursor/rules/exxat-breadcrumbs-no-back.mdc +22 -0
  58. package/template-vite/.cursor/rules/exxat-card-vs-list-rows.mdc +22 -0
  59. package/template-vite/.cursor/rules/exxat-centralized-list-dataset.mdc +46 -0
  60. package/template-vite/.cursor/rules/exxat-collaboration-access.mdc +33 -0
  61. package/{template → template-vite}/.cursor/rules/exxat-command-menu.mdc +5 -5
  62. package/template-vite/.cursor/rules/exxat-data-tables.mdc +47 -0
  63. package/template-vite/.cursor/rules/exxat-dedicated-search-surfaces.mdc +32 -0
  64. package/template-vite/.cursor/rules/exxat-drawer-vs-dialog.mdc +23 -0
  65. package/template-vite/.cursor/rules/exxat-ds-agents.mdc +87 -0
  66. package/template-vite/.cursor/rules/exxat-fontawesome-icons.mdc +32 -0
  67. package/template-vite/.cursor/rules/exxat-hub-supported-views.mdc +56 -0
  68. package/{template → template-vite}/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -0
  69. package/template-vite/.cursor/rules/exxat-kpi-flat-band.mdc +29 -0
  70. package/template-vite/.cursor/rules/exxat-kpi-max-four.mdc +22 -0
  71. package/template-vite/.cursor/rules/exxat-kpi-trends.mdc +32 -0
  72. package/template-vite/.cursor/rules/exxat-library-hub-header.mdc +29 -0
  73. package/template-vite/.cursor/rules/exxat-list-page-connected-views.mdc +28 -0
  74. package/template-vite/.cursor/rules/exxat-list-page-view-shells.mdc +32 -0
  75. package/{template → template-vite}/.cursor/rules/exxat-mono-ids.mdc +1 -0
  76. package/template-vite/.cursor/rules/exxat-nav-single-active.mdc +32 -0
  77. package/template-vite/.cursor/rules/exxat-no-image-pixel-copy.mdc +46 -0
  78. package/template-vite/.cursor/rules/exxat-no-slds-leakage.mdc +84 -0
  79. package/{template → template-vite}/.cursor/rules/exxat-no-toast.mdc +2 -2
  80. package/template-vite/.cursor/rules/exxat-no-vaul.mdc +26 -0
  81. package/template-vite/.cursor/rules/exxat-page-header-actions.mdc +33 -0
  82. package/{template → template-vite}/.cursor/rules/exxat-page-vs-drawer.mdc +5 -3
  83. package/template-vite/.cursor/rules/exxat-person-identity-display.mdc +48 -0
  84. package/template-vite/.cursor/rules/exxat-primary-nav-secondary-panel.mdc +53 -0
  85. package/template-vite/.cursor/rules/exxat-reuse-before-custom.mdc +37 -0
  86. package/template-vite/.cursor/rules/exxat-sidebar-shell.mdc +41 -0
  87. package/template-vite/.cursor/rules/exxat-table-properties-drawer.mdc +79 -0
  88. package/template-vite/.cursor/rules/exxat-table-row-preview.mdc +25 -0
  89. package/template-vite/.cursor/rules/exxat-tabs-chrome.mdc +33 -0
  90. package/template-vite/.cursor/rules/exxat-token-discipline.mdc +109 -0
  91. package/template-vite/.cursor/rules/exxat-ux-discovery-protocol.mdc +202 -0
  92. package/template-vite/.cursor/rules/exxat-ux-principles.mdc +187 -0
  93. package/template-vite/.cursor/skills/exxat-accessibility/SKILL.md +282 -0
  94. package/template-vite/.cursor/skills/exxat-board-cards/SKILL.md +68 -0
  95. package/template-vite/.cursor/skills/exxat-card-vs-list-rows/SKILL.md +20 -0
  96. package/template-vite/.cursor/skills/exxat-centralized-list-dataset/SKILL.md +99 -0
  97. package/template-vite/.cursor/skills/exxat-collaboration-access/SKILL.md +35 -0
  98. package/template-vite/.cursor/skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
  99. package/template-vite/.cursor/skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
  100. package/template-vite/.cursor/skills/exxat-ds-skill/SKILL.md +893 -0
  101. package/template-vite/.cursor/skills/exxat-ds-skill/references/accessibility.md +142 -0
  102. package/template-vite/.cursor/skills/exxat-ds-skill/references/coach-marks.md +169 -0
  103. package/template-vite/.cursor/skills/exxat-ds-skill/references/data-table-pattern.md +392 -0
  104. package/template-vite/.cursor/skills/exxat-fontawesome-icons/SKILL.md +31 -0
  105. package/template-vite/.cursor/skills/exxat-kpi-flat-band/SKILL.md +38 -0
  106. package/template-vite/.cursor/skills/exxat-kpi-max-four/SKILL.md +19 -0
  107. package/template-vite/.cursor/skills/exxat-kpi-trends/SKILL.md +29 -0
  108. package/template-vite/.cursor/skills/exxat-list-page-view-shells/SKILL.md +36 -0
  109. package/template-vite/.cursor/skills/exxat-mono-ids/SKILL.md +56 -0
  110. package/template-vite/.cursor/skills/exxat-primary-nav-secondary-panel/SKILL.md +49 -0
  111. package/template-vite/.cursor/skills/exxat-senior-ux/SKILL.md +198 -0
  112. package/template-vite/.cursor/skills/exxat-token-economy/SKILL.md +287 -0
  113. package/template-vite/.cursor/skills/exxat-ux-audit/SKILL.md +303 -0
  114. package/{template → template-vite}/components/ask-leo-sidebar.tsx +10 -8
  115. package/{template → template-vite}/components/command-menu.tsx +1 -1
  116. package/{template → template-vite}/components/data-views/library-folder-tree-branch.tsx +1 -1
  117. package/{template → template-vite}/components/dedicated-search-recents.tsx +1 -1
  118. package/{template → template-vite}/components/dedicated-search-url-composer.tsx +1 -1
  119. package/{template → template-vite}/components/exxat-product-logo.tsx +3 -3
  120. package/{template → template-vite}/components/library-client.tsx +1 -1
  121. package/{template → template-vite}/components/library-hub-client.tsx +2 -2
  122. package/{template → template-vite}/components/library-secondary-nav.tsx +2 -2
  123. package/{template → template-vite}/components/library-table.tsx +35 -27
  124. package/{template → template-vite}/components/new-library-item-form.tsx +1 -1
  125. package/{template → template-vite}/components/page-breadcrumb-trail.tsx +1 -1
  126. package/{template → template-vite}/components/settings-client.tsx +1 -1
  127. package/{template → template-vite}/components/sidebar/app-sidebar.tsx +2 -2
  128. package/{template → template-vite}/components/sidebar/nav-main.tsx +1 -1
  129. package/{template → template-vite}/components/sidebar/nav-user.tsx +1 -1
  130. package/{template → template-vite}/components/sidebar/secondary-nav.tsx +1 -1
  131. package/{template → template-vite}/components/system-banner-slot.tsx +1 -1
  132. package/{template → template-vite}/components/templates/discovery-hub-template.tsx +2 -2
  133. package/{template → template-vite}/components/templates/new-focus-template.tsx +1 -1
  134. package/{template → template-vite}/components/tokens-secondary-nav.tsx +2 -2
  135. package/{template → template-vite}/components/tokens-themes-client.tsx +1 -1
  136. package/{template → template-vite}/hooks/use-secondary-panel-hub-nav.ts +1 -1
  137. package/template-vite/index.html +49 -0
  138. package/template-vite/lib/next-compat.tsx +98 -0
  139. package/{template → template-vite}/package.json +15 -27
  140. package/template-vite/scripts/port-next-imports.mjs +70 -0
  141. package/template-vite/src/App.tsx +103 -0
  142. package/template-vite/src/main.tsx +50 -0
  143. package/{template/app/(app)/error.tsx → template-vite/src/pages/_error.tsx} +12 -24
  144. package/{template/app/(app)/loading.tsx → template-vite/src/pages/_loading.tsx} +4 -2
  145. package/template-vite/src/pages/_not-found.tsx +17 -0
  146. package/template-vite/src/pages/dashboard.tsx +48 -0
  147. package/{template/app/(app)/help/page.tsx → template-vite/src/pages/help.tsx} +3 -2
  148. package/{template/app/(app)/library/layout.tsx → template-vite/src/pages/library/_layout.tsx} +18 -16
  149. package/{template/app/(app)/library/all/page.tsx → template-vite/src/pages/library/all.tsx} +1 -1
  150. package/{template/app/(app)/library/new/page.tsx → template-vite/src/pages/library/new.tsx} +12 -18
  151. package/template-vite/src/routes.tsx +72 -0
  152. package/template-vite/src/styles/globals.css +25 -0
  153. package/{template → template-vite}/tsconfig.json +5 -14
  154. package/template-vite/vite.config.ts +52 -0
  155. package/consumer-extras/cursor-rules/exxat-dashboard-view-charts.mdc +0 -53
  156. package/template/.agents/skills/shadcn/SKILL.md +0 -242
  157. package/template/.agents/skills/shadcn/agents/openai.yml +0 -5
  158. package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  159. package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
  160. package/template/.agents/skills/shadcn/cli.md +0 -257
  161. package/template/.agents/skills/shadcn/customization.md +0 -202
  162. package/template/.agents/skills/shadcn/evals/evals.json +0 -47
  163. package/template/.agents/skills/shadcn/mcp.md +0 -94
  164. package/template/.agents/skills/shadcn/rules/base-vs-radix.md +0 -306
  165. package/template/.agents/skills/shadcn/rules/composition.md +0 -195
  166. package/template/.agents/skills/shadcn/rules/forms.md +0 -192
  167. package/template/.agents/skills/shadcn/rules/icons.md +0 -101
  168. package/template/.agents/skills/shadcn/rules/styling.md +0 -162
  169. package/template/.cursor/rules/exxat-accessibility.mdc +0 -33
  170. package/template/.cursor/rules/exxat-data-tables.mdc +0 -32
  171. package/template/.cursor/rules/exxat-ds-agents.mdc +0 -26
  172. package/template/.cursor/rules/exxat-list-page-connected-views.mdc +0 -16
  173. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +0 -40
  174. package/template/.nvmrc +0 -1
  175. package/template/.prettierignore +0 -7
  176. package/template/Logo/Exxat_Prism.svg +0 -39
  177. package/template/Logo/Exxat_one.svg +0 -36
  178. package/template/app/(app)/dashboard/loading.tsx +0 -18
  179. package/template/app/(app)/dashboard/page.tsx +0 -36
  180. package/template/app/(app)/layout.tsx +0 -77
  181. package/template/app/global-error.tsx +0 -63
  182. package/template/app/globals.css +0 -20
  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/folder-details-shell.tsx +0 -0
  273. /package/{template → template-vite}/components/form-layout-01.tsx +0 -0
  274. /package/{template → template-vite}/components/hub-tree-panel-view.tsx +0 -0
  275. /package/{template → template-vite}/components/invite-collaborators-drawer.tsx +0 -0
  276. /package/{template → template-vite}/components/key-metrics-ask-leo-bridge.tsx +0 -0
  277. /package/{template → template-vite}/components/key-metrics.tsx +0 -0
  278. /package/{template → template-vite}/components/leo-insight-indicator.tsx +0 -0
  279. /package/{template → template-vite}/components/leo-typing-dots.tsx +0 -0
  280. /package/{template → template-vite}/components/library-board-view.tsx +0 -0
  281. /package/{template → template-vite}/components/library-dashboard-charts.tsx +0 -0
  282. /package/{template → template-vite}/components/library-favorite-button.tsx +0 -0
  283. /package/{template → template-vite}/components/library-new-folder-sheet.tsx +0 -0
  284. /package/{template → template-vite}/components/library-os-folder-view.tsx +0 -0
  285. /package/{template → template-vite}/components/library-page-header.tsx +0 -0
  286. /package/{template → template-vite}/components/library-panel-activator.tsx +0 -0
  287. /package/{template → template-vite}/components/list-hub-status-badge.tsx +0 -0
  288. /package/{template → template-vite}/components/list-page-dashboard-charts.tsx +0 -0
  289. /package/{template → template-vite}/components/onboarding/getting-started.tsx +0 -0
  290. /package/{template → template-vite}/components/onboarding/index.ts +0 -0
  291. /package/{template → template-vite}/components/onboarding/onboarding-01.tsx +0 -0
  292. /package/{template → template-vite}/components/onboarding/onboarding-02.tsx +0 -0
  293. /package/{template → template-vite}/components/onboarding/onboarding-03.tsx +0 -0
  294. /package/{template → template-vite}/components/onboarding/onboarding-04.tsx +0 -0
  295. /package/{template → template-vite}/components/page-header.tsx +0 -0
  296. /package/{template → template-vite}/components/product-switcher.tsx +0 -0
  297. /package/{template → template-vite}/components/product-wordmark.tsx +0 -0
  298. /package/{template → template-vite}/components/settings-appearance-card.tsx +0 -0
  299. /package/{template → template-vite}/components/settings-form-row.tsx +0 -0
  300. /package/{template → template-vite}/components/sidebar/app-sidebar-dynamic.tsx +0 -0
  301. /package/{template → template-vite}/components/sidebar/index.ts +0 -0
  302. /package/{template → template-vite}/components/sidebar/nav-documents.tsx +0 -0
  303. /package/{template → template-vite}/components/sidebar/nav-secondary.tsx +0 -0
  304. /package/{template → template-vite}/components/sidebar/secondary-panel.tsx +0 -0
  305. /package/{template → template-vite}/components/sidebar/sidebar-auto-collapse.tsx +0 -0
  306. /package/{template → template-vite}/components/sidebar/sidebar-auto-open.tsx +0 -0
  307. /package/{template → template-vite}/components/sidebar/sidebar-shell.tsx +0 -0
  308. /package/{template → template-vite}/components/site-header.tsx +0 -0
  309. /package/{template → template-vite}/components/table-properties/column-row.tsx +0 -0
  310. /package/{template → template-vite}/components/table-properties/draggable-list.ts +0 -0
  311. /package/{template → template-vite}/components/table-properties/drawer-button.tsx +0 -0
  312. /package/{template → template-vite}/components/table-properties/drawer.tsx +0 -0
  313. /package/{template → template-vite}/components/table-properties/filter-card.tsx +0 -0
  314. /package/{template → template-vite}/components/table-properties/index.ts +0 -0
  315. /package/{template → template-vite}/components/table-properties/sort-card.tsx +0 -0
  316. /package/{template → template-vite}/components/table-properties/types.ts +0 -0
  317. /package/{template → template-vite}/components/task-list-panel.tsx +0 -0
  318. /package/{template → template-vite}/components/task-priority-badge.tsx +0 -0
  319. /package/{template → template-vite}/components/templates/dedicated-search-landing-template.tsx +0 -0
  320. /package/{template → template-vite}/components/templates/dedicated-search-results-template.tsx +0 -0
  321. /package/{template → template-vite}/components/templates/list-page.tsx +0 -0
  322. /package/{template → template-vite}/components/templates/nested-secondary-panel-shell.tsx +0 -0
  323. /package/{template → template-vite}/components/templates/primary-page-template.tsx +0 -0
  324. /package/{template → template-vite}/components/templates/secondary-panel-hub-template.tsx +0 -0
  325. /package/{template → template-vite}/components/theme-color-sync.tsx +0 -0
  326. /package/{template → template-vite}/components/theme-provider.tsx +0 -0
  327. /package/{template → template-vite}/components/tinted-icon-disc.tsx +0 -0
  328. /package/{template → template-vite}/components/tokens-hub-auxiliary-views.tsx +0 -0
  329. /package/{template → template-vite}/components/tokens-themes-section.tsx +0 -0
  330. /package/{template → template-vite}/components/ui/accordion.tsx +0 -0
  331. /package/{template → template-vite}/components/ui/ai-thinking-surface.tsx +0 -0
  332. /package/{template → template-vite}/components/ui/alert-dialog.tsx +0 -0
  333. /package/{template → template-vite}/components/ui/avatar.tsx +0 -0
  334. /package/{template → template-vite}/components/ui/badge.tsx +0 -0
  335. /package/{template → template-vite}/components/ui/banner.tsx +0 -0
  336. /package/{template → template-vite}/components/ui/breadcrumb.tsx +0 -0
  337. /package/{template → template-vite}/components/ui/button.tsx +0 -0
  338. /package/{template → template-vite}/components/ui/calendar.tsx +0 -0
  339. /package/{template → template-vite}/components/ui/card.tsx +0 -0
  340. /package/{template → template-vite}/components/ui/chart.tsx +0 -0
  341. /package/{template → template-vite}/components/ui/checkbox.tsx +0 -0
  342. /package/{template → template-vite}/components/ui/coach-mark.tsx +0 -0
  343. /package/{template → template-vite}/components/ui/collapsible.tsx +0 -0
  344. /package/{template → template-vite}/components/ui/command.tsx +0 -0
  345. /package/{template → template-vite}/components/ui/context-menu.tsx +0 -0
  346. /package/{template → template-vite}/components/ui/date-picker-field.tsx +0 -0
  347. /package/{template → template-vite}/components/ui/dialog.tsx +0 -0
  348. /package/{template → template-vite}/components/ui/dot-pattern.tsx +0 -0
  349. /package/{template → template-vite}/components/ui/drag-handle-grip.tsx +0 -0
  350. /package/{template → template-vite}/components/ui/dropdown-menu.tsx +0 -0
  351. /package/{template → template-vite}/components/ui/field.tsx +0 -0
  352. /package/{template → template-vite}/components/ui/form.tsx +0 -0
  353. /package/{template → template-vite}/components/ui/hover-card.tsx +0 -0
  354. /package/{template → template-vite}/components/ui/input-group.tsx +0 -0
  355. /package/{template → template-vite}/components/ui/input-mask.tsx +0 -0
  356. /package/{template → template-vite}/components/ui/input.tsx +0 -0
  357. /package/{template → template-vite}/components/ui/kbd.tsx +0 -0
  358. /package/{template → template-vite}/components/ui/label.tsx +0 -0
  359. /package/{template → template-vite}/components/ui/leo-icon.tsx +0 -0
  360. /package/{template → template-vite}/components/ui/payment-card-fields.tsx +0 -0
  361. /package/{template → template-vite}/components/ui/popover.tsx +0 -0
  362. /package/{template → template-vite}/components/ui/radio-group.tsx +0 -0
  363. /package/{template → template-vite}/components/ui/resizable.tsx +0 -0
  364. /package/{template → template-vite}/components/ui/scroll-area.tsx +0 -0
  365. /package/{template → template-vite}/components/ui/select.tsx +0 -0
  366. /package/{template → template-vite}/components/ui/selection-tile-grid.tsx +0 -0
  367. /package/{template → template-vite}/components/ui/separator.tsx +0 -0
  368. /package/{template → template-vite}/components/ui/sheet.tsx +0 -0
  369. /package/{template → template-vite}/components/ui/sidebar.tsx +0 -0
  370. /package/{template → template-vite}/components/ui/skeleton.tsx +0 -0
  371. /package/{template → template-vite}/components/ui/slider.tsx +0 -0
  372. /package/{template → template-vite}/components/ui/sonner.tsx +0 -0
  373. /package/{template → template-vite}/components/ui/status-badge.tsx +0 -0
  374. /package/{template → template-vite}/components/ui/table.tsx +0 -0
  375. /package/{template → template-vite}/components/ui/tabs.tsx +0 -0
  376. /package/{template → template-vite}/components/ui/textarea.tsx +0 -0
  377. /package/{template → template-vite}/components/ui/tip.tsx +0 -0
  378. /package/{template → template-vite}/components/ui/toggle-group.tsx +0 -0
  379. /package/{template → template-vite}/components/ui/toggle-switch.tsx +0 -0
  380. /package/{template → template-vite}/components/ui/toggle.tsx +0 -0
  381. /package/{template → template-vite}/components/ui/tooltip.tsx +0 -0
  382. /package/{template → template-vite}/components/ui/view-segmented-control.tsx +0 -0
  383. /package/{template → template-vite}/components.json +0 -0
  384. /package/{template → template-vite}/contexts/chart-variant-context.tsx +0 -0
  385. /package/{template → template-vite}/contexts/command-menu-context.tsx +0 -0
  386. /package/{template → template-vite}/contexts/dashboard-view-context.tsx +0 -0
  387. /package/{template → template-vite}/contexts/product-context.tsx +0 -0
  388. /package/{template → template-vite}/contexts/system-banner-context.tsx +0 -0
  389. /package/{template → template-vite}/eslint.config.mjs +0 -0
  390. /package/{template → template-vite}/fontawesome-subset.manifest.json +0 -0
  391. /package/{template → template-vite}/hooks/.gitkeep +0 -0
  392. /package/{template → template-vite}/hooks/use-app-theme.ts +0 -0
  393. /package/{template → template-vite}/hooks/use-coach-mark.ts +0 -0
  394. /package/{template → template-vite}/hooks/use-location-hash.ts +0 -0
  395. /package/{template → template-vite}/hooks/use-mobile.ts +0 -0
  396. /package/{template → template-vite}/hooks/use-mod-key-label.ts +0 -0
  397. /package/{template → template-vite}/hooks/use-sidebar-reflow-zoom.ts +0 -0
  398. /package/{template → template-vite}/lib/.gitkeep +0 -0
  399. /package/{template → template-vite}/lib/ask-leo-route-context.ts +0 -0
  400. /package/{template → template-vite}/lib/chart-keyboard-selection.test.ts +0 -0
  401. /package/{template → template-vite}/lib/chart-keyboard-selection.ts +0 -0
  402. /package/{template → template-vite}/lib/chart-line-dash.ts +0 -0
  403. /package/{template → template-vite}/lib/chunk-load-error.ts +0 -0
  404. /package/{template → template-vite}/lib/coach-mark-registry.ts +0 -0
  405. /package/{template → template-vite}/lib/collaborator-access.ts +0 -0
  406. /package/{template → template-vite}/lib/command-menu-config.ts +0 -0
  407. /package/{template → template-vite}/lib/command-menu-search-data.ts +0 -0
  408. /package/{template → template-vite}/lib/conditional-rule-match.ts +0 -0
  409. /package/{template → template-vite}/lib/dashboard-customize-coach-mark.ts +0 -0
  410. /package/{template → template-vite}/lib/dashboard-layout-merge.ts +0 -0
  411. /package/{template → template-vite}/lib/data-list-display-options.ts +0 -0
  412. /package/{template → template-vite}/lib/data-list-persistence.ts +0 -0
  413. /package/{template → template-vite}/lib/data-list-view-registry.ts +0 -0
  414. /package/{template → template-vite}/lib/data-list-view-surface.ts +0 -0
  415. /package/{template → template-vite}/lib/data-list-view.ts +0 -0
  416. /package/{template → template-vite}/lib/data-view-dashboard-storage.ts +0 -0
  417. /package/{template → template-vite}/lib/date-filter.ts +0 -0
  418. /package/{template → template-vite}/lib/dedicated-search-recents.ts +0 -0
  419. /package/{template → template-vite}/lib/dedicated-search-url.ts +0 -0
  420. /package/{template → template-vite}/lib/dev-log.test.ts +0 -0
  421. /package/{template → template-vite}/lib/dev-log.ts +0 -0
  422. /package/{template → template-vite}/lib/discovery-hub.ts +0 -0
  423. /package/{template → template-vite}/lib/editable-target.ts +0 -0
  424. /package/{template → template-vite}/lib/exxat-palette.json +0 -0
  425. /package/{template → template-vite}/lib/exxat-palette.ts +0 -0
  426. /package/{template → template-vite}/lib/floating-sheet-panel.ts +0 -0
  427. /package/{template → template-vite}/lib/full-hub-supported-views.ts +0 -0
  428. /package/{template → template-vite}/lib/hub-connected-view-renderers.ts +0 -0
  429. /package/{template → template-vite}/lib/initials-from-name.ts +0 -0
  430. /package/{template → template-vite}/lib/library-authoring.ts +0 -0
  431. /package/{template → template-vite}/lib/library-dedicated-search.ts +0 -0
  432. /package/{template → template-vite}/lib/library-hub-search.ts +0 -0
  433. /package/{template → template-vite}/lib/library-nav.ts +0 -0
  434. /package/{template → template-vite}/lib/library-recent-searches.ts +0 -0
  435. /package/{template → template-vite}/lib/library-supported-views.ts +0 -0
  436. /package/{template → template-vite}/lib/list-hub-supported-views.ts +0 -0
  437. /package/{template → template-vite}/lib/list-page-table-properties.ts +0 -0
  438. /package/{template → template-vite}/lib/list-status-badges.ts +0 -0
  439. /package/{template → template-vite}/lib/logo-dev.ts +0 -0
  440. /package/{template → template-vite}/lib/mailto.ts +0 -0
  441. /package/{template → template-vite}/lib/mock/dashboard.ts +0 -0
  442. /package/{template → template-vite}/lib/mock/library-folders.ts +0 -0
  443. /package/{template → template-vite}/lib/mock/library-header-collaborators.ts +0 -0
  444. /package/{template → template-vite}/lib/mock/library-inspector.ts +0 -0
  445. /package/{template → template-vite}/lib/mock/library-kpi.ts +0 -0
  446. /package/{template → template-vite}/lib/mock/library.ts +0 -0
  447. /package/{template → template-vite}/lib/mock/navigation.tsx +0 -0
  448. /package/{template → template-vite}/lib/motion-ui.ts +0 -0
  449. /package/{template → template-vite}/lib/product-brand.ts +0 -0
  450. /package/{template → template-vite}/lib/raf-throttle.ts +0 -0
  451. /package/{template → template-vite}/lib/row-height.ts +0 -0
  452. /package/{template → template-vite}/lib/sidebar-state-cookie.ts +0 -0
  453. /package/{template → template-vite}/lib/stock-portrait.ts +0 -0
  454. /package/{template → template-vite}/lib/table-state-lifecycle.ts +0 -0
  455. /package/{template → template-vite}/lib/utils.test.ts +0 -0
  456. /package/{template → template-vite}/lib/utils.ts +0 -0
  457. /package/{template → template-vite}/public/.gitkeep +0 -0
  458. /package/{template → template-vite}/public/Illustration/Rotation.svg +0 -0
  459. /package/{template → template-vite}/public/avatars/user.svg +0 -0
  460. /package/{template/public → template-vite/public/favicon}/favicon.ico +0 -0
  461. /package/{template/app → template-vite/public}/favicon.ico +0 -0
  462. /package/{template → template-vite}/public/folders/icons8-folder-windows-11.svg +0 -0
  463. /package/{template → template-vite}/public/logos/exxat-one.svg +0 -0
  464. /package/{template → template-vite}/public/logos/exxat-prism.svg +0 -0
  465. /package/{template → template-vite}/public/mock-schools/emory.svg +0 -0
  466. /package/{template → template-vite}/public/mock-schools/rush.svg +0 -0
  467. /package/{template → template-vite}/scripts/fontawesome-subset-audit.mjs +0 -0
  468. /package/{template → template-vite}/scripts/pm2-startup-macos.sh +0 -0
  469. /package/{template → template-vite}/skills-lock.json +0 -0
  470. /package/{template/app/(app)/columns/page.tsx → template-vite/src/pages/columns.tsx} +0 -0
  471. /package/{template/app/(app)/library/find/page.tsx → template-vite/src/pages/library/find.tsx} +0 -0
  472. /package/{template/app/(app)/library/page.tsx → template-vite/src/pages/library/index.tsx} +0 -0
  473. /package/{template/app/(app)/library/list/page.tsx → template-vite/src/pages/library/list.tsx} +0 -0
  474. /package/{template/app/(app)/settings/page.tsx → template-vite/src/pages/settings.tsx} +0 -0
  475. /package/{template/app/(app)/tokens-themes/page.tsx → template-vite/src/pages/tokens-themes.tsx} +0 -0
  476. /package/{template → template-vite}/stores/app-store.ts +0 -0
  477. /package/{template → template-vite}/types/react-payment-inputs.d.ts +0 -0
@@ -0,0 +1,282 @@
1
+ ---
2
+ name: exxat-accessibility
3
+ description: WCAG 2.x / ARIA checklist for Exxat DS — tablists, touch targets, contrast, and audit follow-ups. Use when fixing axe/Deque issues, building nav or tab UIs, or reviewing accessibility.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Exxat DS — accessibility checklist
8
+
9
+ Standard target: **WCAG 2.1 Level AA** (and 2.2 where noted).
10
+
11
+ **Canonical for agents (MUST/MUST NOT, checklist):** `apps/web/AGENTS.md` **§8** in the repo (same content summarized there; this skill stays the detailed checklist + product tokens).
12
+
13
+ ## ARIA roles & structure (SC 1.3.1)
14
+
15
+ - **`role="tablist"`** may only contain **`role="tab"`** (or elements that resolve to tabs). Do **not** place `role="button"`, menus (`aria-haspopup`), or ad-hoc controls **inside** the same `tablist` container.
16
+ - **Composite view switchers** (tabs + per-tab settings menu + remove control): use **`role="toolbar"`** with **`aria-label`**, and **`aria-pressed`** on view toggle buttons instead of misusing `tab`/`tablist`.
17
+ - Prefer **`<button type="button">`** over **`span role="button"`** for clickable icons (keyboard + AT).
18
+
19
+ ## Touch targets (WCAG 2.2 AA — 2.5.8 Target Size Minimum)
20
+
21
+ - Interactive controls (including icon-only chevrons and close icons) should be at least **24×24 CSS pixels**, or have **24px** spacing so their **24px** hit circles do not overlap adjacent targets.
22
+ - Use **`min-h-6 min-w-6`** (or `size-6`) with centered icons; avoid `size-4` (16px) for sole click targets.
23
+
24
+ ## Icons that communicate information — always have a text alternative (SC 1.1.1, 3.3.2, 2.4.6)
25
+
26
+ This rule covers **every icon that carries meaning**, not only icon-only buttons. FA glyphs, inline SVGs, avatar placeholders, trend arrows, status dots, chart-legend squares, calendar/clock/pin icons in cells — if the icon **tells the user something**, that something MUST be reachable by screen readers AND discoverable to sighted users who don't recognise the glyph.
27
+
28
+ There are three cases. Pick the one that matches the icon's context:
29
+
30
+ ### Case A — Decorative icon next to text that already names it
31
+
32
+ When the icon sits adjacent to a visible text label that already carries the meaning, the icon is **decorative**. It MUST be `aria-hidden` and MUST NOT carry `aria-label` (screen readers would announce the meaning twice).
33
+
34
+ ```tsx
35
+ <span className="flex items-center gap-1.5">
36
+ <i className="fa-light fa-calendar-days" aria-hidden />
37
+ <span>12/14/2025 – 12/20/2025</span>
38
+ </span>
39
+
40
+ <Button>
41
+ <i className="fa-light fa-plus" aria-hidden />
42
+ <span>New placement</span>
43
+ </Button>
44
+ ```
45
+
46
+ No tooltip needed — the text is the alt. This is the default in table cells, buttons with text labels, breadcrumbs, badges, menu items.
47
+
48
+ ### Case B — Informational icon standing alone (no adjacent label)
49
+
50
+ When the icon is the **only visible carrier** of the information — e.g.:
51
+
52
+ - `fa-calendar-days` in a compact table column header meaning "date range"
53
+ - `fa-clock` meaning "updated at" or "time remaining"
54
+ - `fa-location-dot` meaning "site" / "location"
55
+ - `fa-graduation-cap` meaning "student"
56
+ - trending arrow in a KPI card (↑ / ↓)
57
+ - status dot (a coloured circle meaning "on track / at risk / blocked")
58
+ - icon-only legend key on a chart
59
+ - `fa-paperclip` meaning "has attachments"
60
+ - `fa-lock` meaning "restricted"
61
+
62
+ …the icon MUST:
63
+
64
+ 1. Announce itself to AT via **`role="img"` + `aria-label`** on a wrapping element (span/div), OR inherit the name from a labelled ancestor via `aria-labelledby`.
65
+ 2. Expose a visible **`Tooltip`** so sighted mouse/keyboard users who don't recognise the glyph learn the meaning.
66
+
67
+ ```tsx
68
+ <Tooltip>
69
+ <TooltipTrigger asChild>
70
+ {/* Non-interactive: wrap in a span with role="img".
71
+ tabIndex={0} so the tooltip opens on keyboard focus, not just hover. */}
72
+ <span
73
+ role="img"
74
+ aria-label="Placement date range"
75
+ tabIndex={0}
76
+ className="inline-flex size-6 items-center justify-center rounded-md text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
77
+ >
78
+ <i className="fa-light fa-calendar-days" aria-hidden />
79
+ </span>
80
+ </TooltipTrigger>
81
+ <TooltipContent side="top">Placement date range</TooltipContent>
82
+ </Tooltip>
83
+ ```
84
+
85
+ Rules:
86
+ 1. `TooltipContent` text MUST match the `aria-label`.
87
+ 2. The inner `<i>` / `<svg>` is always `aria-hidden`; the accessible name lives on the wrapper.
88
+ 3. If a visible text label could fit, **prefer Case A** (add the label) over tooltip-only.
89
+ 4. Target/focus size still **≥ 24×24 CSS px** so keyboard users can focus the icon reliably.
90
+ 5. Status dots MUST additionally carry a **text or shape cue** (not colour alone — SC 1.4.1). E.g. pair a coloured dot with a status label nearby, or differentiate by icon shape (`circle-check` vs `triangle-exclamation` vs `circle-xmark`).
91
+
92
+ ### Case C — Interactive icon-only button / link
93
+
94
+ Any button or link whose visible content is a **single icon** — close (`×`), chevron, overflow (`⋯`), sort direction, filter chip dismiss, copy-to-clipboard, Ask Leo toggle, expand/collapse, row actions — MUST carry BOTH:
95
+
96
+ 1. **`aria-label`** on the `<button>` / `<a>` (programmatic name for AT and axe).
97
+ 2. A wrapping **`Tooltip`** whose content repeats the same name (plus a `Kbd` when a shortcut exists).
98
+
99
+ ```tsx
100
+ import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"
101
+ import { Kbd } from "@/components/ui/kbd"
102
+
103
+ <Tooltip>
104
+ <TooltipTrigger asChild>
105
+ <button
106
+ type="button"
107
+ aria-label="Close insight"
108
+ onClick={onClose}
109
+ className="inline-flex size-7 min-h-7 min-w-7 items-center justify-center rounded-md …"
110
+ >
111
+ <i className="fa-solid fa-xmark" aria-hidden />
112
+ </button>
113
+ </TooltipTrigger>
114
+ <TooltipContent side="top" className="flex items-center gap-1.5">
115
+ <span>Close</span>
116
+ <Kbd>Esc</Kbd>
117
+ </TooltipContent>
118
+ </Tooltip>
119
+ ```
120
+
121
+ Rules:
122
+ 1. `TooltipContent` MUST match or extend the `aria-label`.
123
+ 2. Inside the tooltip use the **default tile** `<Kbd>` (NOT `variant="bare"`).
124
+ 3. The inner `<i>` / `<svg>` MUST have `aria-hidden`.
125
+ 4. Click target **≥ 24×24 CSS px** (see Touch targets section).
126
+
127
+ ### Narrow exceptions (all cases)
128
+
129
+ - A chevron inside a labelled composite (`Select`, `Combobox`, `Breadcrumb` separator) where the parent already names the whole control or the glyph is purely structural — mark it `aria-hidden`.
130
+ - Drag handles inside a `dnd-kit` listener that reference a labelled ancestor via `aria-describedby`.
131
+ - Decorative icons that purely decorate (e.g. the Leo star next to an already-named "Ask Leo" button) — `aria-hidden`, no tooltip.
132
+
133
+ ### Decision tree
134
+
135
+ ```
136
+ Icon appears somewhere →
137
+ Is there adjacent visible text that already names the meaning?
138
+ YES → Case A: aria-hidden, no tooltip. Done.
139
+ NO → Is the icon the visible content of a button/link?
140
+ YES → Case C: aria-label on the control + Tooltip.
141
+ NO → Case B: span[role="img", aria-label, tabIndex=0] + Tooltip.
142
+ ```
143
+
144
+ **When in doubt: add the accessible name + tooltip.** Silence is never correct for an icon that means something.
145
+
146
+ ## Keyboard shortcuts inside buttons — `<Kbd variant="bare">` only
147
+
148
+ The `Kbd` primitive (`@/components/ui/kbd`) has two variants:
149
+
150
+ | Variant | Where it goes | Chrome |
151
+ |---------|---------------|--------|
152
+ | `tile` (default) | Inside `TooltipContent`, menu `shortcut=` slots, standalone surfaces | Background + border, fixed contrast |
153
+ | `bare` | Inline **inside** a `Button` (primary, secondary, wizard Next/Back/Submit) | No background, no border, inherits `currentColor` at 70 % opacity |
154
+
155
+ Inside a filled/solid button the `tile` variant looks like a pasted-on patch (wrong background color against the button fill). Always glue multi-key chords into **one** bare kbd:
156
+
157
+ ```tsx
158
+ // ✅ correct — bare inside a button
159
+ <Button>
160
+ <span>Ask Leo</span>
161
+ <KbdGroup className="ml-1.5">
162
+ <Kbd variant="bare">⌘⌥K</Kbd>
163
+ </KbdGroup>
164
+ </Button>
165
+
166
+ // ❌ wrong — tile variant on a primary button
167
+ <Button>
168
+ <span>Ask Leo</span>
169
+ <KbdGroup className="ml-1.5">
170
+ <Kbd>⌘</Kbd><Kbd>⌥</Kbd><Kbd>K</Kbd>
171
+ </KbdGroup>
172
+ </Button>
173
+ ```
174
+
175
+ Reference pattern: `components/new-placement-form.tsx` (Next = `<Kbd variant="bare">{mod}⏎</Kbd>`, Back = `<Kbd variant="bare">{mod}{alt}←</Kbd>`). See `.cursor/rules/exxat-kbd-shortcuts.mdc` for the full shortcut table.
176
+
177
+ ## Color (SC 1.4.3 / 1.4.11)
178
+
179
+ - **Normal text** (including small badge labels): **≥ 4.5:1** against its background.
180
+ - **UI components** (borders, focus rings): **≥ 3:1** where required by 1.4.11.
181
+ - **Muted text on tinted surfaces** (e.g. sidebar): use tokens mixed against **`--sidebar`**, not only against `--background` (see `--sidebar-section-label-foreground`).
182
+
183
+ ## Sidebar badges (design tokens)
184
+
185
+ - **Count / numeric (sidebar):** **red** (`bg-red-600` + white). Placement toolbar counts use **status colors** (see below).
186
+ - **“New”:** use **brand** (`bg-brand` + `text-brand-foreground`).
187
+ - **“Beta”:** bright yellow fill + **dark** text (`bg-yellow-400` + `text-yellow-950`) for contrast.
188
+
189
+ ## Placements toolbar count pills (`filterId`)
190
+
191
+ - **`all`:** slate
192
+ - **`upcoming`:** amber
193
+ - **`ongoing`:** blue
194
+ - **`completed`:** emerald
195
+ - Unknown `filterId` falls back to **all** (slate).
196
+
197
+ ## Form fields — format hints MUST be persistent (SC 3.3.2, 1.3.1)
198
+
199
+ Placeholder text disappears on focus, is low-contrast by default, and is not reliably announced by assistive tech. For any field with a **required format**, render the format as **persistent helper text** tied to the input via `aria-describedby` — never rely on the placeholder alone.
200
+
201
+ **When this applies (non-exhaustive):**
202
+
203
+ - Dates, times, date ranges (even when a picker is present — the typed-format fallback must show the mask).
204
+ - Phone / fax, country-specific formats.
205
+ - Currency, GPA, percentages, hours, durations, unit-bearing numbers ("hrs/wk", "USD").
206
+ - IDs with a pattern (Student ID `STU-YYYY-####`, NPI, license #).
207
+ - Email / URL where a domain or protocol is required.
208
+ - Credit hours, scores, weights — anything whose scale is not obvious from the label.
209
+
210
+ **Pattern — use `FormDescription` from shadcn `Form`:**
211
+
212
+ ```tsx
213
+ <FormField name="dob" render={({ field }) => (
214
+ <FormItem>
215
+ <FormLabel>Date of birth<span aria-hidden="true"> *</span></FormLabel>
216
+ <FormControl>
217
+ <Input {...field} inputMode="numeric" placeholder="MM/DD/YYYY" />
218
+ </FormControl>
219
+ {/* Persistent — announced via aria-describedby, survives focus */}
220
+ <FormDescription>MM/DD/YYYY</FormDescription>
221
+ <FormMessage />
222
+ </FormItem>
223
+ )} />
224
+ ```
225
+
226
+ **Rules:**
227
+
228
+ 1. The format MUST appear as visible text **outside** the placeholder — `FormDescription`, a helper `<p>` with `id` referenced by `aria-describedby`, or an adjacent `<small>`.
229
+ 2. Placeholder MAY mirror the format as extra affordance, but is never the sole source.
230
+ 3. Prefer a **picker primitive** over free-text where one exists: `DatePickerField` for dates, `Select` for enumerated values, masked input for phone/IDs.
231
+ 4. Required marker (`*`) is decorative — always pair with `aria-required="true"` on the input.
232
+ 5. Error messages (`FormMessage`) replace description announcement while active; keep the description concise so the combined `aria-describedby` string stays readable.
233
+ 6. Units belong in the description, not the label suffix, when they vary by context (e.g. "Out of 4.0" under GPA rather than in the label).
234
+
235
+ ## High-Contrast modes
236
+
237
+ There are **two** HC paths in this app. Fix both or the bar will still look broken in one of them:
238
+
239
+ 1. **App HC theme — `html[data-contrast="high"]`** (user toggle in Settings). Use the **`hc:`** Tailwind variant defined in `globals.css` (line 22: `@custom-variant hc (&:is([data-contrast="high"] *));`). This is what the user sees when they switch to High Contrast in-app.
240
+ 2. **OS / browser forced-colors** — Windows High Contrast, some Linux DEs, `prefers-contrast: more`. Use `forced-colors:` variants mapping to system colors (`Canvas`, `CanvasText`, `Highlight`, `HighlightText`, `GrayText`).
241
+
242
+ ### Why tokens collapse in HC
243
+
244
+ In HC themes many surfaces resolve to near-identical values (e.g. `bg-muted` ≈ `bg-background` in HC dark, `bg-brand` gets desaturated). Any UI encoding state **only via fill/track color** (progress bars, quota gauges, chart ranges, meter pills, dashed reference lines, status pills) flattens into a single rectangle. The fix is to force distinct contrast in HC:
245
+
246
+ - **Track / container:** `hc:border hc:border-border hc:bg-background` + `forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]`
247
+ - **Active fill / progress:** `hc:bg-foreground` + `forced-colors:bg-[Highlight]`
248
+ - **Dashed marker / divider:** `hc:border-foreground` + `forced-colors:border-[CanvasText]`
249
+ - **Pill on colored bg:** invert to `hc:bg-background hc:text-foreground hc:border hc:border-foreground` + `forced-colors:bg-[Canvas] forced-colors:text-[CanvasText] forced-colors:border-[CanvasText]`
250
+ - **Disabled state:** `forced-colors:text-[GrayText]`
251
+
252
+ Never rely on `box-shadow` alone for separation in HC — shadows are suppressed; pair with a border.
253
+
254
+ Windows HC mode (and the CSS `forced-colors: active` media query) strips every custom `background-color`, `border-color`, and `box-shadow` and remaps text/icons to a small palette of system colors. Progress bars, pills, chart legends, avg-markers, and any "color-only" indicator collapse into a single flat rectangle if you rely purely on tokens like `bg-brand` or `bg-muted`.
255
+
256
+ **Rule:** for every UI that encodes state **via fill / track color** (progress bars, quota gauges, chart ranges, meter pills, status pills, dashed reference lines):
257
+
258
+ 1. Keep the default `bg-*` tokens for light/dark themes.
259
+ 2. Add `forced-colors:` variants mapping to system colors:
260
+ - **Track / container:** `forced-colors:bg-[Canvas] forced-colors:border forced-colors:border-[CanvasText]`
261
+ - **Active fill / progress:** `forced-colors:bg-[Highlight]` (text on it → `forced-colors:text-[HighlightText]`)
262
+ - **Dashed marker / divider:** `forced-colors:border-[CanvasText]`
263
+ - **Tooltip / pill on colored bg:** `forced-colors:bg-[Canvas] forced-colors:text-[CanvasText] forced-colors:border forced-colors:border-[CanvasText]`
264
+ - **Disabled state:** `forced-colors:text-[GrayText]`
265
+ 3. Never rely on `box-shadow` alone for separation in HC — shadows are suppressed; pair with a border.
266
+
267
+ Reference fix: `components/dashboard-quota-progress-card.tsx` `StudentScoreProgressRow` — track → `Canvas`, fill → `Highlight`, avg-marker pill → `Canvas` + `CanvasText` border.
268
+
269
+ ## Audit rules to track (examples)
270
+
271
+ | Severity | Rule | Criterion |
272
+ |----------|------|-----------|
273
+ | Critical | Certain ARIA roles must contain particular children | 1.3.1 |
274
+ | Serious | Touch targets must be ≥24px or spaced accordingly | 2.5.8 |
275
+
276
+ When fixing, re-run **axe** or your preferred checker on the **Placements** page after changing the Views toolbar.
277
+
278
+ ## Charts (keyboard exploration)
279
+
280
+ - **Chart region:** Product charts that support arrow-key exploration use **`ChartFigure`** (`role="application"`, focus ring on the chart container, click-or-Tab to focus per `charts-overview`).
281
+ - **Selected datum:** Prefer **visible** feedback that matches the **`/dashboard` gallery** — Recharts **`activeBar`** + **`activeIndex`** for bars, **`activeShape`** + **`activeIndex`** for pies — via **`@/lib/chart-keyboard-selection`**. Avoid relying on **opacity-only** dimming as the only “selected” indicator; pair with ring/stroke so focus is perceivable (WCAG **2.4.7 Focus Visible** where applicable).
282
+ - **Tables under charts:** **`ChartDataTable`** (`sr-only`) provides an equivalent programmatic structure for screen-reader users.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: exxat-board-cards
3
+ description: >-
4
+ Build and maintain kanban board cards in Exxat DS — ListPageBoardCard shell, title/avatar/badge/body hierarchy, BoardCardTwoLineBlock, list-status-badges, ListPageBoardTemplate. Use when adding board view, Team/Compliance/Placements cards, or status chips on cards.
5
+ user-invocable: true
6
+ ---
7
+
8
+ # Exxat DS — board (kanban) cards
9
+
10
+ **Canonical MUST/MUST NOT:** **`apps/web/AGENTS.md` §4.4** (or **`./AGENTS.md`** when the workspace is the `apps/web/` app folder only). **Claude:** **`.claude/skills/exxat-board-cards/SKILL.md`** (repo root) — same body as this file. This skill is the **how-to**; the handbook stays the contract.
11
+
12
+ ## When to use this skill
13
+
14
+ - Adding or changing **`viewType === "board"`** on a **`ListPageTemplate`** hub.
15
+ - Building a new **`EntityBoardView`** or **`renderCard`** for **`ListPageBoardTemplate`**.
16
+ - Aligning **Team** / **Compliance** board cards with **Placements**.
17
+ - Wiring **status** on cards so it matches **table** and **list** views.
18
+
19
+ ## Shell: `ListPageBoardCard`
20
+
21
+ Import from **`@/components/data-views/list-page-board-card`**:
22
+
23
+ | Part | Component | Role |
24
+ |------|-----------|------|
25
+ | Outer | **`ListPageBoardCard`** | **`Card` `size="sm"`**; pass **`onClick`**, **`isNew`**, **`style`** (e.g. conditional row bg) like **`BoardPlacementCard`**. |
26
+ | Header wrapper | **`ListPageBoardCardHeader`** | Vertical rhythm inside the card. |
27
+ | Title row | **`ListPageBoardCardTitleRow`** | **`title`** + optional **`trailing`** + **`titleClassName`** (`truncate`, `line-clamp-2`, **`lineClampClass`**). |
28
+ | Avatar | **`ListPageBoardCardAvatar`** | **`initials`** string; **`size-7`**, **`--avatar-initials-bg` / `--avatar-initials-fg`**. |
29
+ | Status / tags | **`ListPageBoardCardBadgeRow`** | **`ListHubStatusBadge`** **`surface="board"`** (+ maps from **`lib/list-status-badges.ts`**) — **not** raw status text or one-off **`Badge`** markup in the body. |
30
+ | Body | **`ListPageBoardCardBody`** | Icon rows and two-line blocks (facts). |
31
+ | Hint | **`ListPageBoardCardSecondary`** | Empty states / helper copy. |
32
+
33
+ ## Primitives: `board-card-primitives.tsx`
34
+
35
+ - **`BoardCardTwoLineBlock`** — icon + **primary** line (`font-medium text-foreground`) + optional **secondary** (muted). Omit **`line2`** for a single-line fact row.
36
+ - **`BoardCardIconRow`** — when mirroring rich **table cells** (see **`BoardPlacementCard`**).
37
+ - **`lineClampClass`** — pass into title or cell wrappers when density options apply.
38
+ - **`BoardNewCardPlaceholder`** — column chrome for **`ListPageBoardTemplate`**.
39
+
40
+ Prefer **two-line blocks** for stacked **primary / secondary** facts so cards match Placements’ visual rhythm.
41
+
42
+ ## Status badges (list hubs: Team, Compliance, Library, …)
43
+
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
+ - **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
+ - Use the **same** maps everywhere for that entity so copy and colors never drift.
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 / Library).
49
+
50
+ ## Avatar when mock has no `initials`
51
+
52
+ Use **`initialsFromDisplayName`** from **`lib/initials-from-name.ts`** (e.g. compliance **owner** name).
53
+
54
+ ## Column template (simple hubs)
55
+
56
+ **`ListPageBoardTemplate`** (`list-page-board-template.tsx`) — define **`ListPageBoardColumnDef<T>`** (`id`, `label`, `description`, **`filter`**, **`renderCard`**). Placements uses a **custom** board with richer headers; new hubs usually start here.
57
+
58
+ ## Placements
59
+
60
+ **`BoardPlacementCard`** (`placement-board-card.tsx`) — domain-specific (lifecycle tabs, **`ColumnDef`**, conditional background). It **still** composes **`ListPageBoardCard`** parts and primitives; copy that structure for new dense entities.
61
+
62
+ ## Checklist
63
+
64
+ - [ ] **`ListPageBoardCard`** (not one-off button/card markup).
65
+ - [ ] Title row; avatar when product shows a person/owner chip.
66
+ - [ ] Status in **`ListPageBoardCardBadgeRow`** with **`ListHubStatusBadge`** **`surface="board"`** + **`list-status-badges`** (if entity has status).
67
+ - [ ] Body facts via **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`**.
68
+ - [ ] No **`uppercase`** on status **`Badge`** labels.
@@ -0,0 +1,20 @@
1
+ # Exxat DS — cards vs rows vs lists
2
+
3
+ Use when choosing **card grids**, **`DataTable`**, or **simple list rows** for a surface.
4
+
5
+ ## Read first
6
+
7
+ - **`docs/card-vs-rows-pattern.md`**
8
+ - **`.cursor/rules/exxat-card-vs-list-rows.mdc`**, **`exxat-data-tables.mdc`**, **`exxat-board-cards.mdc`**
9
+ - **`exxat-centralized-list-dataset.mdc`** — one `tableState.rows` for table + board + cards
10
+
11
+ ## Checklist
12
+
13
+ 1. **10+ homogeneous records + compare/sort/filter?** → **`DataTable`** + hub template.
14
+ 2. **Kanban / visual tiles / folders?** → **`ListPageBoardCard`** or **`ListPageViewFrame`** + card primitives.
15
+ 3. **Medium vertical list without full table chrome?** → List row pattern; **promote** to `DataTable` when density and parity demand it.
16
+
17
+ ## MUST NOT
18
+
19
+ - Card wall for the **primary** sortable hub that other hubs implement as **`DataTable`** without product reason.
20
+ - Second mock row source for cards vs table.
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: exxat-centralized-list-dataset
3
+ description: Single canonical dataset and derived row bag for Exxat list hubs — useTableState.rows across table/list/board/dashboard/folder/panel/tree, TablePropertiesDrawer wired to the same table state, shared maps for status/KPIs/inspector. Use when adding a hub view, detail panel, mock data, or fixing inconsistent counts between views.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Exxat DS — centralized dataset for list hubs
8
+
9
+ Goal: **one row model**, **one filtered bag** (`tableState.rows`), **one place for badge/KPI maps**, so **every** view tab and inspector matches after search/filters.
10
+
11
+ **Canon:** `apps/web/AGENTS.md` §4.1–§4.2, `docs/data-views-pattern.md`, `.cursor/rules/exxat-centralized-list-dataset.mdc`.
12
+
13
+ ---
14
+
15
+ ## 1. Canonical row module
16
+
17
+ - **`lib/mock/<entity>.ts`** — exported typed **`interface`** / type alias + **`ENTITY_ROWS`** (or equivalent) for mocks.
18
+ - Future API: replace **mock export** with **`fetch` + same TS shape**; views stay unchanged.
19
+ - **Extend fields once** (e.g. inspector-only columns): add optional props on the **shared** interface — avoid a parallel **`EntityInspectorRow`** that duplicates IDs.
20
+
21
+ ---
22
+
23
+ ## 2. Single table state → all views
24
+
25
+ | Surface | Data source |
26
+ |---------|-------------|
27
+ | Table | `useTableState` columns + rows |
28
+ | List / Board / Dashboard | **`tableState.rows`** |
29
+ | Folder grid | **`tableState.rows`** (+ folder metadata from **`lib/mock/<entity>-folders.ts`** keyed by **`folderId`**, not a second question list) |
30
+ | Panel / Finder | **`tableState.rows`** + grouping helpers |
31
+ | Tree + detail | **`tableState.rows`** for leaf items; folders from **folder mock**; selected question resolves **by id** from **`tableState.rows`** |
32
+
33
+ **Anti-pattern:** `import { ENTITY_ROWS } from "..."` inside a view component while the table uses **`useTableState`** — filters won’t match.
34
+
35
+ **Fix:** Pass **`tableState.rows`** (or setter/updater from parent) into child views; only use raw **`ENTITY_ROWS`** for **initial seed** into **`useTableState`**.
36
+
37
+ ---
38
+
39
+ ## 3. Table properties = same row shape
40
+
41
+ - **`buildXColumns(rows)`** — column defs use **`accessorKey`** / **`cell`** consistent with the entity interface.
42
+ - **`TablePropertiesDrawer`** — **`columns`**, **`columnVisibility`**, density — all tied to the **`DataTable`** instance that shares **`useTableState`**.
43
+ - **`currentView`** + **`onViewChange`** — from **`ListPageTemplate`** **`renderContent`** (**§4.2**).
44
+
45
+ ---
46
+
47
+ ## 4. KPIs, dashboards, command palette
48
+
49
+ - **`entityKpiMetrics(rows)`** / **`entityTopicInsight(rows)`** — take **`tableState.rows`** (filtered).
50
+ - Dashboard charts on the hub — same **`rows`** prop as **`KeyMetrics`**.
51
+ - ⌘K row search — index **`tableState.rows`** or shared mock through **`lib/command-menu-search-data.ts`** patterns — not a divergent copy (**`docs/command-menu-pattern.md`**).
52
+
53
+ ---
54
+
55
+ ## 5. Status, labels, icons
56
+
57
+ - **`DATA_LIST_VIEW_TILES`** / **`dataListViewLabel`** — single registry for tab labels/icons.
58
+ - Entity status — **`lib/list-status-badges.ts`** (**`ListHubStatusBadge`**) or one **`ENTITY_STATUS_*`** map next to mock data.
59
+ - **MUST NOT** hardcode **"Published"** / tint classes in three components when a map already exists.
60
+
61
+ ---
62
+
63
+ ## 6. Implementation checklist (new hub or new view)
64
+
65
+ - [ ] Exactly **one** primary row array shape in **`lib/mock/<entity>.ts`** (or API).
66
+ - [ ] **`useTableState`** seeded from that array; **child views** receive **`tableState.rows`** (or equivalent), **not** a fresh import of the mock list.
67
+ - [ ] Board / list / dashboard / folder / panel / tree **all** branch from the same client with shared **`rows`**.
68
+ - [ ] Inspector/detail resolves **`selectedId`** with **`rows.find(r => r.id === selectedId)`** (or passes the row object from selection).
69
+ - [ ] **`TablePropertiesDrawer`** receives **`currentView`** / **`onViewChange`** + column model from the **same** table.
70
+ - [ ] KPI/chart helpers invoked with **`tableState.rows`**.
71
+ - [ ] No second mock array unless **derived** (`useMemo(() => rows.filter(...), [rows])`).
72
+
73
+ ---
74
+
75
+ ## 7. Reference implementations
76
+
77
+ - **`components/placements-client.tsx`** + **`placements-table.tsx`** — Placements pattern.
78
+ - **`components/team-client.tsx`** + **`team-table.tsx`**.
79
+ - **`components/library-table.tsx`** — multiple **`DataListViewType`** branches sharing **`tableState`** / **`folders`** / **`items`**.
80
+
81
+ ---
82
+
83
+ ## 8. Centralized presentation (with the same dataset)
84
+
85
+ **Layout:** Non-table view branches wrap in **`ListPageViewFrame`** (constants in **`list-page-view-frame.tsx`**) — see **`.cursor/rules/exxat-list-page-view-shells.mdc`**.
86
+
87
+ **Components:** Prefer **`components/data-views/*`** primitives (**`FolderGridView`**, **`FinderPanelView`**, board template, etc.) with **`rows`** from **`tableState.rows`**, not a second import of **`ENTITY_ROWS`**.
88
+
89
+ **Rule:** **`.cursor/rules/exxat-centralized-list-dataset.mdc`** (presentation bullets §8–10).
90
+
91
+ ---
92
+
93
+ ## See also
94
+
95
+ - `.cursor/rules/exxat-centralized-list-dataset.mdc`
96
+ - `.cursor/rules/exxat-list-page-connected-views.mdc`
97
+ - `.cursor/rules/exxat-list-page-view-shells.mdc`
98
+ - `.cursor/rules/exxat-table-properties-drawer.mdc`
99
+ - `.cursor/rules/exxat-data-tables.mdc`
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: exxat-collaboration-access
3
+ description: Shared hub collaboration — PageHeader face rail, InviteCollaboratorsDrawer, library access vs directory role tags. Use when adding invite people, access roster, or collaboration variant headers.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Exxat DS — collaboration & access
8
+
9
+ **Handbook:** `apps/web/AGENTS.md` §4.7
10
+ **Narrative:** `apps/web/docs/collaboration-access-pattern.md`
11
+ **Cursor rule:** `.cursor/rules/exxat-collaboration-access.mdc`
12
+ **Related (Library folder scope + ⋯ Customize folder):** `.cursor/rules/exxat-library-hub-header.mdc` · `docs/library-hub-header-pattern.md`
13
+
14
+ ## Wiring checklist
15
+
16
+ 1. **`PageHeaderCollaborator`** — `name`, `email`, `access`, optional `roles[]`, avatar fields (`components/page-header.tsx`).
17
+ 2. **Mock/API** — `lib/mock/<entity>-header-collaborators.ts`; one shape for header + invite sheet.
18
+ 3. **Entity header** — `variant="collaboration"`; **Invite people** first in **⋯ More**; pass **`onAddCollaborator`** and **`onCollaboratorsOpen`**.
19
+ 4. **Hub client** — **`CollaborationAccessFlow`** (preferred) or `collaborators` + `inviteOpen` + **`InviteCollaboratorsDrawer`**; on invite, append to **`collaborators`**.
20
+ 5. **Access maps** — `lib/collaborator-access.ts` for Owner / Editor / Commenter / Viewer, invite options, and **`COLLABORATION_HEADER_ADD_LABEL`**.
21
+ 6. **Header** — empty roster → outline **Add collaborator**; non-empty → face rail; both open the invite sheet.
22
+ 7. **Invite sheet** — `InviteCollaboratorsDrawer`: export-style **`Sheet`**, combined email + access menu, grouped roster (name → email → role tags → access badge).
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
+
25
+ ## MUST NOT
26
+
27
+ - A second invite control beside a populated face rail.
28
+ - Per-hub access label forks or parallel collaborator types.
29
+ - Toast/snackbar for invite outcomes (**`exxat-no-toast.mdc`**).
30
+ - **`Select`** in **`InputGroupAddon`** for access inside the sheet.
31
+
32
+ ## Reference
33
+
34
+ - `components/collaboration-access-flow.tsx`, `components/library-page-header.tsx`, `components/library-client.tsx`
35
+ - `components/invite-collaborators-drawer.tsx`, `components/export-drawer.tsx`
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: exxat-dedicated-search-surfaces
3
+ description: >-
4
+ Dedicated search landing vs results split, URL composer, recents storage, and templates
5
+ (generic DedicatedSearch*). Use when adding a route pair like empty ?q= landing + ListPageTemplate results,
6
+ or wiring localStorage recents without hydration mismatches.
7
+ ---
8
+
9
+ # Dedicated search surfaces (Exxat DS)
10
+
11
+ ## When to use
12
+
13
+ - A hub exposes **two URL states** on the same feature: **landing** (no primary query / empty `?q=`) vs **results** (non-empty query drives `ListPageTemplate` / `DataTable`).
14
+ - You need an **AI-style composer** that **updates the URL** with `router.replace` and **does not** open Ask Leo.
15
+ - You need **recent searches** persisted in `localStorage` **without** SSR/client markup drift.
16
+
17
+ ## MUST
18
+
19
+ 1. **Hydration-safe recents** — **MUST NOT** read `localStorage` in `useState` initializers. Initial client paint **MUST** match the server (`[]` then `useEffect` sync). See **`DedicatedSearchRecents`**.
20
+ 2. **Generic names** — Reusable pieces live under **`DedicatedSearch*`** (`components/dedicated-search-*.tsx`, **`components/templates/dedicated-search-*-template.tsx`**, **`lib/dedicated-search-*.ts`**). Hub code passes **`patchSearchParams`**, **`recents` controller**, copy, and entity table — **MUST NOT** fork parallel “question search landing” components for a second hub.
21
+ 3. **Landing shell** — Use **`DedicatedSearchLandingTemplate`** (`ListPageViewFrame` + title + composer slot + optional trailing).
22
+ 4. **Results chrome** — Reuse **`DEDICATED_SEARCH_RESULTS_OUTER_CONTENT_CLASSNAME`** on the hub content wrapper and **`DedicatedSearchResultsHeaderChrome`** around page header + composer strip.
23
+ 5. **Namespaces recents** — Use **`createDedicatedSearchRecentsController(namespace, legacy?)`** from **`lib/dedicated-search-recents.ts`**. When replacing an existing storage key, pass **`legacy: { storageKey, eventName }`** so users do not lose saved rows.
24
+
25
+ ## MUST NOT
26
+
27
+ - Gate render on **`typeof window`** for the **first** paint of recents or landing bodies.
28
+ - Duplicate the landing vertical rhythm (`mt-*` between title, composer, recents) outside the template without updating the template for product-wide consistency.
29
+
30
+ ## References (apps/web)
31
+
32
+ | Piece | Path |
33
+ |-------|------|
34
+ | Landing template | `components/templates/dedicated-search-landing-template.tsx` |
35
+ | Results chrome | `components/templates/dedicated-search-results-template.tsx` |
36
+ | URL composer | `components/dedicated-search-url-composer.tsx` |
37
+ | Recents list | `components/dedicated-search-recents.tsx` |
38
+ | Recents storage factory | `lib/dedicated-search-recents.ts` |
39
+ | Optional default `q` patcher | `lib/dedicated-search-url.ts` |
40
+ | Library adapter (placeholders + patch) | `lib/library-dedicated-search.ts` |
41
+ | Library wiring | `components/library-client.tsx` |
42
+
43
+ ## Cursor rule
44
+
45
+ - **`.cursor/rules/exxat-dedicated-search-surfaces.mdc`**
@@ -0,0 +1,20 @@
1
+ # Exxat DS — drawer vs dialog
2
+
3
+ Use when choosing **Radix `Dialog` / `AlertDialog`** vs **floating `Sheet` panels** vs **route** for a flow.
4
+
5
+ ## Read first
6
+
7
+ - **`docs/drawer-vs-dialog-pattern.md`**
8
+ - **`AGENTS.md` §6.4** + **`docs/data-views-pattern.md`** (page vs drawer)
9
+ - **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**, **`exxat-page-vs-drawer.mdc`**
10
+
11
+ ## Checklist
12
+
13
+ 1. **Must the user see the hub while acting?** Yes → **`Sheet`** panel (properties, export, invite). No and short → **dialog**. Long / own URL → **route**.
14
+ 2. **Destructive confirm?** Prefer **dialog** (`AlertDialog`) unless the product explicitly keeps context in a sheet with the same safeguards.
15
+ 3. **Title + focus** — Dialog and sheet both need an accessible **title**; restore focus to trigger on close.
16
+
17
+ ## Repo references
18
+
19
+ - `TablePropertiesDrawer`, `ExportDrawer`, `InviteCollaboratorsDrawer` — floating **`Sheet`** (names end in “Drawer” but implementation is `Sheet` only).
20
+ - Delete / irreversible — dialog pattern, not toast (**`exxat-no-toast.mdc`**).