@exxatdesignux/ui 0.2.17 → 0.2.19

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 (162) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/consumer-extras/AGENTS.md +76 -0
  3. package/consumer-extras/README.md +5 -1
  4. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +14 -3
  5. package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +37 -0
  6. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +22 -7
  7. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
  8. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +38 -0
  9. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -3
  10. package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
  11. package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
  12. package/consumer-extras/patterns/data-views-pattern.md +42 -3
  13. package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
  14. package/consumer-extras/patterns/kpi-flat-band-pattern.md +57 -0
  15. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +54 -0
  16. package/package.json +2 -1
  17. package/src/components/ui/button-group.tsx +81 -0
  18. package/src/components/ui/button.tsx +4 -4
  19. package/src/components/ui/sidebar.tsx +2 -2
  20. package/src/globals.css +7 -1807
  21. package/src/theme.css +10 -1126
  22. package/src/tokens/README.md +15 -0
  23. package/src/tokens/base.css +337 -0
  24. package/src/tokens/high-contrast.css +1195 -0
  25. package/src/tokens/layers.css +224 -0
  26. package/src/tokens/tailwind-bridge.css +118 -0
  27. package/src/tokens/themes.css +201 -0
  28. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -1
  29. package/template/AGENTS.md +66 -21
  30. package/template/app/(app)/dashboard/loading.tsx +3 -15
  31. package/template/app/(app)/dashboard/page.tsx +2 -14
  32. package/template/app/(app)/data-list/layout.tsx +43 -0
  33. package/template/app/(app)/data-list/page.tsx +2 -2
  34. package/template/app/(app)/error.tsx +22 -6
  35. package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
  36. package/template/app/(app)/examples/page.tsx +1 -0
  37. package/template/app/(app)/layout.tsx +13 -6
  38. package/template/app/(app)/loading.tsx +1 -18
  39. package/template/app/(app)/question-bank/find/page.tsx +2 -1
  40. package/template/app/(app)/question-bank/library/page.tsx +2 -1
  41. package/template/app/(app)/question-bank/list/page.tsx +2 -1
  42. package/template/app/(app)/question-bank/new/page.tsx +15 -23
  43. package/template/app/(app)/question-bank/page.tsx +2 -1
  44. package/template/app/(app)/settings/page.tsx +4 -5
  45. package/template/app/global-error.tsx +63 -0
  46. package/template/app/globals.css +7 -1934
  47. package/template/app/layout.tsx +2 -0
  48. package/template/components/app-route-loading.tsx +14 -0
  49. package/template/components/app-sidebar.tsx +71 -55
  50. package/template/components/data-table/index.tsx +31 -67
  51. package/template/components/data-table/use-table-state.ts +33 -6
  52. package/template/components/data-views/index.ts +37 -9
  53. package/template/components/data-views/list-page-calendar-view.tsx +593 -0
  54. package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
  55. package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
  56. package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
  57. package/template/components/dev-chunk-load-recovery.tsx +41 -0
  58. package/template/components/examples/focused-workflow-showcase.tsx +183 -0
  59. package/template/components/exxat-product-logo.tsx +2 -6
  60. package/template/components/key-metrics.tsx +54 -22
  61. package/template/components/list-hub-board-view.tsx +68 -0
  62. package/template/components/list-hub-client.tsx +186 -0
  63. package/template/components/list-hub-list-view.tsx +36 -0
  64. package/template/components/list-hub-panel-activator.tsx +8 -0
  65. package/template/components/list-hub-secondary-nav.tsx +121 -0
  66. package/template/components/list-hub-table.tsx +336 -0
  67. package/template/components/new-question-composer.tsx +6 -24
  68. package/template/components/product-switcher.tsx +5 -5
  69. package/template/components/product-wordmark.tsx +4 -7
  70. package/template/components/question-bank-client.tsx +4 -1
  71. package/template/components/question-bank-folder-columns-panel.tsx +104 -0
  72. package/template/components/question-bank-hub-client.tsx +2 -5
  73. package/template/components/question-bank-table.tsx +155 -509
  74. package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
  75. package/template/components/secondary-panel.tsx +4 -44
  76. package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
  77. package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
  78. package/template/components/secondary-panels/registry.tsx +15 -0
  79. package/template/components/settings-appearance-card.tsx +3 -2
  80. package/template/components/settings-client.tsx +59 -15
  81. package/template/components/settings-form-row.tsx +9 -4
  82. package/template/components/sidebar-shell.tsx +2 -1
  83. package/template/components/table-properties/drawer-button.tsx +51 -20
  84. package/template/components/table-properties/drawer.tsx +81 -17
  85. package/template/components/templates/focused-workflow-layouts.tsx +448 -0
  86. package/template/components/templates/focused-workflow-page-template.tsx +69 -0
  87. package/template/components/templates/list-page.tsx +40 -13
  88. package/template/components/templates/nested-secondary-panel-shell.tsx +3 -2
  89. package/template/components/templates/page-loading-shell.tsx +262 -0
  90. package/template/components/ui/button-group.tsx +1 -0
  91. package/template/contexts/product-context.tsx +21 -2
  92. package/template/docs/consumer-app-pattern.md +39 -0
  93. package/template/docs/data-views-pattern.md +42 -3
  94. package/template/docs/drawer-vs-dialog-pattern.md +3 -1
  95. package/template/docs/focused-workflow-page-pattern.md +84 -0
  96. package/template/docs/kpi-flat-band-pattern.md +57 -0
  97. package/template/docs/kpi-strip-max-four-pattern.md +1 -0
  98. package/template/docs/shell-surface-elevation-pattern.md +54 -0
  99. package/template/lib/chunk-load-error.ts +13 -0
  100. package/template/lib/command-menu-search-data.ts +11 -27
  101. package/template/lib/conditional-rule-match.ts +87 -22
  102. package/template/lib/data-list-display-options.ts +16 -2
  103. package/template/lib/data-list-view-registry.ts +104 -0
  104. package/template/lib/data-list-view-surface.ts +15 -1
  105. package/template/lib/data-list-view.ts +16 -1
  106. package/template/lib/data-view-dashboard-storage.ts +38 -35
  107. package/template/lib/hub-connected-view-renderers.ts +58 -0
  108. package/template/lib/list-hub-nav.ts +121 -0
  109. package/template/lib/list-hub-supported-views.ts +10 -0
  110. package/template/lib/list-page-table-properties.ts +3 -7
  111. package/template/lib/list-status-badges.ts +4 -97
  112. package/template/lib/mock/list-hub-directory.ts +27 -0
  113. package/template/lib/mock/list-hub-kpi.ts +27 -0
  114. package/template/lib/mock/navigation.tsx +1 -0
  115. package/template/lib/page-loading-variant.ts +40 -0
  116. package/template/lib/question-bank-supported-views.ts +13 -0
  117. package/template/lib/sidebar-state-cookie.ts +9 -0
  118. package/template/lib/table-state-lifecycle.ts +60 -13
  119. package/template/app/(app)/data-list/[id]/page.tsx +0 -44
  120. package/template/app/(app)/data-list/new/page.tsx +0 -34
  121. package/template/components/compliance-board-view.tsx +0 -142
  122. package/template/components/compliance-client.tsx +0 -92
  123. package/template/components/compliance-list-view.tsx +0 -54
  124. package/template/components/compliance-page-header.tsx +0 -89
  125. package/template/components/compliance-table.tsx +0 -632
  126. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  127. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  128. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  129. package/template/components/new-placement-back-btn.tsx +0 -28
  130. package/template/components/new-placement-form.tsx +0 -1068
  131. package/template/components/placement-board-card.tsx +0 -262
  132. package/template/components/placement-detail.tsx +0 -438
  133. package/template/components/placements-board-view.tsx +0 -404
  134. package/template/components/placements-client.tsx +0 -252
  135. package/template/components/placements-list-view.tsx +0 -171
  136. package/template/components/placements-page-header.tsx +0 -166
  137. package/template/components/placements-table-cells.test.tsx +0 -22
  138. package/template/components/placements-table-cells.tsx +0 -173
  139. package/template/components/placements-table-columns.tsx +0 -640
  140. package/template/components/placements-table.tsx +0 -1675
  141. package/template/components/rotations-empty-state.tsx +0 -50
  142. package/template/components/rotations-panel-activator.tsx +0 -8
  143. package/template/components/sites-all-client.tsx +0 -154
  144. package/template/components/sites-board-view.tsx +0 -67
  145. package/template/components/sites-list-view.tsx +0 -42
  146. package/template/components/sites-table.tsx +0 -402
  147. package/template/components/team-board-view.tsx +0 -122
  148. package/template/components/team-client.tsx +0 -100
  149. package/template/components/team-list-view.tsx +0 -59
  150. package/template/components/team-page-header.tsx +0 -92
  151. package/template/components/team-table.tsx +0 -714
  152. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  153. package/template/lib/mock/compliance-kpi.ts +0 -61
  154. package/template/lib/mock/compliance.ts +0 -146
  155. package/template/lib/mock/placements-kpi.ts +0 -134
  156. package/template/lib/mock/placements.ts +0 -183
  157. package/template/lib/mock/sites-directory.ts +0 -16
  158. package/template/lib/mock/sites-kpi.ts +0 -25
  159. package/template/lib/mock/team-kpi.ts +0 -60
  160. package/template/lib/mock/team.ts +0 -118
  161. package/template/lib/placement-board-card-layout.ts +0 -79
  162. package/template/lib/placement-lifecycle.ts +0 -5
