@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
@@ -1,262 +0,0 @@
1
- # Exxat DS — Voice and tone
2
-
3
- > The single biggest driver of "this feels like one product" — or "this feels random" — is **copy**. Empty states, errors, buttons, banners, validation, labels. This doc is the binding reference for all of it.
4
- >
5
- > **Audience:** designers writing copy, engineers shipping strings, AI agents drafting UI text. Reviewed in PR alongside `.cursor/rules/exxat-accessibility.mdc`.
6
-
7
- ---
8
-
9
- ## 1. Voice (the constant)
10
-
11
- Exxat copy is **clear, direct, and respectful**. We talk to grown professionals (faculty, admins) and to students. We never:
12
-
13
- - talk down (no "Oops!" / "Whoops" / "Uh-oh"),
14
- - over-celebrate ("Awesome!" / "Great job!" / 🎉),
15
- - guilt-trip ("You haven't… yet" / "Don't forget!"),
16
- - hide the cause behind metaphor ("Something went wrong" with no detail),
17
- - shout (sentence case everywhere; ALL CAPS only for system status acronyms).
18
-
19
- We **always**:
20
-
21
- - name the **thing**: the entity (placement, site, student), the action (Save, Invite, Export), the outcome (saved, sent, downloaded),
22
- - say what **changed** or what **the user can do next**,
23
- - use **present tense** (`Saving` / `Saved` / `Try again`), not past-aspirational (`We've saved your changes!`),
24
- - use **active voice** (`Exxat couldn't save the placement.` not `The placement could not be saved.`).
25
-
26
- ---
27
-
28
- ## 2. Tone (the variable)
29
-
30
- Tone shifts by surface. Same voice, different temperature.
31
-
32
- | Surface | Tone | Example |
33
- |---|---|---|
34
- | Primary action label | Imperative, 1–3 words | "Save", "Invite people", "Export" |
35
- | Empty state (filtered) | Helpful, names the filter | "No placements match the current filters. Clear them to see all 247." |
36
- | Empty state (no data ever) | Welcoming, names the next step | "No placements yet. Add your first one to get started." |
37
- | Inline validation | Neutral, says the rule | "Use the format MM/DD/YYYY." |
38
- | Banner (informational) | Calm, explains why | "Read-only — this term has ended." |
39
- | Banner (warning) | Direct, says the consequence | "Saving will overwrite 3 students' assignments." |
40
- | Banner (error) | Honest, names the failure + next step | "Exxat couldn't reach the placement service. Try again, or check your network." |
41
- | Dialog (destructive confirm) | Specific, names the object | "Delete placement P-2026-014? This cannot be undone." |
42
- | Microcopy (helper, hints) | Brief, format-only | "Out of 4.0", "MM/DD/YYYY" |
43
-
44
- ---
45
-
46
- ## 3. Buttons
47
-
48
- | Pattern | Use | Example |
49
- |---|---|---|
50
- | **Sentence case** | Always | `Save`, `Invite people`, `Export selection` |
51
- | **Title case** | Never | ~~`Save Changes`~~ → `Save changes` |
52
- | **Verb-first imperative** | Primary actions | `Save`, `Send invite`, `Create placement`, `Download CSV` |
53
- | **No "Please"** | The button is the request | ~~`Please save`~~ → `Save` |
54
- | **Don't repeat the field name** | Inline context is enough | ~~`Save changes to placement`~~ → `Save` |
55
- | **"Cancel" not "Discard"** | Cancel returns to a safe state; Discard is only for destructive draft loss | dialog primary = `Save changes`; cancel = `Cancel` |
56
- | **Loading verb** | When in-flight, swap to `…` form, preserve width | `Save` → `Saving…` |
57
- | **Disabled tooltip** | Always say why | "Add at least one collaborator first." |
58
-
59
- ### Don't
60
-
61
- | ❌ Don't | ✅ Do |
62
- |---|---|
63
- | `Click here` | `Open placement` |
64
- | `OK` | `Save`, `Got it`, `Continue` (the actual outcome) |
65
- | `Submit` | `Send invite`, `Save changes`, `Create placement` |
66
- | `Yes` / `No` in destructive dialogs | `Delete placement`, `Cancel` |
67
- | `Loading…` for >2 s with no detail | `Loading placements…` (name the thing) |
68
-
69
- ---
70
-
71
- ## 4. Empty states
72
-
73
- There are **three** kinds. The copy is different for each.
74
-
75
- ### 4a. Filter-empty ("zero matches")
76
-
77
- The dataset has rows; the active filters hide all of them. Tell the user the filters are responsible and offer to clear them.
78
-
79
- ```
80
- No placements match the current filters.
81
- [Clear filters]
82
- ```
83
-
84
- Rules:
85
-
86
- - Name the entity ("placements", not "items").
87
- - Mention "the current filters" — not just "no results".
88
- - Surface a **Clear filters** action if the toolbar has any filters applied.
89
- - If the search box is the only filter, the message becomes `No placements match "<query>".` (quote the query exactly).
90
-
91
- ### 4b. True-empty ("no data ever")
92
-
93
- The dataset is empty for this user / scope. Tell them what to do next.
94
-
95
- ```
96
- No placements yet.
97
- Add your first one to start tracking student rotations.
98
- [New placement]
99
- ```
100
-
101
- Rules:
102
-
103
- - One-line headline + one-line context.
104
- - The CTA is the same imperative used elsewhere in the app — don't invent "Start now" for an empty state.
105
- - If creation requires a precursor ("Add at least one site first"), say so and link to the precursor.
106
-
107
- ### 4c. Permission-empty ("not allowed to see")
108
-
109
- The user can't see anything because of access. Don't show counts; don't suggest creating.
110
-
111
- ```
112
- You don't have access to this hub.
113
- Ask a coordinator to invite you with Viewer or higher access.
114
- ```
115
-
116
- Rules:
117
-
118
- - Name the access level required.
119
- - Don't show row counts the user can't see.
120
- - Don't expose the existence of restricted records by mentioning "0 of N hidden".
121
-
122
- ---
123
-
124
- ## 5. Errors
125
-
126
- Errors must answer three questions: **What happened? Why? What can the user do?**
127
-
128
- | Element | Required? | Example |
129
- |---|---|---|
130
- | Subject | required | `Exxat couldn't save the placement.` |
131
- | Cause | when knowable | `The site was deleted while you were editing.` |
132
- | Next step | required | `Choose another site or refresh.` |
133
-
134
- ### Don't
135
-
136
- - `Something went wrong.` — every word is empty. Replace with the subject + cause.
137
- - `Error 500: Internal Server Error.` — leak the trace ID to logs, not the user. User-facing: "Exxat had a problem. Try again in a moment."
138
- - Toasts for errors. Use `SystemBanner` (route-level) or inline `FormMessage` (field-level). See [`exxat-no-toast.mdc`](../../../.cursor/rules/exxat-no-toast.mdc).
139
-
140
- ### Field-level (`FormMessage`)
141
-
142
- Match the rule, not the field name. Keep under 60 chars.
143
-
144
- | Rule | Message |
145
- |---|---|
146
- | Required | `This is required.` |
147
- | Format (date) | `Use MM/DD/YYYY.` |
148
- | Format (phone) | `Use +1 (555) 555-0100.` |
149
- | Range (number) | `Enter a number between 0 and 4.0.` |
150
- | Unique | `That ID is already in use.` |
151
- | Server | `Exxat couldn't save this field. Try again.` |
152
-
153
- ---
154
-
155
- ## 6. Banners
156
-
157
- We use **persistent banners** (not toasts) for all product feedback. See [`exxat-no-toast.mdc`](../../../.cursor/rules/exxat-no-toast.mdc) and `LocalBanner` / `SystemBanner` in `packages/ui`.
158
-
159
- | Variant | Use | Lead with |
160
- |---|---|---|
161
- | `info` | Read-only / scope notice / "you're viewing X" | The fact: "Read-only — this term has ended." |
162
- | `success` | Confirmed state change (long-lived; toasts forbidden) | The outcome: "Invite sent to 3 collaborators." |
163
- | `warning` | Action will cause consequence | The consequence: "Saving will overwrite 3 students' assignments." |
164
- | `error` | Recoverable failure | The subject + next step: "Exxat couldn't reach the placement service. Try again." |
165
- | `destructive` | Pending destructive change | The object + reversibility: "This term will be archived in 7 days. Restore now to keep it." |
166
-
167
- Rules:
168
-
169
- - One banner per region. If two compete, the higher-severity wins.
170
- - Pair a banner with an **action** when there's something the user can do (`[Restore]`, `[Try again]`, `[Open log]`).
171
- - No exclamation marks. The variant color carries the tone.
172
-
173
- ---
174
-
175
- ## 7. Status badges
176
-
177
- Status labels live in [`lib/list-status-badges.ts`](../lib/list-status-badges.ts) and are shared across every surface (table, board card, list row). Don't fork labels per page.
178
-
179
- | Type | Conventions | Example |
180
- |---|---|---|
181
- | Lifecycle | Title case, single word where possible | `Active`, `Draft`, `Archived`, `Pending` |
182
- | Outcome | Past-tense verb when an action completed | `Approved`, `Rejected`, `Sent`, `Skipped` |
183
- | Risk | Plain language, no jargon | `On track`, `At risk`, `Blocked` |
184
- | Mandatory pairing | Color **AND** icon (WCAG 1.4.1 — never color alone) | green check, amber triangle, red circle-x |
185
-
186
- Never: `OK`, `N/A`, `?` as a status label. Show the actual lifecycle state or `Unknown` with a tooltip describing how to resolve it.
187
-
188
- ---
189
-
190
- ## 8. KPI copy (the highest-stakes copy in the product)
191
-
192
- KPIs read top-down: **label → value → trend chip → description**. The reader is scanning, not reading. Each line earns its place.
193
-
194
- | Field | Rule | Example |
195
- |---|---|---|
196
- | `label` | Sentence case noun phrase. ≤ 24 chars. No "Total" prefix (the value is total by default). | `Active placements`, `New invites`, `Compliance issues` |
197
- | `value` | The number. Use `tabular-nums`. Round to a sensible precision. Suffix unit (`%`, `hrs`) without space when natural. | `247`, `12 %`, `38 hrs` |
198
- | `delta` | The **count** of change. Empty (`""` or `0`) hides the chip entirely. Never prose. | `+12`, `-3`, `+8 %` |
199
- | `description` | The caption beneath the value row. Prose explaining what the number is or how it splits. Never the delta. | `vs last week`, `across 4 sites`, `left + right` |
200
-
201
- See [`exxat-kpi-trends.mdc`](../../../.cursor/rules/exxat-kpi-trends.mdc) for the trend-polarity rules (when up means bad, set `trendPolarity: "lower_is_better"`).
202
-
203
- ### Don't
204
-
205
- - `Total: 247` — drop "Total".
206
- - `247 placements` in `value` — put `247` in `value`, `placements` is in `label`.
207
- - `+5 (up 12 % from last week)` in `delta` — `delta = "+5"`, `description = "vs last week"`.
208
-
209
- ---
210
-
211
- ## 9. Form labels and helper text
212
-
213
- | Element | Rule | Example |
214
- |---|---|---|
215
- | Label | Sentence case, no colon, no "*" — pair `*` decoration with `aria-required` only. | `Date of birth` |
216
- | Required marker | Visible `*` (`aria-hidden`), programmatic `aria-required="true"`. | `Date of birth *` |
217
- | Helper text | Persistent, format-first. Use `FormDescription`. Never placeholder-only. | `MM/DD/YYYY` |
218
- | Placeholder | May mirror the format. Never the sole carrier. | `MM/DD/YYYY` |
219
- | Inline error | Replaces helper while active. Match the rule, not the field name. | `Use MM/DD/YYYY.` |
220
- | Unit in label vs description | Units go in description when context-dependent (`Out of 4.0` under "GPA"), in label when fixed (`Hours per week`). | — |
221
-
222
- See [`exxat-accessibility.mdc`](../../../.cursor/rules/exxat-accessibility.mdc) §"Form fields — format hints MUST be persistent".
223
-
224
- ---
225
-
226
- ## 10. Dates, times, numbers
227
-
228
- | Type | Format | Example |
229
- |---|---|---|
230
- | Date (UI) | Locale-aware, defaults to `MM/DD/YYYY` for en-US | `12/14/2025` |
231
- | Date range | Same format, en-dash with hair-thin spaces (or hyphen-minus in mono) | `12/14/2025 – 12/20/2025` |
232
- | Time | 12-hour with am/pm in lowercase | `2:30 pm` |
233
- | Relative time | Use sparingly; pair with absolute on hover/tooltip | `Updated 3 hours ago` (tip: `12/15/2025, 11:14 am`) |
234
- | Numbers | `tabular-nums`. Use thousands separator (`12,547`) for ≥ 4 digits | `12,547` |
235
- | Currency | Symbol-first, no space (`$1,200.00`) | `$1,200.00` |
236
- | Percent | No space (`12 %` is wrong, use `12%`) | `12%` |
237
- | Duration | Short form (`38 hrs`, `2 wks`) | `38 hrs` |
238
-
239
- ---
240
-
241
- ## 11. Reviewer checklist (paste into PRs that touch copy)
242
-
243
- - [ ] Sentence case (not title case) everywhere.
244
- - [ ] No `Click here`, `Submit`, `OK`, `Loading…` without a noun.
245
- - [ ] No toasts; banners or inline status instead.
246
- - [ ] Empty state names the entity and surfaces a next action or "Clear filters".
247
- - [ ] Errors answer: what / why / what now.
248
- - [ ] Status labels come from `lib/list-status-badges.ts`, not new strings.
249
- - [ ] KPI `delta` is a count; KPI `description` is prose. Polarity matches the metric.
250
- - [ ] Form helper text is `FormDescription`, not placeholder-only.
251
- - [ ] No exclamation marks outside `success` banners (and even those rarely).
252
- - [ ] No emoji unless explicitly approved for that surface.
253
-
254
- ---
255
-
256
- ## See also
257
-
258
- - [`HANDBOOK.md`](./HANDBOOK.md) — where this fits in the docs map
259
- - [`glossary.md`](./glossary.md) — vocabulary
260
- - [`.cursor/rules/exxat-no-toast.mdc`](../../../.cursor/rules/exxat-no-toast.mdc) — why banners, not toasts
261
- - [`.cursor/rules/exxat-kpi-trends.mdc`](../../../.cursor/rules/exxat-kpi-trends.mdc) — KPI delta vs description
262
- - [`.cursor/rules/exxat-accessibility.mdc`](../../../.cursor/rules/exxat-accessibility.mdc) — format-hint persistence rule
@@ -1,32 +0,0 @@
1
- /**
2
- * PM2 — keep `next dev` running after the terminal closes; restarts on crash.
3
- * Start: `nvm use && npm run dev:daemon`
4
- * @see README.md — Development (daemon)
5
- * @see docs/perf-memory-pattern.md — Dev memory tuning
6
- */
7
- module.exports = {
8
- apps: [
9
- {
10
- name: "exxat-ds",
11
- script: "npm",
12
- args: "run dev",
13
- cwd: __dirname,
14
- interpreter: "none",
15
- autorestart: true,
16
- max_restarts: 30,
17
- min_uptime: "4s",
18
- exp_backoff_restart_delay: 2000,
19
- // Dev memory tuning. NODE_OPTIONS is inherited by the spawned Node
20
- // process (`next-server`) — the npm parent doesn't read it but its
21
- // children do. See docs/perf-memory-pattern.md §2.
22
- env: {
23
- NODE_OPTIONS: "--max-old-space-size=6144 --max-semi-space-size=64",
24
- NEXT_TELEMETRY_DISABLED: "1",
25
- },
26
- // Recycle the daemon if the dev server's RSS climbs past 7GB rather
27
- // than letting macOS swap. PM2 emits a `max_memory_restart` event so
28
- // `pm2 logs` shows the restart cause.
29
- max_memory_restart: "7G",
30
- },
31
- ],
32
- }
@@ -1,216 +0,0 @@
1
- import bundleAnalyzer from "@next/bundle-analyzer"
2
-
3
- const withBundleAnalyzer = bundleAnalyzer({
4
- enabled: process.env.ANALYZE === "true",
5
- })
6
-
7
- const isDev = process.env.NODE_ENV !== "production"
8
-
9
- /**
10
- * Content Security Policy
11
- *
12
- * Allowlist matches the runtime third-party origins this app actually contacts
13
- * (see `app/layout.tsx` and `lib/{logo-dev,stock-portrait}.ts`):
14
- *
15
- * • Font Awesome Pro Kit — *.fontawesome.com (loader at kit.*, Pro CSS/font/telemetry
16
- * at ka-p.*, free fallback at ka-f.*). Wildcard is used
17
- * because FA rotates resource subdomains per kit tier and
18
- * every subdomain is FA-controlled.
19
- * • Adobe Typekit — use.typekit.net (CSS) + p.typekit.net (font files)
20
- * • Avatars (mock) — img.logo.dev (school logos), randomuser.me (mock portraits)
21
- *
22
- * `'unsafe-inline'` is kept for `style-src` because Tailwind injects inline styles
23
- * and the chart primitive uses one `<style dangerouslySetInnerHTML>` block for
24
- * per-instance CSS variables. `'unsafe-inline'` is kept for `script-src` because
25
- * Next.js inlines small bootstrap scripts (page data, RSC payload) on every
26
- * route; migrating to nonces is a separate refactor. `'unsafe-eval'` is only
27
- * permitted in development for HMR.
28
- *
29
- * If you add a new third-party origin (fonts, images, telemetry), update the
30
- * matching directive below before shipping.
31
- */
32
- function buildContentSecurityPolicy() {
33
- // `https://*.fontawesome.com` covers every FA Kit subdomain (loader at
34
- // `kit.fontawesome.com`, Pro resources at `ka-p.fontawesome.com`, free
35
- // fallback at `ka-f.fontawesome.com`, telemetry, etc.) without us having to
36
- // chase per-tier subdomain changes. Every subdomain is FA-controlled.
37
- const FA_HOSTS = "https://*.fontawesome.com"
38
-
39
- const scriptSrc = [
40
- "'self'",
41
- "'unsafe-inline'",
42
- isDev ? "'unsafe-eval'" : "",
43
- FA_HOSTS,
44
- ]
45
- .filter(Boolean)
46
- .join(" ")
47
-
48
- const styleSrc = [
49
- "'self'",
50
- "'unsafe-inline'",
51
- "https://use.typekit.net",
52
- "https://p.typekit.net",
53
- FA_HOSTS,
54
- ].join(" ")
55
-
56
- const fontSrc = [
57
- "'self'",
58
- "data:",
59
- "https://use.typekit.net",
60
- "https://p.typekit.net",
61
- FA_HOSTS,
62
- ].join(" ")
63
-
64
- const imgSrc = [
65
- "'self'",
66
- "data:",
67
- "blob:",
68
- "https://img.logo.dev",
69
- "https://randomuser.me",
70
- FA_HOSTS,
71
- ].join(" ")
72
-
73
- const connectSrc = [
74
- "'self'",
75
- "https://use.typekit.net",
76
- FA_HOSTS,
77
- // Next.js dev server uses websockets for HMR.
78
- isDev ? "ws:" : "",
79
- isDev ? "wss:" : "",
80
- ]
81
- .filter(Boolean)
82
- .join(" ")
83
-
84
- const directives = [
85
- `default-src 'self'`,
86
- `script-src ${scriptSrc}`,
87
- `style-src ${styleSrc}`,
88
- `font-src ${fontSrc}`,
89
- `img-src ${imgSrc}`,
90
- `connect-src ${connectSrc}`,
91
- `frame-ancestors 'none'`,
92
- `frame-src 'none'`,
93
- `object-src 'none'`,
94
- `base-uri 'self'`,
95
- `form-action 'self'`,
96
- `manifest-src 'self'`,
97
- `worker-src 'self' blob:`,
98
- // `upgrade-insecure-requests` is only meaningful on HTTPS responses.
99
- isDev ? "" : "upgrade-insecure-requests",
100
- ]
101
- .filter(Boolean)
102
- .join("; ")
103
-
104
- return directives
105
- }
106
-
107
- const SECURITY_HEADERS = [
108
- {
109
- key: "Content-Security-Policy",
110
- value: buildContentSecurityPolicy(),
111
- },
112
- // Defence-in-depth — `frame-ancestors 'none'` already blocks framing in
113
- // modern browsers, but X-Frame-Options is still honoured by older clients
114
- // and is required by some scanners.
115
- { key: "X-Frame-Options", value: "DENY" },
116
- { key: "X-Content-Type-Options", value: "nosniff" },
117
- { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
118
- {
119
- key: "Permissions-Policy",
120
- value: [
121
- "accelerometer=()",
122
- "autoplay=()",
123
- "camera=()",
124
- "display-capture=()",
125
- "encrypted-media=()",
126
- "fullscreen=(self)",
127
- "geolocation=()",
128
- "gyroscope=()",
129
- "magnetometer=()",
130
- "microphone=()",
131
- "midi=()",
132
- "payment=()",
133
- "picture-in-picture=()",
134
- "publickey-credentials-get=()",
135
- "screen-wake-lock=()",
136
- "sync-xhr=()",
137
- "usb=()",
138
- "xr-spatial-tracking=()",
139
- "interest-cohort=()",
140
- ].join(", "),
141
- },
142
- // HSTS only takes effect over HTTPS; harmless on http://localhost during dev.
143
- // Subdomain + preload opt-in — drop `; preload` if the apex domain is not
144
- // ready for the HSTS preload list.
145
- {
146
- key: "Strict-Transport-Security",
147
- value: "max-age=63072000; includeSubDomains; preload",
148
- },
149
- { key: "X-DNS-Prefetch-Control", value: "on" },
150
- { key: "Cross-Origin-Opener-Policy", value: "same-origin" },
151
- ]
152
-
153
- /** @type {import('next').NextConfig} */
154
- const nextConfig = {
155
- transpilePackages: ["@exxatdesignux/ui"],
156
- // Hard cap on the Turbopack worker. Without this, the dev cache + module
157
- // graph + mmap'd .meta files grow unbounded (we observed 3.2 GB on disk
158
- // and 5+ GB RSS per process). 4 GiB is generous — Turbopack rarely needs
159
- // more on apps with < ~1000 routes. See `docs/perf-memory-pattern.md` §6.
160
- turbopack: {
161
- memoryLimit: 4 * 1024 * 1024 * 1024,
162
- },
163
- // Dev memory tuning — see `docs/perf-memory-pattern.md` for rationale.
164
- experimental: {
165
- // Tree-shake heavy barrel re-exports. Every package here was identified by
166
- // bundle analyzer as a re-export hot spot in the dev server's heap snapshot.
167
- optimizePackageImports: [
168
- "lucide-react",
169
- "recharts",
170
- "@exxatdesignux/ui",
171
- "@tabler/icons-react",
172
- "motion",
173
- "@dnd-kit/core",
174
- "@dnd-kit/sortable",
175
- "@dnd-kit/modifiers",
176
- "@dnd-kit/utilities",
177
- ],
178
- // Compile routes the user actually visits instead of pre-warming every
179
- // entry on `next dev` start. The dev server reaches a usable state in
180
- // ~2s instead of ~15s on this app and steady-state heap is ~30% lower.
181
- preloadEntriesOnStart: false,
182
- // Webpack fallback (`pnpm dev:webpack`) — drops large in-memory caches at
183
- // the cost of slightly slower rebuilds. Ignored by Turbopack.
184
- webpackMemoryOptimizations: true,
185
- },
186
- // Cap how long inactive routes stay compiled in the dev server's memory.
187
- // Defaults (25s / 2 pages) are fine for small apps — keeping them explicit
188
- // makes the trade-off discoverable when memory grows on a larger app.
189
- onDemandEntries: {
190
- maxInactiveAge: 25 * 1000,
191
- pagesBufferLength: 2,
192
- },
193
- async headers() {
194
- return [
195
- {
196
- // Apply to every route. Static assets under `/_next/static/*` get the
197
- // same headers — that's fine since they are first-party.
198
- source: "/:path*",
199
- headers: SECURITY_HEADERS,
200
- },
201
- ]
202
- },
203
- async redirects() {
204
- return [
205
- // Legacy demo routes (placements, rotations, compliance, sites) were removed.
206
- // Redirect anything pointing at them to Dashboard rather than 404.
207
- { source: "/rotations", destination: "/dashboard", permanent: false },
208
- { source: "/compliance", destination: "/dashboard", permanent: false },
209
- { source: "/sites/all", destination: "/dashboard", permanent: false },
210
- { source: "/sites/all/:path*", destination: "/dashboard", permanent: false },
211
- { source: "/examples", destination: "/dashboard", permanent: false },
212
- ]
213
- },
214
- }
215
-
216
- export default withBundleAnalyzer(nextConfig)
@@ -1,8 +0,0 @@
1
- /** @type {import('postcss-load-config').Config} */
2
- const config = {
3
- plugins: {
4
- "@tailwindcss/postcss": {},
5
- },
6
- }
7
-
8
- export default config
Binary file
@@ -1,26 +0,0 @@
1
- import "@testing-library/jest-dom/vitest"
2
- import { vi } from "vitest"
3
-
4
- // jsdom does not implement window.matchMedia — stub it for tests that use
5
- // useTableState (which calls useSyncExternalStore with a matchMedia listener)
6
- Object.defineProperty(window, "matchMedia", {
7
- writable: true,
8
- value: (query: string) => ({
9
- matches: false,
10
- media: query,
11
- onchange: null,
12
- addListener: vi.fn(),
13
- removeListener: vi.fn(),
14
- addEventListener: vi.fn(),
15
- removeEventListener: vi.fn(),
16
- dispatchEvent: vi.fn(),
17
- }),
18
- })
19
-
20
- // jsdom does not implement ResizeObserver — stub it for components that use
21
- // Radix UI primitives (Sheet, Tooltip, etc.) which call it internally.
22
- globalThis.ResizeObserver = class {
23
- observe = vi.fn()
24
- unobserve = vi.fn()
25
- disconnect = vi.fn()
26
- }
@@ -1,18 +0,0 @@
1
- import path from "node:path"
2
- import { defineConfig } from "vitest/config"
3
- import react from "@vitejs/plugin-react"
4
-
5
- export default defineConfig({
6
- plugins: [react()],
7
- test: {
8
- environment: "jsdom",
9
- setupFiles: ["./tests/setup.ts"],
10
- include: ["**/*.{test,spec}.{ts,tsx}"],
11
- exclude: ["node_modules", ".next", "out", "build"],
12
- },
13
- resolve: {
14
- alias: {
15
- "@": path.resolve(__dirname, "./"),
16
- },
17
- },
18
- })
File without changes
File without changes
File without changes
File without changes