@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.
- package/CHANGELOG.md +30 -0
- package/consumer-extras/AGENTS.md +76 -0
- package/consumer-extras/README.md +5 -1
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +14 -3
- package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +37 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +22 -7
- package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +38 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -3
- package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
- package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
- package/consumer-extras/patterns/data-views-pattern.md +42 -3
- package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +57 -0
- package/consumer-extras/patterns/shell-surface-elevation-pattern.md +54 -0
- package/package.json +2 -1
- package/src/components/ui/button-group.tsx +81 -0
- package/src/components/ui/button.tsx +4 -4
- package/src/components/ui/sidebar.tsx +2 -2
- package/src/globals.css +7 -1807
- package/src/theme.css +10 -1126
- package/src/tokens/README.md +15 -0
- package/src/tokens/base.css +337 -0
- package/src/tokens/high-contrast.css +1195 -0
- package/src/tokens/layers.css +224 -0
- package/src/tokens/tailwind-bridge.css +118 -0
- package/src/tokens/themes.css +201 -0
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -1
- package/template/AGENTS.md +66 -21
- package/template/app/(app)/dashboard/loading.tsx +3 -15
- package/template/app/(app)/dashboard/page.tsx +2 -14
- package/template/app/(app)/data-list/layout.tsx +43 -0
- package/template/app/(app)/data-list/page.tsx +2 -2
- package/template/app/(app)/error.tsx +22 -6
- package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
- package/template/app/(app)/examples/page.tsx +1 -0
- package/template/app/(app)/layout.tsx +13 -6
- package/template/app/(app)/loading.tsx +1 -18
- package/template/app/(app)/question-bank/find/page.tsx +2 -1
- package/template/app/(app)/question-bank/library/page.tsx +2 -1
- package/template/app/(app)/question-bank/list/page.tsx +2 -1
- package/template/app/(app)/question-bank/new/page.tsx +15 -23
- package/template/app/(app)/question-bank/page.tsx +2 -1
- package/template/app/(app)/settings/page.tsx +4 -5
- package/template/app/global-error.tsx +63 -0
- package/template/app/globals.css +7 -1934
- package/template/app/layout.tsx +2 -0
- package/template/components/app-route-loading.tsx +14 -0
- package/template/components/app-sidebar.tsx +71 -55
- package/template/components/data-table/index.tsx +31 -67
- package/template/components/data-table/use-table-state.ts +33 -6
- package/template/components/data-views/index.ts +37 -9
- package/template/components/data-views/list-page-calendar-view.tsx +593 -0
- package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
- package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
- package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
- package/template/components/dev-chunk-load-recovery.tsx +41 -0
- package/template/components/examples/focused-workflow-showcase.tsx +183 -0
- package/template/components/exxat-product-logo.tsx +2 -6
- package/template/components/key-metrics.tsx +54 -22
- package/template/components/list-hub-board-view.tsx +68 -0
- package/template/components/list-hub-client.tsx +186 -0
- package/template/components/list-hub-list-view.tsx +36 -0
- package/template/components/list-hub-panel-activator.tsx +8 -0
- package/template/components/list-hub-secondary-nav.tsx +121 -0
- package/template/components/list-hub-table.tsx +336 -0
- package/template/components/new-question-composer.tsx +6 -24
- package/template/components/product-switcher.tsx +5 -5
- package/template/components/product-wordmark.tsx +4 -7
- package/template/components/question-bank-client.tsx +4 -1
- package/template/components/question-bank-folder-columns-panel.tsx +104 -0
- package/template/components/question-bank-hub-client.tsx +2 -5
- package/template/components/question-bank-table.tsx +155 -509
- package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
- package/template/components/secondary-panel.tsx +4 -44
- package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
- package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
- package/template/components/secondary-panels/registry.tsx +15 -0
- package/template/components/settings-appearance-card.tsx +3 -2
- package/template/components/settings-client.tsx +59 -15
- package/template/components/settings-form-row.tsx +9 -4
- package/template/components/sidebar-shell.tsx +2 -1
- package/template/components/table-properties/drawer-button.tsx +51 -20
- package/template/components/table-properties/drawer.tsx +81 -17
- package/template/components/templates/focused-workflow-layouts.tsx +448 -0
- package/template/components/templates/focused-workflow-page-template.tsx +69 -0
- package/template/components/templates/list-page.tsx +40 -13
- package/template/components/templates/nested-secondary-panel-shell.tsx +3 -2
- package/template/components/templates/page-loading-shell.tsx +262 -0
- package/template/components/ui/button-group.tsx +1 -0
- package/template/contexts/product-context.tsx +21 -2
- package/template/docs/consumer-app-pattern.md +39 -0
- package/template/docs/data-views-pattern.md +42 -3
- package/template/docs/drawer-vs-dialog-pattern.md +3 -1
- package/template/docs/focused-workflow-page-pattern.md +84 -0
- package/template/docs/kpi-flat-band-pattern.md +57 -0
- package/template/docs/kpi-strip-max-four-pattern.md +1 -0
- package/template/docs/shell-surface-elevation-pattern.md +54 -0
- package/template/lib/chunk-load-error.ts +13 -0
- package/template/lib/command-menu-search-data.ts +11 -27
- package/template/lib/conditional-rule-match.ts +87 -22
- package/template/lib/data-list-display-options.ts +16 -2
- package/template/lib/data-list-view-registry.ts +104 -0
- package/template/lib/data-list-view-surface.ts +15 -1
- package/template/lib/data-list-view.ts +16 -1
- package/template/lib/data-view-dashboard-storage.ts +38 -35
- package/template/lib/hub-connected-view-renderers.ts +58 -0
- package/template/lib/list-hub-nav.ts +121 -0
- package/template/lib/list-hub-supported-views.ts +10 -0
- package/template/lib/list-page-table-properties.ts +3 -7
- package/template/lib/list-status-badges.ts +4 -97
- package/template/lib/mock/list-hub-directory.ts +27 -0
- package/template/lib/mock/list-hub-kpi.ts +27 -0
- package/template/lib/mock/navigation.tsx +1 -0
- package/template/lib/page-loading-variant.ts +40 -0
- package/template/lib/question-bank-supported-views.ts +13 -0
- package/template/lib/sidebar-state-cookie.ts +9 -0
- package/template/lib/table-state-lifecycle.ts +60 -13
- package/template/app/(app)/data-list/[id]/page.tsx +0 -44
- package/template/app/(app)/data-list/new/page.tsx +0 -34
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-list-view.tsx +0 -54
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -632
- package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
- package/template/components/data-view-dashboard-charts-team.tsx +0 -971
- package/template/components/data-view-dashboard-charts.tsx +0 -1503
- package/template/components/new-placement-back-btn.tsx +0 -28
- package/template/components/new-placement-form.tsx +0 -1068
- package/template/components/placement-board-card.tsx +0 -262
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -404
- package/template/components/placements-client.tsx +0 -252
- package/template/components/placements-list-view.tsx +0 -171
- package/template/components/placements-page-header.tsx +0 -166
- package/template/components/placements-table-cells.test.tsx +0 -22
- package/template/components/placements-table-cells.tsx +0 -173
- package/template/components/placements-table-columns.tsx +0 -640
- package/template/components/placements-table.tsx +0 -1675
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-all-client.tsx +0 -154
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-list-view.tsx +0 -42
- package/template/components/sites-table.tsx +0 -402
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-list-view.tsx +0 -59
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -714
- package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
- package/template/lib/mock/compliance-kpi.ts +0 -61
- package/template/lib/mock/compliance.ts +0 -146
- package/template/lib/mock/placements-kpi.ts +0 -134
- package/template/lib/mock/placements.ts +0 -183
- package/template/lib/mock/sites-directory.ts +0 -16
- package/template/lib/mock/sites-kpi.ts +0 -25
- package/template/lib/mock/team-kpi.ts +0 -60
- package/template/lib/mock/team.ts +0 -118
- package/template/lib/placement-board-card-layout.ts +0 -79
- package/template/lib/placement-lifecycle.ts +0 -5
package/template/AGENTS.md
CHANGED
|
@@ -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 **
|
|
28
|
-
16.
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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/
|
|
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
|
|
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/
|
|
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`**
|
|
427
|
-
|
|
|
428
|
-
|
|
|
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; **
|
|
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 → **
|
|
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
|
-
|
|
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 {
|
|
1
|
+
import { DashboardLoadingFallback } from "@/components/templates/page-loading-shell"
|
|
2
2
|
|
|
3
|
-
/**
|
|
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 {
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
{
|
|
34
|
-
?
|
|
35
|
-
:
|
|
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
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
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
|
}
|
|
@@ -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
|
|
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
|
|
25
|
-
|
|
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
|
-
|
|
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={
|
|
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={
|
|
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={
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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 {
|
|
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
|
-
<
|
|
35
|
+
<FocusedWorkflowPageTemplate
|
|
44
36
|
beforeSiteHeader={<SidebarAutoCollapse />}
|
|
45
37
|
siteHeader={{ back }}
|
|
46
|
-
|
|
47
|
-
maxWidthClassName="mx-auto w-full max-w-[1100px]"
|
|
48
|
-
contentClassName="px-8 py-4 pb-16"
|
|
38
|
+
maxWidth="lg"
|
|
49
39
|
>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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={
|
|
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
|
-
<
|
|
7
|
-
|
|
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
|
-
</
|
|
14
|
+
</FocusedWorkflowPageTemplate>
|
|
16
15
|
)
|
|
17
16
|
}
|