@@ -24,18 +24,21 @@ Cross-cutting Cursor rules also live in the repo root `.cursor/rules/` (data tab
24
24
  12. **Before** adding **onboarding tours, feature walkthroughs, or coach marks**, read **§11** and `references/coach-marks.md`.
25
25
  13. **Before** changing the **global command palette (⌘K)** or search/AI entry UX, read **§7.1** and **`docs/command-menu-pattern.md`**.
26
26
  14. **Before** choosing **drawer vs dialog vs new page** for a task flow, read **§6.4**, **`docs/data-views-pattern.md`** (Page vs drawer), and **`docs/drawer-vs-dialog-pattern.md`** (modal vs side panel on the same route).
27
- 15. **Before** adding **success/error/confirmation feedback**, read **§6.5** and **`.cursor/rules/exxat-no-toast.mdc`** (no toast or snackbars).
28
- 16. Prefer **composing existing components** over new one-off UI. If something is missing, **extend** shared components under `components/`, not a single page file.
27
+ 15. **Before** adding or changing a **dedicated form, wizard, or sectioned settings route**, read **`docs/focused-workflow-page-pattern.md`**, **`.cursor/rules/exxat-focused-workflow-page.mdc`**, **`.cursor/skills/exxat-focused-workflow-page/SKILL.md`**, and run **§14** checklist.
28
+ 16. **Before** adding **success/error/confirmation feedback**, read **§6.5** and **`.cursor/rules/exxat-no-toast.mdc`** (no toast or snackbars).
29
+ 17. Prefer **composing existing components** over new one-off UI. If something is missing, **extend** shared components under `components/`, not a single page file.
29
30
  - **MUST** scan `components/` (especially `components/ui/`, `components/data-views/`, `components/templates/`, `components/key-metrics.tsx`, `components/page-header.tsx`, and the charts/banner/dot-pattern surfaces) **before** writing any new UI. If a primitive or composition already exists, **use it** — don't build a parallel one.
30
- - **Examples of existing surfaces to reuse:** card grid → `ListPageBoardCard` + `BoardCardIconRow` / `BoardCardTwoLineBlock`; AI / dot animation → `AiThinkingOverlay` + `DotPattern`; search input → `InputGroup` + `InputGroupAddon` + `InputGroupInput`; page title → `PageHeader` (serif via `font-heading`); list hub shell → `ListPageTemplate` (`metrics`, `defaultTabs`, `renderContent`); metrics strip → `KeyMetrics`; **view body gutter + centered max-width** → **`ListPageViewFrame`** (**§4.5**); **shared access / invite** → **`PageHeader` `variant="collaboration"`** + **`InviteCollaboratorsDrawer`** (**§4.7**).
31
+ - **Examples of existing surfaces to reuse:** card grid → `ListPageBoardCard` + `BoardCardIconRow` / `BoardCardTwoLineBlock`; AI / dot animation → `AiThinkingOverlay` + `DotPattern`; search input → `InputGroup` + `InputGroupAddon` + `InputGroupInput`; page title → `PageHeader` (serif via `font-heading`); list hub shell → `ListPageTemplate` (`metrics`, `defaultTabs`, `renderContent`); **dedicated form / wizard / settings route** → **`FocusedWorkflowPageTemplate`** + **`focused-workflow-layouts.tsx`** (**§14**); metrics strip → `KeyMetrics`; **view body gutter + centered max-width** → **`ListPageViewFrame`** (**§4.5**); **shared access / invite** → **`PageHeader` `variant="collaboration"`** + **`InviteCollaboratorsDrawer`** (**§4.7**).
31
32
  - **If** nothing fits and you would add a **new shared primitive or large bespoke widget**: **ask the user** for direction first — **`.cursor/rules/exxat-reuse-before-custom.mdc`** (unless the task already explicitly approved a greenfield build).
32
- 17. **Match** naming, imports, and patterns of the nearest reference implementation (usually Placements).
33
- 18. **Before** adding entity **mock data**, a **new view tab**, or **detail/inspector** panels on a list hub, read **`.cursor/rules/exxat-centralized-list-dataset.mdc`** and **`.cursor/skills/exxat-centralized-list-dataset/SKILL.md`** (single **`useTableState`** row bag for every view; **no** parallel mock arrays per view).
34
- 19. **Before** choosing **cards vs table rows vs simple lists** for a hub, read **`docs/card-vs-rows-pattern.md`** and **`.cursor/rules/exxat-card-vs-list-rows.mdc`**.
35
- 20. **Before** adding **`KeyMetrics`** strips on list hubs or dashboard key-metrics cards, read **`docs/kpi-strip-max-four-pattern.md`** and **`.cursor/rules/exxat-kpi-max-four.mdc`** (at most **four** tiles).
36
- 21. **Before** adding **new shared UI primitives** or bespoke widgets when nothing in **`components/`** fits after scanning, follow **`.cursor/rules/exxat-reuse-before-custom.mdc`** **ask the user** for direction unless the task already approved a greenfield build.
33
+ 18. **Match** naming, imports, and patterns of the nearest reference implementation (usually Placements).
34
+ 19. **Before** adding entity **mock data**, a **new view tab**, or **detail/inspector** panels on a list hub, read **`.cursor/rules/exxat-centralized-list-dataset.mdc`** and **`.cursor/skills/exxat-centralized-list-dataset/SKILL.md`** (single **`useTableState`** row bag for every view; **no** parallel mock arrays per view).
35
+ 20. **Before** choosing **cards vs table rows vs simple lists** for a hub, read **`docs/card-vs-rows-pattern.md`** and **`.cursor/rules/exxat-card-vs-list-rows.mdc`**.
36
+ 21. **Before** adding **`KeyMetrics`** strips on list hubs or dashboard key-metrics cards, read **`docs/kpi-strip-max-four-pattern.md`** and **`.cursor/rules/exxat-kpi-max-four.mdc`** (at most **four** tiles).
37
+ 22. **Before** styling **`KeyMetrics variant="flat"`** (list hub metrics strip, dashboard mix KPI band), read **`docs/kpi-flat-band-pattern.md`** and **`.cursor/rules/exxat-kpi-flat-band.mdc`** / **`.cursor/skills/exxat-kpi-flat-band/SKILL.md`** (transparent band, OKLCH glow, border hairlines only).
38
+ 23. **Before** changing **secondary panel** or **sidebar** brand chrome, read **`docs/shell-surface-elevation-pattern.md`** and **§4.6** ( **`--secondary-panel-bg`**, active product theme).
39
+ 24. **Before** adding **new shared UI primitives** or bespoke widgets when nothing in **`components/`** fits after scanning, follow **`.cursor/rules/exxat-reuse-before-custom.mdc`** — **ask the user** for direction unless the task already approved a greenfield build.
37
40
 
38
- **Longer narrative and architecture:** `docs/data-views-pattern.md`, `docs/drawer-vs-dialog-pattern.md`, `docs/card-vs-rows-pattern.md`, `docs/kpi-strip-max-four-pattern.md`, `docs/command-menu-pattern.md`, `docs/collaboration-access-pattern.md` (keep in sync with this handbook for big refactors).
41
+ **Longer narrative and architecture:** `docs/data-views-pattern.md`, `docs/drawer-vs-dialog-pattern.md`, **`docs/focused-workflow-page-pattern.md`**, `docs/card-vs-rows-pattern.md`, `docs/kpi-strip-max-four-pattern.md`, **`docs/kpi-flat-band-pattern.md`**, **`docs/shell-surface-elevation-pattern.md`**, `docs/command-menu-pattern.md`, `docs/collaboration-access-pattern.md` (keep in sync with this handbook for big refactors).
39
42
 
40
43
  ---
41
44
 
@@ -73,7 +76,7 @@ If two documents conflict, prefer the **more specific** rule for the file type,
73
76
 
74
77
  **MUST:** If the main surface is a **`DataTable`** (or equivalent data grid), wrap it in **`ListPageTemplate`** so the **views toolbar** exists (tabs, add view, per-tab settings). Do **not** place `DataTable` only under `PageHeader` without the tab shell.
75
78
 
76
- **Reference implementations:** `components/data-list-client.tsx` (Placements), `components/team-client.tsx` (Team).
79
+ **Reference implementations:** `components/list-hub-client.tsx` (List hub), `components/question-bank-client.tsx` (Question bank). Template mirror: `packages/ui/template/components/list-hub-*.tsx`, `question-bank-*.tsx`.
77
80
 
78
81
  **Rationale:** Consistent navigation, saved views, per-tab view type (table / list / board / dashboard), export at template level.
79
82
 
@@ -91,6 +94,19 @@ If two documents conflict, prefer the **more specific** rule for the file type,
91
94
 
92
95
  **Details:** `docs/data-views-pattern.md` (mock data, connected views, dashboard view).
93
96
 
97
+ ### 4.1.1 View registry + `ListPageConnectedViewBody` (required for new hubs)
98
+
99
+ **MUST** route view bodies through the shared registry — **MUST NOT** use `if (view === "board")` chains with a dashboard fallback.
100
+
101
+ | Step | Action |
102
+ |------|--------|
103
+ | **Register** | Add the tile in **`lib/data-list-view.ts`**; capabilities derive in **`lib/data-list-view-registry.ts`** (`renderKind`, `showsListPageHubMetricsStrip`). |
104
+ | **Declare hub** | **`lib/<hub>-supported-views.ts`** — pass the same array to **`ListPageTemplate`** (`supportedViewTypes`) and **`TablePropertiesDrawerButton`** (`supportedViewTypes`). |
105
+ | **Render** | Hub **`*-table.tsx`** returns **`ListPageConnectedViewBody`** with **`defineHubViewRenderers(supportedViewTypes, { … })`** — one entry per **`DataListViewRenderKind`**; dev warns on gaps; missing kinds show **`ListPageViewNotConfigured`** (never silent dashboard). |
106
+ | **Bodies** | Generic UI in **`components/data-views/`** (`ListPageCalendarView`, `ListPageBoardTemplate`, `ListPageFolderColumnsPanel`, `FolderGridView`, …); entity wiring in thin hub wrappers (e.g. **`QuestionBankFolderColumnsPanel`**). **MUST NOT** export QB-only tree branches from the generic **`data-views`** barrel. |
107
+
108
+ **Golden references (this app):** `components/list-hub-table.tsx`, `components/question-bank-table.tsx`. **Template** (`packages/ui/template/`): same list-hub + question-bank stack — keep in sync when publishing **`@exxatdesignux/ui`**.
109
+
94
110
  ### 4.2 `TablePropertiesDrawer` and the active view
95
111
 
96
112
  **MUST:** Any page that uses **`ListPageTemplate`** with **`tab.viewType`** (table / list / board / dashboard) and renders **`TablePropertiesDrawer`** **MUST** pass:
@@ -99,10 +115,11 @@ If two documents conflict, prefer the **more specific** rule for the file type,
99
115
  |------|--------|
100
116
  | **`currentView`** | The same **`DataListViewType`** as the tab’s active view (e.g. **`view={tab.viewType}`** on the table component). |
101
117
  | **`onViewChange`** | From **`renderContent={(tab, updateTab) => ...}`**: **`(v) => updateTab({ viewType: v, icon: dataListViewIcon(v) })`** — import **`dataListViewIcon`** from **`@/lib/data-list-view`**. |
118
+ | **`supportedViewTypes`** | Same **`lib/<hub>-supported-views.ts`** array as **`ListPageTemplate`** — limits Properties view tiles and **⌘1–9** Add-view shortcuts to views the hub implements. |
102
119
 
103
- Thread **`view`** and **`onViewChange`** from the **client** → **table / toolbar wrapper** → **`TablePropertiesDrawer`**. If **`currentView`** is omitted, the drawer defaults to **table** labels and controls even on **Board**, which is incorrect.
120
+ Thread **`view`**, **`onViewChange`**, and **`supportedViewTypes`** from the **client** → **table / toolbar wrapper** → **`TablePropertiesDrawer`**. If **`currentView`** is omitted, the drawer defaults to **table** labels and controls even on **Board**, which is incorrect.
104
121
 
105
- **Reference:** `components/data-list-table.tsx`, `components/team-table.tsx`, `components/compliance-table.tsx`. Root **`.cursor/rules/exxat-table-properties-drawer.mdc`**.
122
+ **Reference:** `components/list-hub-table.tsx`, `components/question-bank-table.tsx`; template **`components/placements-table.tsx`**, **`team-table.tsx`**. Root **`.cursor/rules/exxat-table-properties-drawer.mdc`**.
106
123
 
107
124
  ### 4.3 Data view dashboard — charts, customisation, and parity with the gallery
108
125
 
@@ -165,6 +182,8 @@ Thread **`view`** and **`onViewChange`** from the **client** → **table / toolb
165
182
 
166
183
  **Folder-scoped library (Question bank):** When the URL is scoped to a folder (**`scope === "folder"`** + **`folderId`** via **`lib/question-bank-nav.ts`**), the hub **`QuestionBankPageHeader`** **⋯ More** menu **MUST** include **Customize folder** and open **`QuestionBankNewFolderSheet`** from the **hub client** so the action works on **every** **`ListPageTemplate`** view tab — not only inside **`QuestionBankTable`** branches that mount their own sheet. **Pattern:** **`docs/question-bank-hub-header-pattern.md`**. **Cursor rule:** **`.cursor/rules/exxat-question-bank-hub-header.mdc`**.
167
184
 
185
+ **Surface elevation:** Secondary panel = **level 1** between primary sidebar (**`--sidebar`**, level 0) and page canvas (**`--background`**, level 2). **`NestedSecondaryPanelShell`** uses **`bg-[var(--secondary-panel-bg)]`** — OKLCH mix from **`--brand-tint*`** per active product (**One** indigo, **Prism** rose, **`theme-custom`** when accent differs from default). **MUST NOT** set panel to **`bg-sidebar`** or a fixed rose fill for all products. **`docs/shell-surface-elevation-pattern.md`**.
186
+
168
187
  **Cursor rule (panel wiring):** **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**. **Icons in panel:** **`.cursor/rules/exxat-fontawesome-icons.mdc`**.
169
188
 
170
189
  ### 4.7 Collaboration & access (shared hubs)
@@ -247,7 +266,9 @@ Match **Placements**:
247
266
 
248
267
  **Rationale:** Drawers preserve **spatial context**; dialogs enforce **focus**; full pages avoid cramming complex work into overlays.
249
268
 
250
- **Details:** `docs/data-views-pattern.md` (Page vs drawer), **`docs/drawer-vs-dialog-pattern.md`** (drawer vs modal on the same route). Root **`.cursor/rules/exxat-page-vs-drawer.mdc`**, **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**.
269
+ **Details:** `docs/data-views-pattern.md` (Page vs drawer), **`docs/drawer-vs-dialog-pattern.md`** (drawer vs modal on the same route), **`docs/focused-workflow-page-pattern.md`** (dedicated form/wizard/settings shell — **`FocusedWorkflowPageTemplate`** + body layouts). Root **`.cursor/rules/exxat-page-vs-drawer.mdc`**, **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**, **`.cursor/rules/exxat-focused-workflow-page.mdc`**, **`.cursor/skills/exxat-focused-workflow-page/SKILL.md`**.
270
+
271
+ **Focused workflow routes:** Primary / long / multi-step / sectioned settings on an **own URL** → **`FocusedWorkflowPageTemplate`** (§14 checklist) — **not** **`ListPageTemplate`**, **not** Miller-column explorers.
251
272
 
252
273
  ### 6.5 Messaging — no toast
253
274
 
@@ -421,11 +442,14 @@ Reference: `components/new-placement-form.tsx` (Next/Back buttons); full shortcu
421
442
 
422
443
  | Need | Reuse | Where |
423
444
  |------|--------|--------|
424
- | View tabs + shell | `ListPageTemplate` | `components/templates/list-page.tsx` |
445
+ | View tabs + shell | `ListPageTemplate` (+ **`supportedViewTypes`**) | `components/templates/list-page.tsx` |
446
+ | Focused form / wizard / settings route | `FocusedWorkflowPageTemplate` + layouts in `focused-workflow-layouts.tsx` | `components/templates/focused-workflow-page-template.tsx` — **`docs/focused-workflow-page-pattern.md`** |
447
+ | View router | `ListPageConnectedViewBody`, `defineHubViewRenderers`, `data-list-view-registry` | `components/data-views/list-page-connected-view-body.tsx`, `lib/hub-connected-view-renderers.ts`, `lib/data-list-view-registry.ts` |
425
448
  | Table + toolbar | `DataTable`, `DataTableToolbar`, `useTableState` | `components/data-table/` |
426
- | Properties | `TablePropertiesDrawer` (+ **`currentView`** / **`onViewChange`** when using view tabs — §4.2) | `@/components/table-properties` |
427
- | Placements flow | `DataListClient`, `DataListTable` | `components/data-list-client.tsx`, `components/data-list-table.tsx` |
428
- | Team flow | `TeamClient`, `TeamTable`, `TeamPageHeader` | `components/team-client.tsx`, etc. |
449
+ | Properties | `TablePropertiesDrawer` (+ **`currentView`** / **`onViewChange`** / **`supportedViewTypes`** — §4.2) | `@/components/table-properties` |
450
+ | List hub (golden) | `ListHubClient`, `ListHubTable` | `components/list-hub-client.tsx`, `list-hub-table.tsx` |
451
+ | Question bank | `QuestionBankClient`, `QuestionBankTable` | `components/question-bank-client.tsx`, `question-bank-table.tsx` |
452
+ | Template-only hubs (npm) | Placements, Team, Compliance, Sites | `packages/ui/template/components/*` — diff on upgrade |
429
453
  | Dashboard view tab (KPIs + charts) | **`DashboardReportCharts`**; default **`ChartsOverview`** (placement demo). **Team** passes **`chartsSection`** (`TeamDashboardChartsSection`) so graphs match roster rows. KPIs from **`tableState.rows`** | `components/dashboard-report-charts.tsx`, `data-view-dashboard-charts-team.tsx`, `data-list-table.tsx` |
430
454
  | Data view layout + graph keyboard tokens | **`loadDataViewLayout` / `saveDataViewLayout`**, **`CHART_KBD_ACTIVE_BAR`**, **`CHART_KBD_ACTIVE_PIE_SHAPE`** | `lib/data-view-dashboard-storage.ts`, `lib/chart-keyboard-selection.ts` |
431
455
  | Customize dashboard coach marks | Shared steps in **`lib/dashboard-customize-coach-mark.ts`**; flows **`placements-dashboard-customize`**, **`team-dashboard-customize`**, **`compliance-dashboard-customize`** | `hooks/use-coach-mark.ts` (`enabled`, `dependsOnDismissedFlowId`), `data-list-table.tsx`, `team-table.tsx`, `compliance-table.tsx` |
@@ -508,8 +532,12 @@ New pages **SHOULD** namespace keys and version JSON (`v: 1`) for future migrati
508
532
 
509
533
  - **Deep dive:** `docs/data-views-pattern.md` (includes **Page vs drawer** with **§6.4**)
510
534
  - **Drawer vs dialog (same route):** `docs/drawer-vs-dialog-pattern.md` — **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**
535
+ - **Focused workflow (form / wizard / settings route):** `docs/focused-workflow-page-pattern.md` — **`.cursor/rules/exxat-focused-workflow-page.mdc`**, **`.cursor/skills/exxat-focused-workflow-page/SKILL.md`**
536
+ - **Consumer app (npm):** `docs/consumer-app-pattern.md` — **`packages/ui/consumer-extras/AGENTS.md`**, **`.cursor/rules/exxat-consumer-app.mdc`**, **`.cursor/skills/exxat-consumer-app/SKILL.md`**
511
537
  - **Cards vs table rows:** `docs/card-vs-rows-pattern.md` — **`.cursor/rules/exxat-card-vs-list-rows.mdc`**
512
538
  - **KPI strip (max four tiles):** `docs/kpi-strip-max-four-pattern.md` — **`.cursor/rules/exxat-kpi-max-four.mdc`**
539
+ - **KPI flat band (list hubs):** `docs/kpi-flat-band-pattern.md` — **`.cursor/rules/exxat-kpi-flat-band.mdc`**
540
+ - **Shell surfaces (sidebar · secondary panel · page):** `docs/shell-surface-elevation-pattern.md`
513
541
  - **KPI deltas & trend arrows:** `docs/kpi-trend-pattern.md` (`MetricItem.trendPolarity`, `KeyMetrics`, chart mini-metrics)
514
542
  - **Global command palette (⌘K):** `docs/command-menu-pattern.md`
515
543
  - **No toast / snackbars:** **§6.5**, root **`.cursor/rules/exxat-no-toast.mdc`**
@@ -527,7 +555,7 @@ New pages **SHOULD** namespace keys and version JSON (`v: 1`) for future migrati
527
555
  | Match Placements for export + primary CTA + More menu | Outline button as the single primary CTA on exportable pages |
528
556
  | Pair `Kbd` hints with real shortcuts | Browser-reserved chords for app actions |
529
557
  | Global palette: **§7.1** — search + quick in-menu AI vs **Ask Leo**; **`dataGroups`** + **`searchOnly`** for bulky indexes | Palette as link-only dump; AI that belongs in **Ask Leo** forced into the palette; mounting full **`dataGroups`** on open when **`searchOnly`** should hide them |
530
- | **§6.4** — **drawer** when **page context + quick** view/actions; **dialog** for **blocking** confirm/alert/short choice; **new page** for primary / long / own-URL flows | Forcing **full workflows** into a drawer when a route fits; using a **dialog** when users must **reference** the grid (prefer drawer); **routing** for tasks that are only quick glances over a hub |
558
+ | **§6.4** — **drawer** when **page context + quick** view/actions; **dialog** for **blocking** confirm/alert/short choice; **focused workflow route** (`FocusedWorkflowPageTemplate`) for primary / long / wizard / sectioned settings | Forcing **full workflows** into a drawer when a route fits; using a **dialog** when users must **reference** the grid (prefer drawer); **`ListPageTemplate`** or Miller columns on dedicated create/edit routes |
531
559
  | **KPI strips** — **≤ 4** `MetricItem` per **`KeyMetrics`** on template metrics + Data-tab key-metrics cards (**`KEY_METRICS_KPI_COUNT_MAX`**) | Fifth+ headline tile in the same strip; duplicate tiles to pad count |
532
560
  | **Cards vs rows** — **DataTable** for dense comparable hubs; **`ListPageBoardCard`** / **`ListPageViewFrame`** when visual/kanban/folder — **`docs/card-vs-rows-pattern.md`** | Card walls for **50+** homogeneous records where the product expects **sort/filter/compare** without a deliberate UX exception |
533
561
  | **Reuse before custom** — scan **`components/`** + **§9**; **ask the user** before new shared primitives or large bespoke widgets — **`exxat-reuse-before-custom.mdc`** | Parallel stacks; silent new “table” or metric systems when **`DataTable`** / **`KeyMetrics`** already apply |
@@ -550,6 +578,7 @@ New pages **SHOULD** namespace keys and version JSON (`v: 1`) for future migrati
550
578
  Copy and complete when implementing or reviewing:
551
579
 
552
580
  - [ ] **Centralized dataset:** One **`useTableState`** / **`tableState.rows`** for **all** view tabs and inspectors; **TablePropertiesDrawer** on the **same** `DataTable`; **no** parallel mock arrays per view — **`.cursor/rules/exxat-centralized-list-dataset.mdc`**.
581
+ - [ ] **View registry (§4.1.1):** **`lib/<hub>-supported-views.ts`**; **`ListPageConnectedViewBody`** + **`defineHubViewRenderers`**; same **`supportedViewTypes`** on template + Properties; no silent dashboard fallback — **`docs/data-views-pattern.md`**.
553
582
  - [ ] **Reuse:** `ListPageTemplate`, `DataTable` / `useTableState`, `TablePropertiesDrawer` — no parallel bespoke tabs/filters. **New shared primitives:** **ask the user** after scanning **`components/`** + **§9** — **`.cursor/rules/exxat-reuse-before-custom.mdc`**.
554
583
  - [ ] **Tabs:** Any main `DataTable` sits under `ListPageTemplate` with appropriate view tabs.
555
584
  - [ ] **Inset:** No double horizontal padding around `DataTable`.
@@ -562,7 +591,7 @@ Copy and complete when implementing or reviewing:
562
591
  - [ ] **Data view dashboard (Placements / Team / Compliance):** Charts use **`ChartFigure`** + **`ChartDataTable`**; **Edit layout** on toolbar; **`activeBar` / `activeShape`** keyboard styling from **`lib/chart-keyboard-selection`** — not opacity-only **`Cell`** hacks (§4.3).
563
592
  - [ ] **Dashboard layout persistence:** **`lib/data-view-dashboard-storage`** (or **`saveDashboardLayout`** / **`loadDashboardLayout`** on Placements); **`mergeDashboardLayout`** on load — no new ad-hoc storage keys for the same layout (§4.3).
564
593
  - [ ] **⌘K palette (§7.1):** If adding or changing **`dataGroups`**, map rows in **`lib/command-menu-search-data.ts`** (not `command-menu.tsx`); use **`searchOnly`** on bulky groups; keep **`docs/command-menu-pattern.md`** aligned.
565
- - [ ] **Page vs drawer vs dialog (§6.4):** Quick auxiliary with **parent context** and interactable hub → **drawer/sheet**; **blocking** short confirm → **dialog**; primary or long flows → **new route** — **`docs/data-views-pattern.md`**, **`docs/drawer-vs-dialog-pattern.md`**.
594
+ - [ ] **Page vs drawer vs dialog (§6.4):** Quick auxiliary with **parent context** and interactable hub → **drawer/sheet**; **blocking** short confirm → **dialog**; primary or long flows → **focused workflow route** or other dedicated page — **`docs/data-views-pattern.md`**, **`docs/drawer-vs-dialog-pattern.md`**, **`docs/focused-workflow-page-pattern.md`**.
566
595
  - [ ] **Cards vs rows:** Primary sortable hub with many homogeneous records → **`DataTable`**; kanban / visual tiles → **`ListPageBoardCard`** — **`docs/card-vs-rows-pattern.md`**, **`.cursor/rules/exxat-card-vs-list-rows.mdc`**.
567
596
  - [ ] **KPI count (max four):** **`entityKpiMetrics`** (and any static **`MetricItem[]`** for the same strip) has **≤ 4** tiles for template metrics + Data-tab key-metrics — **`docs/kpi-strip-max-four-pattern.md`**, **`.cursor/rules/exxat-kpi-max-four.mdc`**.
568
597
  - [ ] **No toast (§6.5):** No **`toast()`** / Sonner / snackbars — use banners, inline status, or dialogs.
@@ -574,7 +603,8 @@ Copy and complete when implementing or reviewing:
574
603
  - [ ] **Accessibility:** §8 — tablist/toolbar patterns, **≥24px** targets for icon-only controls, contrast on tinted surfaces, dialog/sheet/drawer **titles**; **every icon that communicates info has a text alternative** — adjacent label (preferred) OR `aria-label` + `Tooltip` (§8.6 Case A/B/C, covers informational icons like calendar-for-date, status dots, AND icon-only buttons); **kbd inside a button uses `<Kbd variant="bare">`** (§8.7); re-run **axe** on Placements when changing views toolbar.
575
604
  - [ ] **Coach marks (§11):** `CoachMark` + `useCoachMark`; register in **`coach-mark-registry`**; use **`enabled`** / **`dependsOnDismissedFlowId`** when a tour must wait for another flow or a specific view (e.g. **dashboard**); customize-dashboard flows use **`lib/dashboard-customize-coach-mark.ts`**.
576
605
  - [ ] **Application sidebar (§9.1):** **`ExxatProductLogo`** for product; **`logoDevUrl`** for schools; team switcher **`DropdownMenuContent`** keeps the explicit wide school/program surface (**`!w-max`** + min/max width); expanded switcher **`h-auto min-h-12`**; no **`CollapsibleTrigger` → `SidebarMenuButton` with `tooltip` prop**; child links **popover** on icon rail; profile **`stockPortraitUrl`** + **`referrerPolicy="no-referrer"`** on **`AvatarImage`**.
577
- - [ ] **Secondary panel (§4.6):** If **`NavLinkItem.secondaryPanel`** is set — **`PANELS[id]`** in **`secondary-panel.tsx`**, hub mounts **`useAutoPanel(id)`**, scope syncs to URL + **`tableState.rows`** — **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**. **Question bank folder scope:** header **⋯** → **Customize folder** + **`QuestionBankNewFolderSheet`** on **`QuestionBankClient`** — **`docs/question-bank-hub-header-pattern.md`**, **`.cursor/rules/exxat-question-bank-hub-header.mdc`**.
606
+ - [ ] **Secondary panel (§4.6):** If **`NavLinkItem.secondaryPanel`** is set — **`PANELS[id]`** in **`secondary-panel.tsx`**, hub mounts **`useAutoPanel(id)`**, scope syncs to URL + **`tableState.rows`** — **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**. Panel shell uses **`--secondary-panel-bg`** (brand OKLCH, not **`bg-sidebar`**) — **`docs/shell-surface-elevation-pattern.md`**. **Question bank folder scope:** header **⋯** → **Customize folder** + **`QuestionBankNewFolderSheet`** on **`QuestionBankClient`** — **`docs/question-bank-hub-header-pattern.md`**, **`.cursor/rules/exxat-question-bank-hub-header.mdc`**.
607
+ - [ ] **Flat KPI strip:** **`KeyMetrics variant="flat"`** — transparent cells, radial glow only, **`flatMetricsHairlineClass`** borders — **`docs/kpi-flat-band-pattern.md`**, **`.cursor/rules/exxat-kpi-flat-band.mdc`**.
578
608
  - [ ] **Collaboration & access (§4.7):** Shared hubs use **`variant="collaboration"`**, empty **Add collaborator** / non-empty face rail, **⋯ → Invite people**, **`CollaborationAccessFlow`** or **`InviteCollaboratorsDrawer`**, **`lib/collaborator-access.ts`**, roster **name → email → role tags** — **`.cursor/rules/exxat-collaboration-access.mdc`**.
579
609
  - [ ] **Dedicated search (§4.8):** Landing uses **`DedicatedSearchLandingTemplate`**; results use **`DedicatedSearchResultsHeaderChrome`** + outer **`DEDICATED_SEARCH_RESULTS_OUTER_CONTENT_CLASSNAME`**; **`DedicatedSearchUrlComposer`** + **`DedicatedSearchRecents`** with **`createDedicatedSearchRecentsController`** — **`.cursor/rules/exxat-dedicated-search-surfaces.mdc`**.
580
610
  - [ ] **KPI trends:** **`MetricItem.trend`** matches the delta direction; **`trendPolarity`** set for “more is worse” metrics (flags, defects, overdue) — **`docs/kpi-trend-pattern.md`**, **`.cursor/rules/exxat-kpi-trends.mdc`**.
@@ -583,4 +613,19 @@ Copy and complete when implementing or reviewing:
583
613
 
584
614
  ---
585
615
 
586
- *Last updated: monospace system IDs rule/skill; question bank folder-scoped header Customize + rule/skill; drawer vs dialog / card vs rows / KPI max-four pattern docs + rules + skills; §6.4 table; §4.8 dedicated search templates; §4.7 collaboration & access; §4.1 centralized dataset + presentation; §4.5–§4.6 view shells + secondary panel; Font Awesome rule; §9.1 application sidebar shell; §4.4 board cards; §6.5 no toast; §7.1 command palette; §13 checklist.*
616
+ ## 14. AI execution checklist (focused workflow route)
617
+
618
+ Copy and complete when implementing or reviewing a **dedicated form, wizard, or settings** page (not a list hub):
619
+
620
+ - [ ] **`FocusedWorkflowPageTemplate`** on **`app/(app)/…/page.tsx`** — **`siteHeader`** with **`back`** or **`breadcrumbs`** + **`title`**.
621
+ - [ ] **`maxWidth`**: **`md`** | **`lg`** | **`xl`** — not list-hub **`max-w-[1440px]`**.
622
+ - [ ] **One body layout:** **`FocusedWorkflowSingleColumn`** | **`FocusedWorkflowStepForm`** | **`FocusedWorkflowSidebarSections`** | **`FocusedWorkflowEmptyState`**.
623
+ - [ ] **Wizard / actions:** **`FocusedWorkflowWizardFooter`** or **`FocusedWorkflowActionFooter`** + **`Shortcut`** + **`<Kbd variant="bare">`** (**`exxat-kbd-shortcuts.mdc`**).
624
+ - [ ] **Domain UI** in **`*-composer.tsx`** / **`*-client.tsx`**; **`FocusedWorkflow*`** names stay generic.
625
+ - [ ] **No** **`ListPageTemplate`**, **no** **`ListPageFolderColumnsPanel`**, **no** Miller-column split explorer in this shell.
626
+ - [ ] **No toast (§6.5)** for product feedback on this route.
627
+ - [ ] **Reference:** **`/examples/focused-workflow`**, **`docs/focused-workflow-page-pattern.md`**, **`.cursor/skills/exxat-focused-workflow-page/SKILL.md`**.
628
+
629
+ ---
630
+
631
+ *Last updated: focused workflow page pattern + §14 checklist + rule/skill; KPI flat band + shell surface elevation; §4.6 secondary panel OKLCH; monospace system IDs; question bank folder header; drawer vs dialog / card vs rows / KPI max-four; §4.8 dedicated search; §4.7 collaboration; §4.1 centralized dataset; §4.5 view shells; Font Awesome; §9.1 sidebar; §4.4 board cards; §6.5 no toast; §7.1 command palette; §13 checklist.*
@@ -1,18 +1,6 @@
1
- import { Skeleton } from "@/components/ui/skeleton"
1
+ import { DashboardLoadingFallback } from "@/components/templates/page-loading-shell"
2
2
 
3
- /** Route-level fallback while the dashboard shell + tabs chunk load. */
3
+ /** Dashboard route same shell as `app/(app)/loading` dashboard variant. */
4
4
  export default function DashboardLoading() {
5
- return (
6
- <div className="flex flex-col gap-4 p-4 md:p-6" aria-busy="true" aria-label="Loading dashboard">
7
- <Skeleton className="h-9 w-56 max-w-full" />
8
- <Skeleton className="h-11 w-full max-w-xl" />
9
- <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
10
- <Skeleton className="h-24 rounded-xl" />
11
- <Skeleton className="h-24 rounded-xl" />
12
- <Skeleton className="h-24 rounded-xl" />
13
- <Skeleton className="h-24 rounded-xl" />
14
- </div>
15
- <Skeleton className="min-h-[320px] w-full rounded-xl" />
16
- </div>
17
- )
5
+ return <DashboardLoadingFallback />
18
6
  }
@@ -1,24 +1,12 @@
1
1
  import dynamic from "next/dynamic"
2
2
  import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
3
- import { Skeleton } from "@/components/ui/skeleton"
3
+ import { DashboardLoadingBody } from "@/components/templates/page-loading-shell"
4
4
  import { DASHBOARD_METRICS, DASHBOARD_INSIGHT } from "@/lib/mock/dashboard"
5
5
 
6
6
  const DashboardTabs = dynamic(
7
7
  () => import("@/components/dashboard-tabs").then(m => ({ default: m.DashboardTabs })),
8
8
  {
9
- loading: () => (
10
- <div className="flex flex-col gap-4 p-4 md:p-6" aria-busy="true" aria-label="Loading dashboard">
11
- <Skeleton className="h-9 w-56 max-w-full" />
12
- <Skeleton className="h-11 w-full max-w-xl" />
13
- <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
14
- <Skeleton className="h-24 rounded-xl" />
15
- <Skeleton className="h-24 rounded-xl" />
16
- <Skeleton className="h-24 rounded-xl" />
17
- <Skeleton className="h-24 rounded-xl" />
18
- </div>
19
- <Skeleton className="min-h-[320px] w-full rounded-xl" />
20
- </div>
21
- ),
9
+ loading: () => <DashboardLoadingBody />,
22
10
  },
23
11
  )
24
12
 
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { usePathname } from "next/navigation"
5
+
6
+ import { useSecondaryPanel } from "@/components/secondary-panel"
7
+ import { LIST_HUB_PATH } from "@/lib/list-hub-nav"
8
+
9
+ /**
10
+ * Keeps the List hub secondary panel open on `/data-list` while scope links update in place.
11
+ */
12
+ export default function DataListLayout({ children }: { children: React.ReactNode }) {
13
+ const pathname = usePathname()
14
+ const { openPanel, closePanel, activePanel } = useSecondaryPanel()
15
+ const closePanelRef = React.useRef(closePanel)
16
+ const openPanelRef = React.useRef(openPanel)
17
+
18
+ React.useEffect(() => {
19
+ closePanelRef.current = closePanel
20
+ openPanelRef.current = openPanel
21
+ })
22
+
23
+ React.useEffect(() => {
24
+ return () => {
25
+ closePanelRef.current({ mainSidebar: "leave" })
26
+ }
27
+ }, [])
28
+
29
+ React.useEffect(() => {
30
+ const normalized =
31
+ pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname
32
+ if (normalized !== LIST_HUB_PATH) {
33
+ closePanelRef.current({ mainSidebar: "leave" })
34
+ return undefined
35
+ }
36
+ if (activePanel !== "list-hub") {
37
+ openPanelRef.current("list-hub")
38
+ }
39
+ return undefined
40
+ }, [pathname, activePanel])
41
+
42
+ return children
43
+ }
@@ -1,10 +1,10 @@
1
- import { PlacementsClient } from "@/components/placements-client"
1
+ import { ListHubClient } from "@/components/list-hub-client"
2
2
  import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
3
3
 
4
4
  export default function DataListPage() {
5
5
  return (
6
6
  <PrimaryPageTemplate siteHeader={{ title: "List hub" }}>
7
- <PlacementsClient />
7
+ <ListHubClient />
8
8
  </PrimaryPageTemplate>
9
9
  )
10
10
  }
@@ -4,6 +4,7 @@ import * as React from "react"
4
4
  import { AlertCircle } from "lucide-react"
5
5
 
6
6
  import { Button } from "@/components/ui/button"
7
+ import { isChunkLoadError } from "@/lib/chunk-load-error"
7
8
 
8
9
  /**
9
10
  * Route error boundary for the signed-in app shell. Lets users retry without a full reload.
@@ -15,6 +16,8 @@ export default function AppRouteError({
15
16
  error: Error & { digest?: string }
16
17
  reset: () => void
17
18
  }) {
19
+ const chunkStale = isChunkLoadError(error)
20
+
18
21
  React.useEffect(() => {
19
22
  if (process.env.NODE_ENV === "development") {
20
23
  console.error(error)
@@ -30,14 +33,27 @@ export default function AppRouteError({
30
33
  <div className="space-y-2">
31
34
  <h1 className="text-lg font-semibold text-foreground">Something went wrong</h1>
32
35
  <p className="max-w-md text-sm text-muted-foreground">
33
- {process.env.NODE_ENV === "development"
34
- ? error.message
35
- : "Please try again. If the problem continues, contact support."}
36
+ {chunkStale
37
+ ? "The app loaded an outdated script bundle (common after a dev-server rebuild). Reload the page to fetch the latest chunks."
38
+ : process.env.NODE_ENV === "development"
39
+ ? error.message
40
+ : "Please try again. If the problem continues, contact support."}
36
41
  </p>
37
42
  </div>
38
- <Button type="button" onClick={() => reset()}>
39
- Try again
40
- </Button>
43
+ <div className="flex flex-wrap items-center justify-center gap-2">
44
+ {chunkStale ? (
45
+ <Button type="button" onClick={() => window.location.reload()}>
46
+ Reload page
47
+ </Button>
48
+ ) : null}
49
+ <Button
50
+ type="button"
51
+ variant={chunkStale ? "outline" : "default"}
52
+ onClick={() => reset()}
53
+ >
54
+ Try again
55
+ </Button>
56
+ </div>
41
57
  </div>
42
58
  )
43
59
  }
@@ -0,0 +1,5 @@
1
+ import { FocusedWorkflowShowcase } from "@/components/examples/focused-workflow-showcase"
2
+
3
+ export default function FocusedWorkflowExamplePage() {
4
+ return <FocusedWorkflowShowcase />
5
+ }
@@ -7,6 +7,7 @@ const LINKS = [
7
7
  { href: "/data-list", label: "List hub", description: "Table, list, board, and dashboard views on shared state." },
8
8
  { href: "/question-bank", label: "Question bank", description: "Discovery hub for browsing folders, recents, and AI-assisted create/import flows." },
9
9
  { href: "/question-bank/library", label: "Question library", description: "Folders, OS folder view, panel, and tree demos on mock items." },
10
+ { href: "/examples/focused-workflow", label: "Focused workflow", description: "Empty, step wizard, and sidebar section layouts for large forms." },
10
11
  { href: "/settings", label: "Settings", description: "Appearance, tours, and shell preferences." },
11
12
  { href: "/help", label: "Help", description: "Support and documentation entry points." },
12
13
  ] as const
@@ -1,6 +1,10 @@
1
- import * as React from "react"
1
+ import { cookies } from "next/headers"
2
2
  import { AppSidebar } from "@/components/app-sidebar"
3
3
  import { SidebarShell } from "@/components/sidebar-shell"
4
+ import {
5
+ SIDEBAR_STATE_COOKIE_NAME,
6
+ sidebarDefaultOpenFromCookie,
7
+ } from "@/lib/sidebar-state-cookie"
4
8
  import { DashboardViewProvider } from "@/contexts/dashboard-view-context"
5
9
  import { ChartVariantProvider } from "@/contexts/chart-variant-context"
6
10
  import { AskLeoProvider, AskLeoSidebar } from "@/components/ask-leo-sidebar"
@@ -20,11 +24,14 @@ import { COMMAND_MENU_SEARCH_DATA_GROUPS } from "@/lib/command-menu-search-data"
20
24
  * The SystemBanner is configured from Settings (persisted to localStorage
21
25
  * via SystemBannerProvider) — no hardcoded copy here.
22
26
  */
23
- export default function AppLayout({ children }: { children: React.ReactNode }) {
24
- const commandMenuConfig = React.useMemo(
25
- () => buildCommandMenuConfig({ dataGroups: COMMAND_MENU_SEARCH_DATA_GROUPS }),
26
- [],
27
+ export default async function AppLayout({ children }: { children: React.ReactNode }) {
28
+ const cookieStore = await cookies()
29
+ const sidebarDefaultOpen = sidebarDefaultOpenFromCookie(
30
+ cookieStore.get(SIDEBAR_STATE_COOKIE_NAME)?.value,
27
31
  )
32
+ const commandMenuConfig = buildCommandMenuConfig({
33
+ dataGroups: COMMAND_MENU_SEARCH_DATA_GROUPS,
34
+ })
28
35
 
29
36
  return (
30
37
  <DashboardViewProvider>
@@ -33,7 +40,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
33
40
  <SystemBannerProvider>
34
41
  <CommandMenuProvider value={commandMenuConfig}>
35
42
 
36
- <SidebarShell wrapperClassName="flex min-h-svh flex-col">
43
+ <SidebarShell defaultOpen={sidebarDefaultOpen} wrapperClassName="flex min-h-svh flex-col">
37
44
  {/* ⌘K command palette */}
38
45
  <CommandMenu />
39
46
  <SystemBannerSlot />
@@ -1,18 +1 @@
1
- import { Skeleton } from "@/components/ui/skeleton"
2
-
3
- /**
4
- * Default loading UI for app routes (sidebar chrome stays; main column shows this fallback).
5
- */
6
- export default function AppRouteLoading() {
7
- return (
8
- <div
9
- className="flex flex-col gap-4 p-6 md:p-8"
10
- aria-busy="true"
11
- aria-label="Loading page"
12
- >
13
- <Skeleton className="h-8 w-48" />
14
- <Skeleton className="h-32 w-full max-w-3xl" />
15
- <Skeleton className="h-64 w-full" />
16
- </div>
17
- )
18
- }
1
+ export { AppRouteLoading as default } from "@/components/app-route-loading"
@@ -1,11 +1,12 @@
1
1
  import { Suspense } from "react"
2
2
 
3
3
  import { QuestionBankClient } from "@/components/question-bank-client"
4
+ import { DedicatedSearchLoadingFallback } from "@/components/templates/page-loading-shell"
4
5
 
5
6
  /** Discovery hub composer results — same hub chrome as the library, distinct from `/question-bank/list`. */
6
7
  export default function QuestionBankHubFindPage() {
7
8
  return (
8
- <Suspense fallback={null}>
9
+ <Suspense fallback={<DedicatedSearchLoadingFallback />}>
9
10
  <QuestionBankClient />
10
11
  </Suspense>
11
12
  )
@@ -1,10 +1,11 @@
1
1
  import { Suspense } from "react"
2
2
 
3
3
  import { QuestionBankClient } from "@/components/question-bank-client"
4
+ import { PrimaryListHubLoadingFallback } from "@/components/templates/page-loading-shell"
4
5
 
5
6
  export default function QuestionBankLibraryPage() {
6
7
  return (
7
- <Suspense fallback={null}>
8
+ <Suspense fallback={<PrimaryListHubLoadingFallback />}>
8
9
  <QuestionBankClient />
9
10
  </Suspense>
10
11
  )
@@ -1,11 +1,12 @@
1
1
  import { Suspense } from "react"
2
2
 
3
3
  import { QuestionBankClient } from "@/components/question-bank-client"
4
+ import { DedicatedSearchLoadingFallback } from "@/components/templates/page-loading-shell"
4
5
 
5
6
  /** Question bank list surface — same hub as `/question-bank/library`, optimized for `?q=` search landings. */
6
7
  export default function QuestionBankListPage() {
7
8
  return (
8
- <Suspense fallback={null}>
9
+ <Suspense fallback={<DedicatedSearchLoadingFallback />}>
9
10
  <QuestionBankClient />
10
11
  </Suspense>
11
12
  )
@@ -1,22 +1,14 @@
1
1
  /**
2
2
  * `/question-bank/new` — full-page authoring composer.
3
3
  *
4
- * Mirrors focused flows: `SiteHeader` back nav ( Question hub) + `PageHeader` title
5
- * `SidebarAutoCollapse` mounted in `beforeSiteHeader` so the main sidebar
6
- * collapses on enter and restores to its prior state on leave
7
- * • A wider max-width than the placement form (the composer carries a
8
- * two-column document + metadata-rail layout)
9
- *
10
- * The body is `NewQuestionComposer` — see `components/new-question-composer.tsx`.
11
- *
12
- * Folder pre-selection: callers may pass `?folderId=<id>` so users dropped into
13
- * the composer from a folder-scoped library land with that folder pre-selected
14
- * in the metadata rail.
4
+ * Pattern: `FocusedWorkflowPageTemplate` + `NewQuestionComposer` (single column).
5
+ * Folder pre-selection: `?folderId=<id>` from a folder-scoped library link.
15
6
  */
16
7
 
17
8
  import { NewQuestionComposer } from "@/components/new-question-composer"
18
9
  import { SidebarAutoCollapse } from "@/components/sidebar-auto-collapse"
19
- import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
10
+ import { FocusedWorkflowPageTemplate } from "@/components/templates/focused-workflow-page-template"
11
+ import { FocusedWorkflowSingleColumn } from "@/components/templates/focused-workflow-layouts"
20
12
  import {
21
13
  DEFAULT_QUESTION_BANK_FOLDERS,
22
14
  type QuestionBankFolder,
@@ -40,19 +32,19 @@ export default async function NewQuestionPage({ searchParams }: NewQuestionPageP
40
32
  const draftQuestionId = generateDraftQuestionId()
41
33
 
42
34
  return (
43
- <PrimaryPageTemplate
35
+ <FocusedWorkflowPageTemplate
44
36
  beforeSiteHeader={<SidebarAutoCollapse />}
45
37
  siteHeader={{ back }}
46
- bodyClassName="min-h-0 flex-1 overflow-y-auto overscroll-y-contain"
47
- maxWidthClassName="mx-auto w-full max-w-[1100px]"
48
- contentClassName="px-8 py-4 pb-16"
38
+ maxWidth="lg"
49
39
  >
50
- <NewQuestionComposer
51
- draftQuestionId={draftQuestionId}
52
- defaultFolderId={defaultFolderId}
53
- backHref={back.href}
54
- folders={folders}
55
- />
56
- </PrimaryPageTemplate>
40
+ <FocusedWorkflowSingleColumn>
41
+ <NewQuestionComposer
42
+ draftQuestionId={draftQuestionId}
43
+ defaultFolderId={defaultFolderId}
44
+ backHref={back.href}
45
+ folders={folders}
46
+ />
47
+ </FocusedWorkflowSingleColumn>
48
+ </FocusedWorkflowPageTemplate>
57
49
  )
58
50
  }
@@ -1,10 +1,11 @@
1
1
  import { Suspense } from "react"
2
2
 
3
3
  import { QuestionBankHubClient } from "@/components/question-bank-hub-client"
4
+ import { QuestionBankHubLoadingFallback } from "@/components/templates/page-loading-shell"
4
5
 
5
6
  export default function QuestionBankHubPage() {
6
7
  return (
7
- <Suspense fallback={null}>
8
+ <Suspense fallback={<QuestionBankHubLoadingFallback />}>
8
9
  <QuestionBankHubClient />
9
10
  </Suspense>
10
11
  )
@@ -1,17 +1,16 @@
1
- import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
2
1
  import { SettingsClient } from "@/components/settings-client"
2
+ import { FocusedWorkflowPageTemplate } from "@/components/templates/focused-workflow-page-template"
3
3
 
4
4
  export default function SettingsPage() {
5
5
  return (
6
- <PrimaryPageTemplate
7
- maxWidthClassName="max-w-3xl"
8
- contentClassName="px-8 pt-10 pb-32"
6
+ <FocusedWorkflowPageTemplate
7
+ maxWidth="lg"
9
8
  siteHeader={{
10
9
  breadcrumbs: [{ label: "Dashboard", href: "/dashboard" }],
11
10
  title: "Settings",
12
11
  }}
13
12
  >
14
13
  <SettingsClient />
15
- </PrimaryPageTemplate>
14
+ </FocusedWorkflowPageTemplate>
16
15
  )
17
16
  }