@exxatdesignux/ui 0.3.0 → 0.4.1
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 +701 -6
- package/README.md +138 -0
- package/bin/init.mjs +134 -31
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
- package/consumer-extras/handbook/HANDBOOK.md +2 -0
- package/consumer-extras/handbook/glossary.md +2 -1
- package/consumer-extras/handbook/reference-implementations.md +31 -4
- package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
- package/consumer-extras/patterns/data-views-pattern.md +18 -16
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
- package/dist/components/data-table/index.js +2 -2
- package/dist/components/data-table/index.js.map +1 -1
- package/dist/components/data-table/pagination.js +3 -3
- package/dist/components/data-table/pagination.js.map +1 -1
- package/dist/components/data-table/use-table-state.d.ts +1 -1
- package/dist/components/data-table/use-table-state.js.map +1 -1
- package/dist/components/data-views/data-row-list.js.map +1 -1
- package/dist/components/data-views/finder-panel-view.d.ts +1 -1
- package/dist/components/data-views/finder-panel-view.js.map +1 -1
- package/dist/components/data-views/hub-table.d.ts +9 -3
- package/dist/components/data-views/hub-table.js +262 -40
- package/dist/components/data-views/hub-table.js.map +1 -1
- package/dist/components/data-views/index.js +262 -40
- package/dist/components/data-views/index.js.map +1 -1
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
- package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
- package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
- package/dist/components/data-views/os-folder-glyph.js.map +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/key-metrics.js.map +1 -1
- package/dist/index.js +136 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/data-table/index.tsx +2 -2
- package/src/components/data-table/pagination.tsx +5 -1
- package/src/components/data-table/use-table-state.ts +1 -1
- package/src/components/data-views/data-row-list.tsx +1 -1
- package/src/components/data-views/finder-panel-view.tsx +2 -2
- package/src/components/data-views/hub-table.tsx +149 -41
- package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
- package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/src/components/data-views/os-folder-glyph.tsx +1 -1
- package/src/components/ui/key-metrics.tsx +1 -1
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
- package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
- package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
- package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
- package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
- package/template/AGENTS.md +43 -37
- package/template/app/(app)/columns/page.tsx +11 -0
- package/template/app/(app)/library/all/page.tsx +11 -0
- package/template/app/(app)/library/find/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
- package/template/app/(app)/library/list/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
- package/template/app/(app)/library/page.tsx +11 -0
- package/template/app/(app)/tokens-themes/page.tsx +11 -0
- package/template/components/ask-leo-composer.tsx +2 -2
- package/template/components/columns-client.tsx +158 -0
- package/template/components/columns-showcase.tsx +541 -0
- package/template/components/data-views/index.ts +32 -6
- package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
- package/template/components/data-views/table-cells.tsx +673 -0
- package/template/components/folder-details-shell.tsx +11 -11
- package/template/components/hub-tree-panel-view.tsx +24 -24
- package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
- package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
- package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
- package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
- package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
- package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
- package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
- package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
- package/template/components/library-panel-activator.tsx +8 -0
- package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
- package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
- package/template/components/list-hub-status-badge.tsx +2 -2
- package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
- package/template/components/sidebar/app-sidebar.tsx +61 -5
- package/template/components/sidebar/secondary-panel.tsx +109 -56
- package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
- package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
- package/template/components/table-properties/types.ts +1 -1
- package/template/components/templates/discovery-hub-template.tsx +1 -1
- package/template/components/templates/new-focus-template.tsx +2 -2
- package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
- package/template/components/tokens-secondary-nav.tsx +192 -0
- package/template/components/tokens-themes-client.tsx +476 -0
- package/template/components/tokens-themes-section.tsx +386 -0
- package/template/docs/HANDBOOK.md +187 -0
- package/template/docs/blueprints/README.md +1 -1
- package/template/docs/blueprints/board-card.md +1 -1
- package/template/docs/blueprints/data-table.md +2 -2
- package/template/docs/blueprints/list-page-template.md +3 -3
- package/template/docs/blueprints/page-header.md +4 -4
- package/template/docs/collaboration-access-pattern.md +7 -7
- package/template/docs/component-selection-guide.md +1 -1
- package/template/docs/data-views-pattern.md +18 -16
- package/template/docs/glossary.md +58 -0
- package/template/docs/kpi-flat-band-pattern.md +3 -3
- package/template/docs/kpi-trend-pattern.md +18 -3
- package/template/docs/large-dataset-strategy.md +155 -0
- package/template/docs/library-hub-header-pattern.md +25 -0
- package/template/docs/migrations/_template.md +1 -1
- package/template/docs/reference-implementations.md +151 -0
- package/template/docs/token-taxonomy.md +1 -1
- package/template/docs/voice-and-tone.md +262 -0
- package/template/eslint.config.mjs +9 -39
- package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
- package/template/lib/ask-leo-route-context.ts +6 -18
- package/template/lib/coach-mark-registry.ts +0 -16
- package/template/lib/command-menu-config.ts +5 -12
- package/template/lib/command-menu-search-data.ts +8 -39
- package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
- package/template/lib/library-dedicated-search.ts +19 -0
- package/template/lib/library-hub-search.ts +90 -0
- package/template/lib/library-nav.ts +477 -0
- package/template/lib/library-recent-searches.ts +22 -0
- package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
- package/template/lib/list-status-badges.ts +16 -104
- package/template/lib/mock/dashboard.ts +1 -1
- package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
- package/template/lib/mock/library-header-collaborators.ts +54 -0
- package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
- package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
- package/template/lib/mock/library.ts +249 -0
- package/template/lib/mock/navigation.tsx +32 -26
- package/template/lib/table-state-lifecycle.ts +1 -1
- package/template/next.config.mjs +7 -4
- package/template/package.json +0 -1
- package/tokens/hooks-index.json +2874 -0
- package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
- package/template/app/(app)/examples/page.tsx +0 -41
- package/template/app/(app)/question-bank/find/page.tsx +0 -12
- package/template/app/(app)/question-bank/library/page.tsx +0 -11
- package/template/app/(app)/question-bank/list/page.tsx +0 -12
- package/template/app/(app)/question-bank/page.tsx +0 -11
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -468
- 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 -942
- package/template/components/placement-board-card.tsx +0 -250
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -397
- package/template/components/placements-client.tsx +0 -220
- package/template/components/placements-list-view.tsx +0 -124
- 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 -210
- package/template/components/placements-table.tsx +0 -934
- package/template/components/question-bank-panel-activator.tsx +0 -8
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-client.tsx +0 -154
- package/template/components/sites-table.tsx +0 -249
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -553
- package/template/docs/question-bank-hub-header-pattern.md +0 -25
- package/template/lib/compliance-supported-views.ts +0 -10
- 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 -176
- package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
- package/template/lib/mock/question-bank.ts +0 -249
- 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/question-bank-dedicated-search.ts +0 -19
- package/template/lib/question-bank-hub-search.ts +0 -90
- package/template/lib/question-bank-nav.ts +0 -477
- package/template/lib/question-bank-recent-searches.ts +0 -22
- package/template/lib/question-bank-supported-views.ts +0 -12
- package/template/lib/sites-supported-views.ts +0 -10
- package/template/lib/team-supported-views.ts +0 -10
|
@@ -89,7 +89,7 @@ ID token gets `font-mono tabular-nums`, the rest of the line stays sans.
|
|
|
89
89
|
| `base` | Default — most routes | Title + actions only |
|
|
90
90
|
| `object-home` | Lists / hubs with optional metrics + tabs **below** the header | Adds a count/freshness slot under the title |
|
|
91
91
|
| `record-home` | Detail / record views | May expose a small detail row (label/value pairs) — keep ≤ 5 pairs |
|
|
92
|
-
| `collaboration` | Shared hubs (
|
|
92
|
+
| `collaboration` | Shared hubs (Library, future) | Adds face rail + invite entry — see [`exxat-collaboration-access.mdc`](../../../.cursor/rules/exxat-collaboration-access.mdc) |
|
|
93
93
|
|
|
94
94
|
Pick **one** variant per header. Combining `collaboration` with
|
|
95
95
|
`record-home` is allowed only when the record itself is a shareable
|
|
@@ -99,7 +99,7 @@ artifact (e.g. a question).
|
|
|
99
99
|
|
|
100
100
|
| Framework | Component(s) | File |
|
|
101
101
|
|---|---|---|
|
|
102
|
-
| **React (this app)** | `PageHeader` (shell) + per-hub headers (composition) | [`apps/web/components/page-header.tsx`](../../components/page-header.tsx), `
|
|
102
|
+
| **React (this app)** | `PageHeader` (shell) + per-hub headers (composition) | [`apps/web/components/page-header.tsx`](../../components/page-header.tsx), [`apps/web/components/library-page-header.tsx`](../../components/library-page-header.tsx) |
|
|
103
103
|
| Mobile | — | — |
|
|
104
104
|
| Figma | — | — |
|
|
105
105
|
|
|
@@ -107,7 +107,7 @@ Reference hub-level compositions:
|
|
|
107
107
|
|
|
108
108
|
- **Placements** — filled primary CTA `New placement` + `⋯` (Export, Customize…)
|
|
109
109
|
- **Team** — same shape, count + freshness in subtitle
|
|
110
|
-
- **
|
|
110
|
+
- **Library** — `variant="collaboration"` + folder-scoped customize entry
|
|
111
111
|
|
|
112
112
|
## 8. Do / Don't
|
|
113
113
|
|
|
@@ -124,7 +124,7 @@ Reference hub-level compositions:
|
|
|
124
124
|
|
|
125
125
|
- [`apps/web/docs/data-views-pattern.md`](../data-views-pattern.md) — `PageHeader` in context of `ListPageTemplate`
|
|
126
126
|
- [`apps/web/docs/collaboration-access-pattern.md`](../collaboration-access-pattern.md) — `variant="collaboration"`
|
|
127
|
-
- [`apps/web/docs/
|
|
127
|
+
- [`apps/web/docs/library-hub-header-pattern.md`](../library-hub-header-pattern.md) — folder-scoped header
|
|
128
128
|
- [`.cursor/rules/exxat-collaboration-access.mdc`](../../../.cursor/rules/exxat-collaboration-access.mdc)
|
|
129
129
|
- [`.cursor/rules/exxat-mono-ids.mdc`](../../../.cursor/rules/exxat-mono-ids.mdc)
|
|
130
130
|
- [`apps/web/AGENTS.md`](../../AGENTS.md) §4.7, §6.2, §9
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Collaboration & access pattern
|
|
2
2
|
|
|
3
|
-
Shared UI for **who can access a hub** (face stack in the header) and **inviting people** (floating sheet). **Reference:**
|
|
3
|
+
Shared UI for **who can access a hub** (face stack in the header) and **inviting people** (floating sheet). **Reference:** Library — `LibraryPageHeader`, `LibraryClient`, `InviteCollaboratorsDrawer`.
|
|
4
4
|
|
|
5
|
-
**Folder-scoped
|
|
5
|
+
**Folder-scoped library:** When the library URL selects a folder (`?scope=folder&folderId=`), the same header **⋯ More** menu also exposes **Customize folder** (name / color / icon) via **`LibraryNewFolderSheet`** mounted on **`LibraryClient`** so it works on every view tab. See **`docs/library-hub-header-pattern.md`** and **`.cursor/rules/exxat-library-hub-header.mdc`**.
|
|
6
6
|
|
|
7
7
|
## When to use
|
|
8
8
|
|
|
@@ -33,11 +33,11 @@ Shared UI for **who can access a hub** (face stack in the header) and **inviting
|
|
|
33
33
|
|
|
34
34
|
```tsx
|
|
35
35
|
<CollaborationAccessFlow
|
|
36
|
-
initialCollaborators={
|
|
36
|
+
initialCollaborators={LIBRARY_HEADER_COLLABORATORS}
|
|
37
37
|
resourceLabel={hubHeader.title}
|
|
38
38
|
>
|
|
39
39
|
{({ collaborators, openInvite }) => (
|
|
40
|
-
<
|
|
40
|
+
<LibraryPageHeader
|
|
41
41
|
variant="collaboration"
|
|
42
42
|
title={hubHeader.title}
|
|
43
43
|
questionCount={count}
|
|
@@ -100,9 +100,9 @@ Row order:
|
|
|
100
100
|
| Hub flow | `components/collaboration-access-flow.tsx` |
|
|
101
101
|
| Collaborator type | `components/page-header.tsx` (`PageHeaderCollaborator`) |
|
|
102
102
|
| Invite sheet | `components/invite-collaborators-drawer.tsx` |
|
|
103
|
-
| Entity header | `components/
|
|
104
|
-
| Hub wiring | `components/
|
|
105
|
-
| Demo roster | `lib/mock/
|
|
103
|
+
| Entity header | `components/library-page-header.tsx` |
|
|
104
|
+
| Hub wiring | `components/library-client.tsx` |
|
|
105
|
+
| Demo roster | `lib/mock/library-header-collaborators.ts` |
|
|
106
106
|
|
|
107
107
|
## Checklist (new hub)
|
|
108
108
|
|
|
@@ -56,7 +56,7 @@ primitive after asking the user
|
|
|
56
56
|
| Export | Filled primary CTA + `⋯` → `ExportDrawer` |
|
|
57
57
|
| Kanban view body | **`ListPageBoardCard`** + `ListPageBoardTemplate` ([`exxat-board-cards.mdc`](../../.cursor/rules/exxat-board-cards.mdc)) |
|
|
58
58
|
| Folder / panel view body | **`FolderGridView`** / **`FinderPanelView`** wrapped in **`ListPageViewFrame`** ([`exxat-list-page-view-shells.mdc`](../../.cursor/rules/exxat-list-page-view-shells.mdc)) |
|
|
59
|
-
| Dashboard view body | **`KeyMetrics variant="card"`** + chart
|
|
59
|
+
| Dashboard view body | **`KeyMetrics variant="card"`** + a hub-specific chart section (reference: `library-dashboard-charts.tsx`) |
|
|
60
60
|
| Nested scope nav (All / Mine / tree) | **`secondaryPanel`** + `PANELS` + `useAutoPanel` ([`exxat-primary-nav-secondary-panel.mdc`](../../.cursor/rules/exxat-primary-nav-secondary-panel.mdc)) |
|
|
61
61
|
| Shared hub w/ invite | **`PageHeader` `variant="collaboration"`** + `InviteCollaboratorsDrawer` |
|
|
62
62
|
| Dedicated search (empty `?q=` vs results) | **`DedicatedSearchLandingTemplate`** + **`DedicatedSearchResultsHeaderChrome`** |
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> **Canonical rules for agents (MUST/MUST NOT, checklists):** [`AGENTS.md`](../AGENTS.md) in this package (including **§8 Accessibility**). This file is the long-form narrative; keep both aligned when patterns change.
|
|
4
4
|
|
|
5
|
+
> **Scaling past ~2K rows:** see [`large-dataset-strategy.md`](./large-dataset-strategy.md) for pagination, server-mode upgrade path, and the virtualization follow-up.
|
|
6
|
+
|
|
5
7
|
This document describes how list pages combine **views**, **toolbar** behavior, **filters**, **properties**, and **persistence** in this codebase.
|
|
6
8
|
|
|
7
9
|
## Reuse existing components (required)
|
|
@@ -10,18 +12,18 @@ This document describes how list pages combine **views**, **toolbar** behavior,
|
|
|
10
12
|
|
|
11
13
|
| Need | Reuse | Where Placements uses it |
|
|
12
14
|
| --- | --- | --- |
|
|
13
|
-
| **View tabs** (table / list / board, lifecycle filters) | `ListPageTemplate` (`ViewTab`, `renderContent`, optional metrics + export) | `components/
|
|
14
|
-
| **Table shell** (search, filter bar, sort, grouping, columns, pagination) | `DataTable`, `DataTableToolbar`, `useTableState` | `components/data-table/`, `components/
|
|
15
|
-
| **Properties drawer** (display, columns, filters, sort, view type tiles) | `TablePropertiesDrawer` from `@/components/table-properties` | `DrawerToolbar` / list–board shells in `
|
|
15
|
+
| **View tabs** (table / list / board, lifecycle filters) | `ListPageTemplate` (`ViewTab`, `renderContent`, optional metrics + export) | `components/library-hub-client.tsx` + `components/templates/list-page.tsx` |
|
|
16
|
+
| **Table shell** (search, filter bar, sort, grouping, columns, pagination) | `HubTable` → `DataTable`, `DataTableToolbar`, `useTableState` | `components/data-table/`, `components/data-views/hub-table.tsx`, `components/columns-showcase.tsx` |
|
|
17
|
+
| **Properties drawer** (display, columns, filters, sort, view type tiles) | `TablePropertiesDrawer` from `@/components/table-properties` (auto-wired by `HubTable`) | `DrawerToolbar` / list–board shells in `library-table.tsx` |
|
|
16
18
|
| **Board / list** | `PlacementsBoardView`, `PlacementListRowContent` (wrapped by `HubTable.renderListRow` → `DataRowList`) + same `useTableState` | `PlacementsTable` |
|
|
17
|
-
| **Page header** (primary CTA + More ⋯ + export) | `
|
|
18
|
-
| **
|
|
19
|
-
| **
|
|
20
|
-
| **Dashboard view (list tab)** | **`KeyMetrics`** (`variant="flat"` or `"card"`) — same KPI system as the template metrics strip; **do not** add ad-hoc `Card` grids for entity summaries | `
|
|
21
|
-
| **List hub metrics strip** | **`KeyMetrics variant="flat"`** — transparent cells, OKLCH brand glow only, border hairlines (**no** grey panel) | **`docs/kpi-flat-band-pattern.md`**, Placements / Team /
|
|
22
|
-
| **Secondary panel chrome** | **`--secondary-panel-bg`** on **`NestedSecondaryPanelShell`** (lighter than sidebar, follows active product) | **`docs/shell-surface-elevation-pattern.md`**,
|
|
19
|
+
| **Page header** (primary CTA + More ⋯ + export) | Per-hub page header composing `PageHeader` + `ExportDrawer` button | `components/library-page-header.tsx` |
|
|
20
|
+
| **Primary hub composition** | `*Client` = `ListPageTemplate` + `KeyMetrics` + `*PageHeader` + `*Table`. Reference: `LibraryHubClient`. | `components/library-hub-client.tsx`, `components/columns-client.tsx`, `components/tokens-themes-client.tsx` |
|
|
21
|
+
| **Hub table body** | `HubTable` — wraps `DataTable` + `useTableState` + `TablePropertiesDrawer`; list / board / dashboard read **`tableState.rows`**. Pagination chrome is auto-mounted when the hub passes `pagination` + `onPaginationChange`. | `components/data-views/hub-table.tsx`, `components/library-table.tsx`, `components/columns-showcase.tsx` |
|
|
22
|
+
| **Dashboard view (list tab)** | **`KeyMetrics`** (`variant="flat"` or `"card"`) — same KPI system as the template metrics strip; **do not** add ad-hoc `Card` grids for entity summaries. | `LibraryTable` dashboard branch, `components/library-dashboard-charts.tsx` |
|
|
23
|
+
| **List hub metrics strip** | **`KeyMetrics variant="flat"`** — transparent cells, OKLCH brand glow only, border hairlines (**no** grey panel) | **`docs/kpi-flat-band-pattern.md`**, Placements / Team / Library clients |
|
|
24
|
+
| **Secondary panel chrome** | **`--secondary-panel-bg`** on **`NestedSecondaryPanelShell`** (lighter than sidebar, follows active product) | **`docs/shell-surface-elevation-pattern.md`**, Library |
|
|
23
25
|
| **Export** | `ExportDrawer` | `ListPageTemplate` export props; `PlacementsClient` |
|
|
24
|
-
| **View body layout** (gutter + centered max-width for folder / icon / panel-style content) | **`ListPageViewFrame`** (`components/data-views/list-page-view-frame.tsx`, re-exported from `components/data-views`) | **`FolderGridView`** (uses the frame); **`
|
|
26
|
+
| **View body layout** (gutter + centered max-width for folder / icon / panel-style content) | **`ListPageViewFrame`** (`components/data-views/list-page-view-frame.tsx`, re-exported from `components/data-views`) | **`FolderGridView`** (uses the frame); **`LibraryOsFolderView`** — see **`AGENTS.md` §4.5** |
|
|
25
27
|
|
|
26
28
|
**Rules:** (1) Import and compose these components; pass **props** and **column defs** for your entity. (2) If something is missing, **extend the shared component** under `components/` (e.g. a new optional slot on `DataTableToolbar`) rather than copying markup into a single page. (3) Card-only or lightweight pages may use a smaller **Properties** sheet only when there is **no** table—otherwise use `TablePropertiesDrawer` with `DataTable` (see Team).
|
|
27
29
|
|
|
@@ -89,13 +91,13 @@ Non-table view branches (e.g. **folder** icon grid, **panel** finder, OS-style f
|
|
|
89
91
|
- **Status (Team & Compliance)** — `lib/list-status-badges.ts` — single source for label strings + badge `className` tails for **table, list, and board**. Do **not** pair with `uppercase` on the Badge (labels are sentence case, aligned with Placements `BoardStatusBadge`).
|
|
90
92
|
- **Owner initials** — `lib/initials-from-name.ts` when mock rows have a display name but no `initials` field.
|
|
91
93
|
- **Shared column shell** — `components/data-views/list-page-board-template.tsx` — `ListPageBoardTemplate` + `ListPageBoardColumnDef<T>`: define columns with `filter` predicates, `renderCard`, `getRowKey`. Used by **Team** and **Compliance** boards; new hubs should start here before custom chrome.
|
|
92
|
-
- **
|
|
94
|
+
- **Library board** — `components/library-board-view.tsx` — composes **`ListPageBoardCard`** parts with `BoardCardTwoLineBlock` for the body and `ListHubStatusBadge` for the status row. Use this as the reference for any new domain board card.
|
|
93
95
|
- New entities should add their own card component that composes **`ListPageBoardCard`** + primitives rather than duplicating column scroll/layout or ad-hoc card chrome.
|
|
94
96
|
|
|
95
97
|
## Dashboard view (list pages)
|
|
96
98
|
|
|
97
|
-
- **Reuse the dashboard chart bundle** — `components/dashboard-report-charts.tsx` — `DashboardReportCharts`: flat `KeyMetrics` + middle chart section + period comparison `KeyMetrics` card. **`ChartsOverview`**
|
|
98
|
-
- **Data tab canvas charts**
|
|
99
|
+
- **Reuse the dashboard chart bundle** — `components/dashboard-report-charts.tsx` — `DashboardReportCharts`: flat `KeyMetrics` + middle chart section + period comparison `KeyMetrics` card. **`ChartsOverview`** is the default middle section for `/dashboard`. Hubs pass **`chartsSection={<MyDashboardChartsSection rows={tableState.rows} />}`** so charts reflect the hub's own row set. Reference: **`LibraryDashboardChartsSection`** in `components/library-dashboard-charts.tsx`. Chart **style** can follow `ChartVariantProvider` when using `ChartsOverview`.
|
|
100
|
+
- **Data tab canvas charts** share **`ChartFigure`**, **`ChartCard`**, and **`ChartDataTable`** with `charts-overview.tsx`. **Layout** is stored in one place: **`lib/data-view-dashboard-storage.ts`** under a per-hub scope (see `AGENTS.md` §4.3). **Keyboard-selected bars/slices** must use **`lib/chart-keyboard-selection.ts`** (`activeBar` / `activeShape`) so behavior matches the gallery — not opacity-only `Cell` dimming.
|
|
99
101
|
|
|
100
102
|
## Persistence
|
|
101
103
|
|
|
@@ -130,13 +132,13 @@ Below the threshold, these may be omitted unless the page is a primary data hub
|
|
|
130
132
|
|
|
131
133
|
## Data pages: primary CTA + More + Export
|
|
132
134
|
|
|
133
|
-
If the page **has exportable data** (rows, members,
|
|
135
|
+
If the page **has exportable data** (rows, members, library items, etc.), follow the **Library** header pattern:
|
|
134
136
|
|
|
135
|
-
1. **Primary action** — One default (filled) button for the main task (e.g. **New
|
|
137
|
+
1. **Primary action** — One default (filled) button for the main task (e.g. **New question**, **Invite collaborator**, **Add token**). Do **not** use `variant="outline"` for that primary action.
|
|
136
138
|
2. **More (⋯)** — An outline **icon** button opening a menu that includes **Export** (and other overflow actions). Wire **Export** to `ExportDrawer` (or equivalent).
|
|
137
139
|
3. **Subtitle** — Prefer a short line with **count + freshness** (e.g. `24 records · Last updated now`), matching `PlacementsPageHeader`.
|
|
138
140
|
|
|
139
|
-
Reference: `components/
|
|
141
|
+
Reference: `components/library-page-header.tsx`, `components/library-hub-client.tsx`.
|
|
140
142
|
|
|
141
143
|
---
|
|
142
144
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Exxat DS — Glossary
|
|
2
|
+
|
|
3
|
+
> Shared vocabulary. When a rule or pattern uses a term in **bold**, it's defined here. Add a term when you find yourself explaining the same word twice in PR review.
|
|
4
|
+
|
|
5
|
+
| Term | Definition | See also |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| **Active view** | The currently selected view tab on a hub (table, list, board, dashboard, …). Drives which `HubTableRenderers` entry mounts and what the **Properties drawer** shows in its view-type tile grid. | `exxat-table-properties-drawer.mdc` |
|
|
8
|
+
| **Bare Kbd** | The `<Kbd variant="bare">` rendering — no background, no border, inherits `currentColor` at 70 %. Used inline **inside** a button so the chord doesn't look pasted on. The default `tile` variant is reserved for tooltips and menu shortcut slots. | `exxat-kbd-shortcuts.mdc` |
|
|
9
|
+
| **Blueprint** | Framework-agnostic spec for one UI pattern. Says *what* the pattern is and *what* it must do without committing to React. See `docs/blueprints/`. | `docs/blueprints/README.md` |
|
|
10
|
+
| **Board card** | The kanban-style card surface on a board view. Composed of `ListPageBoardCard` (shell), `ListPageBoardCardTitleRow`, optional `ListPageBoardCardAvatar`, optional badge row with `ListHubStatusBadge` (`surface="board"`), and a body using `BoardCardTwoLineBlock` / `BoardCardIconRow`. | `exxat-board-cards.mdc` |
|
|
11
|
+
| **Brand glow** | The OKLCH-mixed brand tint applied as a soft halo behind a surface (KPI flat band, secondary panel). Not a hard background — the tint **adds**, the underlying surface still reads. | `docs/kpi-flat-band-pattern.md`, `docs/shell-surface-elevation-pattern.md` |
|
|
12
|
+
| **Bulk-actions slot** | The floating action bar that appears when one or more rows are selected in a `HubTable`. Wired via `bulkActionsSlot={(selected) => …}`. | `packages/ui/src/components/data-views/hub-table.tsx` |
|
|
13
|
+
| **Cell pattern** | One column's rendering recipe — built by composing existing primitives (`Badge`, `AvatarInitials`, `ListHubStatusBadge`, `ToggleSwitch`, FA glyphs, `Tip`, `Tooltip`, `DropdownMenu`). The canonical catalog of patterns lives in `apps/web/components/columns-showcase.tsx`; the live demo is at `/columns`. | `docs/reference-implementations.md` |
|
|
14
|
+
| **Cell primitive** | One of the named, importable `ColumnDef['cell']` renderers exported from `@/components/data-views` (re-exported from `apps/web/components/data-views/table-cells.tsx`): `ProgressCell`, `CurrencyCell`, `NumericCell`, `RatingCell`, `SignalBarsCell`, `BooleanToggleCell`, `AttachmentCountCell`, `ExternalLinkCell`, `RelativeTimeCell`, `PeopleAvatarRailCell`, `PillCell`, `TagListCell`, `RowActionsCell`. Every cell renderer in the showcase comes from this module; new hubs **must** import these instead of inlining the same JSX. | `.cursor/skills/exxat-token-economy/SKILL.md` §3, `exxat-data-tables.mdc` |
|
|
15
|
+
| **Centralized list dataset** | The rule that **one** `useTableState` row bag drives **every** view (table, list, board, dashboard, panel, tree, …) on a given hub. No parallel mock arrays per view. | `exxat-centralized-list-dataset.mdc` |
|
|
16
|
+
| **Coach mark** | The onboarding overlay that highlights a UI element and explains it. State managed by `useCoachMark`. Dismissals stick per flow id. | `apps/web/lib/coach-mark-registry.ts` |
|
|
17
|
+
| **Collaboration variant** | The `PageHeader` flavor that exposes the face rail + Invite-people overflow item for shared hubs. | `exxat-collaboration-access.mdc` |
|
|
18
|
+
| **Column def (`ColumnDef`)** | The typed column shape consumed by `DataTable` / `HubTable`. Adding `filter:` to a column auto-generates a filter chip and toolbar dropdown entry. | `packages/ui/src/components/data-table/types.ts` |
|
|
19
|
+
| **Conditional rule** | A `ColumnDef`-level rule that applies a background tint to a cell when its value matches an operator + value (e.g. "score < 60 → red"). Authored in the **Properties drawer**. | `packages/ui/src/components/table-properties/` |
|
|
20
|
+
| **Content rail** | The centered, max-width column the `PrimaryPageTemplate` reserves for primary content. Width comes from the template, not the page; don't override per-page. | `apps/web/components/templates/primary-page-template.tsx` |
|
|
21
|
+
| **DataTable** | The low-level table primitive in `packages/ui`. Mount this **only** outside a hub (drawer-body mini-grids, modal sub-tables). Inside `ListPageTemplate`, use `HubTable`. | `exxat-data-tables.mdc` |
|
|
22
|
+
| **`delta`** | The KPI **count** of change (e.g. `"+5"`, `-3`, `"+12 %"`) on a `MetricItem`. Pass `""` or `0` to suppress the trend chip. Never prose. | `exxat-kpi-trends.mdc` |
|
|
23
|
+
| **`description`** | The KPI **caption** beneath the value and trend row on a `MetricItem`. Use this for prose like `"left + right"` or `"vs last week"`. Never a delta count. | `exxat-kpi-trends.mdc` |
|
|
24
|
+
| **Display options** | Per-table preferences (toolbar search visibility, density, gridlines, pagination toggle, …) that flow through `DataListDisplayOptions`. `HubTable` owns the state by default; the hub client can take it over with `displayOptions` + `onDisplayOptionsChange`. | `packages/ui/src/components/table-properties/` |
|
|
25
|
+
| **Empty state** | The text + icon + (optional) action shown when a view body has zero rows after filters. Distinct from "no data ever" (use `EmptyTableState`) vs "no matches" (filter-aware copy). | `docs/voice-and-tone.md` |
|
|
26
|
+
| **Face rail** | The small overlapping-faces row on a `PageHeader` (collaboration variant) showing collaborators. Click to open the invite drawer. | `exxat-collaboration-access.mdc` |
|
|
27
|
+
| **Filter chip** | The dismissible chip rendered above a `HubTable` body for each active filter. Auto-generated from `ColumnDef.filter`. | `exxat-data-tables.mdc` |
|
|
28
|
+
| **Flat band (KPI)** | `KeyMetrics variant="flat"` — transparent cell, brand glow only, hairline cell borders. The shape used on every primary hub metrics strip. | `exxat-kpi-flat-band.mdc` |
|
|
29
|
+
| **Hairline** | A 1 px border at exactly `--border` color used to separate KPI cells, table cells, surface divisions. Never thicker for hierarchy — use spacing or color instead. | `docs/kpi-flat-band-pattern.md` |
|
|
30
|
+
| **HubTable** | The canonical hub view body. Wraps `useTableState`, the toolbar (search + filter chips + filter dropdown + sort), `TablePropertiesDrawerButton`, view-type tiles, bulk-actions, and conditional rules. Always use this inside `ListPageTemplate.renderContent`. | `exxat-data-tables.mdc` |
|
|
31
|
+
| **Hub primitive** | Synonym for `HubTable` — the single primitive that produces a "hub-shaped" view body. | same |
|
|
32
|
+
| **Inspector** | A side-anchored drawer that shows a single record's details without leaving the hub. Resolved against the same `tableState.rows` as the grid. | `exxat-centralized-list-dataset.mdc` |
|
|
33
|
+
| **`KeyMetrics`** | The KPI strip / band component. Accepts `MetricItem[]` (≤ 4) and a single `MetricInsight`. Use `variant="flat"` on hubs; `variant="card"` for embedded analytics cards. | `exxat-kpi-flat-band.mdc`, `exxat-kpi-max-four.mdc` |
|
|
34
|
+
| **KPI strip** | The horizontal KPI row at the top of a hub. ≤ 4 tiles. | `exxat-kpi-max-four.mdc` |
|
|
35
|
+
| **Lifecycle tab label** | The string shown under "Properties" in the drawer header (e.g. "Library", "Tokens & themes", "Columns"). Set on `HubTable.lifecycleTabLabel`. | `packages/ui/src/components/table-properties/drawer-button.tsx` |
|
|
36
|
+
| **List hub status badge** | The shared status chip + icon used everywhere status appears (table rows, board cards, list rows). Colors and icons live in `lib/list-status-badges.ts`. `surface="table"` for grid; `surface="board"` for cards. | `exxat-board-cards.mdc` |
|
|
37
|
+
| **List page template** | The hub frame component. Owns the page header slot, metrics strip slot, view-tabs row, and the `renderContent(tab, updateTab)` body callback. | `packages/ui/src/components/templates/list-page.tsx` |
|
|
38
|
+
| **Mono ID** | A system-generated identifier shown in `font-mono tabular-nums` so digits align and the ID is visibly distinct from prose. Mono **only** the ID token in a mixed line. | `exxat-mono-ids.mdc` |
|
|
39
|
+
| **OKLCH** | The perceptually uniform color space used by the brand-tint mix and the glow tokens. Surfaces stack: page → secondary panel → sidebar. | `docs/shell-surface-elevation-pattern.md` |
|
|
40
|
+
| **Pattern** | Long-form narrative that explains a UI behavior in prose: when to use it, how it composes, anti-patterns, references. Lives in `docs/*-pattern.md`. Complements (but doesn't replace) a blueprint or rule. | `docs/HANDBOOK.md` |
|
|
41
|
+
| **`PrimaryPageTemplate`** | The page chrome: breadcrumbs, site header, content rail with the project's standard max-width. Wrap every primary route in this. | `apps/web/components/templates/primary-page-template.tsx` |
|
|
42
|
+
| **Progressive disclosure** | The principle that complexity is exposed only when the user opts into it. KPIs default visible, filters default folded into the toolbar, conditional rules live in the Properties drawer, etc. | `docs/HANDBOOK.md` §1 |
|
|
43
|
+
| **Properties drawer** | The right-side `Sheet` opened from the table toolbar, hosting view-type tiles, column visibility, density, sort, group-by, filters, conditional rules, and pagination toggle. Mounted automatically by `HubTable`. | `exxat-table-properties-drawer.mdc` |
|
|
44
|
+
| **Reference page** | A canonical full implementation of a hub or pattern in `apps/web/components/*.tsx` (e.g. `columns-showcase.tsx`, `tokens-themes-client.tsx`, `library-table.tsx`). Listed in `docs/reference-implementations.md`. Copy from these before inventing. | `docs/reference-implementations.md` |
|
|
45
|
+
| **Rule (binding)** | A `.cursor/rules/*.mdc` doc with MUST / MUST NOT. Binds the AI agent and the human reviewer. Wins over patterns and narratives. | `docs/HANDBOOK.md` §4 |
|
|
46
|
+
| **Secondary panel** | A scoped navigation rail (e.g. "Library → All / Mine / Tree", "Tokens & themes → Colors / Radius / Motion / …") that sits between the main sidebar and the page. Opening one collapses the main sidebar; closing one restores the previous sidebar state. | `exxat-primary-nav-secondary-panel.mdc` |
|
|
47
|
+
| **Site header** | The top bar on a primary route (org/product switcher + breadcrumbs + actions). Owned by `PrimaryPageTemplate`. | `apps/web/components/templates/primary-page-template.tsx` |
|
|
48
|
+
| **Skill** | A `.cursor/skills/<name>/SKILL.md` (mirrored in `.claude/skills/`) workflow + checklist for a recurring agent task. Use a skill when the same checklist would be repeated across many sessions. | `apps/web/AGENTS.md` |
|
|
49
|
+
| **`supportedViewTypes`** | The allowlist of `DataListViewType` values a hub implements. Passed to `HubTable.supportedViewTypes` so the Properties drawer never offers a view the hub can't render. | `packages/ui/src/components/data-views/hub-table.tsx` |
|
|
50
|
+
| **Trend polarity** | `MetricItem.trendPolarity` says whether "up" is good (`higher_is_better`, default), bad (`lower_is_better`), or value-neutral (`informational`). The arrow's tint follows the polarity, not the sign. | `exxat-kpi-trends.mdc` |
|
|
51
|
+
| **`useTableState`** | The state hook that owns rows, filters, search, sort, pagination, group-by, and column visibility. Always one instance per hub. | `exxat-centralized-list-dataset.mdc` |
|
|
52
|
+
| **View tab** | A tab on `ListPageTemplate` representing one view of the same dataset (table, list, board, dashboard, folder, panel, tree, …). Each tab carries a `viewType` and the `renderContent` callback receives it. | `exxat-list-page-connected-views.mdc` |
|
|
53
|
+
| **View type (`DataListViewType`)** | The enum of view shapes the design system supports — `"table" \| "list" \| "board" \| "dashboard" \| "folder" \| "panel" \| "tree"`. Each tab declares one. | `apps/web/lib/data-list-view.ts` |
|
|
54
|
+
| **Voice & tone** | The microcopy rules for the product: empty states, errors, banners, buttons, validation. See `docs/voice-and-tone.md`. | `docs/voice-and-tone.md` |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
*Missing a term? Add it. Glossary entries are short — link to the rule or pattern for depth, don't restate it here.*
|
|
@@ -24,7 +24,7 @@ List hubs and the main dashboard mix view use **`KeyMetrics variant="flat"`** as
|
|
|
24
24
|
|
|
25
25
|
## MUST NOT
|
|
26
26
|
|
|
27
|
-
- Add **`--key-metrics-flat-band-linear`** back into `flatBandStyle` or hub inline styles (e.g.
|
|
27
|
+
- Add **`--key-metrics-flat-band-linear`** back into `flatBandStyle` or hub inline styles (e.g. library hub hero).
|
|
28
28
|
- Use **`variant="card"`** on **`ListPageTemplate`** metrics when the design calls for a **flat strip** on the page background.
|
|
29
29
|
- Duplicate KPI numbers in ad-hoc **`Card`** grids on the same hub.
|
|
30
30
|
- Set **`variant="mutedSuffix"`** on product wordmarks to grey out the **suffix** in dark mode — suffix stays **Exxat pink** (`wordmarkColor`); see **`lib/product-brand.ts`**.
|
|
@@ -42,9 +42,9 @@ Dark mode (`.dark`): same rules — transparent cells, radial glow only, no line
|
|
|
42
42
|
|
|
43
43
|
## Reference implementations
|
|
44
44
|
|
|
45
|
-
- `components/
|
|
45
|
+
- `components/library-client.tsx` — `KeyMetrics variant="flat" metricsSingleRow`
|
|
46
46
|
- `components/dashboard-tabs.tsx` — mix view flat band + insight
|
|
47
|
-
- `components/
|
|
47
|
+
- `components/library-hub-client.tsx`, `columns-client.tsx`, `tokens-themes-client.tsx` — list hub metrics slot
|
|
48
48
|
|
|
49
49
|
## Insight rail (flat + side-by-side)
|
|
50
50
|
|
|
@@ -13,10 +13,23 @@
|
|
|
13
13
|
| Field | Role |
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| `value` | Current bucket total or rate (formatted string or number). |
|
|
16
|
-
| `delta` |
|
|
17
|
-
| `
|
|
16
|
+
| `delta` | **Count change for the trend chip**, e.g. `+5`, `-3`, `+12%`. Pass `""` (or `0`) when there is no comparison this period — the chip is then **hidden**, not rendered as `—`. **Never** put captions or labels like `"left + right"` here. |
|
|
17
|
+
| `description` | Optional **caption** rendered **below** the value + trend row (muted, small). Use for *what* moved or *how* the value breaks down — `"left + right"`, `"vs last week"`, `"across 4 sites"`, `"scheduled for removal"`. |
|
|
18
|
+
| `trend` | **Visual direction** of the delta: more → `up`, less → `down`, flat / N/A → `neutral`. Combined with an empty `delta`, `neutral` collapses the chip. |
|
|
18
19
|
| `trendPolarity` | Optional. **`higher_is_better`** (default) \| **`lower_is_better`** \| **`informational`**. |
|
|
19
20
|
|
|
21
|
+
### Layout
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────────────────────────────────────┐
|
|
25
|
+
│ Pinned columns │ ← label
|
|
26
|
+
│ 2 ↑ +1 │ ← value + (delta in chip)
|
|
27
|
+
│ left + right │ ← description (caption)
|
|
28
|
+
└─────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The **only** thing next to the arrow is the **count**. Prose lives **below** the value as a description. When there is no direction *and* no count, the chip is **suppressed** entirely (no `—` placeholder).
|
|
32
|
+
|
|
20
33
|
## Polarity cheat sheet
|
|
21
34
|
|
|
22
35
|
| `trendPolarity` | Use when | Up arrow tint | Down arrow tint |
|
|
@@ -36,7 +49,9 @@
|
|
|
36
49
|
|
|
37
50
|
- Forcing **`trend: "up"`** green because “up feels good” when the metric is **defects** or **flags** — set **`lower_is_better`** instead.
|
|
38
51
|
- Hiding a worsening metric by flipping the arrow without changing **`trend`** — arrows must match the data.
|
|
39
|
-
- Using **`informational`** for KPIs that **do** have an agreed quality bar — pick a polarity
|
|
52
|
+
- Using **`informational`** for KPIs that **do** have an agreed quality bar — pick a polarity instead.
|
|
53
|
+
- Rendering an **empty `—`** chip just for layout symmetry. The component already hides the chip when there is no direction *and* no count; leave `delta: ""` + `trend: "neutral"` and use `description` for the supporting caption.
|
|
54
|
+
- Putting captions like **`"left + right"`**, **`"hidden"`**, **`"shown"`**, **`"vs last week"`** in **`delta`**. Those are not deltas. Use **`description`**.
|
|
40
55
|
|
|
41
56
|
## Related surfaces
|
|
42
57
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Large dataset strategy
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Large dataset strategy
|
|
6
|
+
|
|
7
|
+
How the Exxat DS table stack scales as record counts grow — what works today, when to
|
|
8
|
+
turn pagination on, and what to add when the dataset outgrows the browser. Cross-linked
|
|
9
|
+
from [`apps/web/AGENTS.md`](../AGENTS.md) and [`data-views-pattern.md`](./data-views-pattern.md).
|
|
10
|
+
|
|
11
|
+
## TL;DR
|
|
12
|
+
|
|
13
|
+
| Row count | What to do |
|
|
14
|
+
|---|---|
|
|
15
|
+
| **≤ 200** | Default `HubTable` setup. No pagination. Filter / sort all in memory. |
|
|
16
|
+
| **200 – 5K** | Turn pagination on (`pagination={true}` on `HubTable`). Default page size 10–25. List + board views auto-virtualize at 100 rows via `DataRowList`. |
|
|
17
|
+
| **5K – 50K** | Stay client-side with pagination, but consider adding `@tanstack/react-virtual` to the `DataTable` `<tbody>` (follow-up; see below). Filtering all rows in memory still works — `useTableState` is sub-100ms at 50K on a typical laptop. |
|
|
18
|
+
| **> 50K** | Switch to **server mode**: lift filters / sort / page out of `useTableState` via `paginationOverride` and fetch one page at a time. The hub composition (`ListPageTemplate` + `HubTable` + Properties drawer) does not change. |
|
|
19
|
+
|
|
20
|
+
## Today (client mode, in-memory)
|
|
21
|
+
|
|
22
|
+
The default `HubTable` rendering path is fully client-side:
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
flowchart LR
|
|
26
|
+
Mock["lib/mock/* (or API on first paint)"] --> Rows["rows: TRow[] (full dataset)"]
|
|
27
|
+
Rows --> UTS["useTableState(rows, columns, sort, paginationOverride?)"]
|
|
28
|
+
UTS --> FRows["state.rows (filtered + sorted)"]
|
|
29
|
+
UTS --> PRows["state.pagedRows (sliced when pagination is on)"]
|
|
30
|
+
FRows --> DT["DataTable body (renders every row in pagedRows)"]
|
|
31
|
+
FRows --> DRL["DataRowList (auto-virtualizes at 100 rows)"]
|
|
32
|
+
FRows --> Board["ListPageBoardTemplate (virtualizes per-column)"]
|
|
33
|
+
FRows --> Dash["Dashboard charts"]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Key properties:
|
|
37
|
+
|
|
38
|
+
1. **One row bag, every view.** Table / list / board / dashboard / folder / panel all read
|
|
39
|
+
`tableState.rows` (already filtered + sorted). No parallel arrays — see
|
|
40
|
+
[`.cursor/rules/exxat-centralized-list-dataset.mdc`](../../.cursor/rules/exxat-centralized-list-dataset.mdc).
|
|
41
|
+
2. **`useTableState` is the only filter pass.** Every keystroke in the toolbar search or any
|
|
42
|
+
filter-chip change re-runs the predicate set across the full input array. This is
|
|
43
|
+
in-memory `Array.filter` / `Array.sort` — fast enough to be invisible up to ~50K rows on
|
|
44
|
+
typical hardware.
|
|
45
|
+
3. **Table grid does NOT virtualize today.** When pagination is off, `DataTable` renders
|
|
46
|
+
every row in `state.rows` as a `<tr>`. With pagination on, it only renders the current
|
|
47
|
+
page — typically 10–25 rows — so DOM cost is constant regardless of the dataset size.
|
|
48
|
+
4. **List + board views DO virtualize.** `DataRowList` uses `@tanstack/react-virtual` with
|
|
49
|
+
a `virtualizeThreshold` (default 100) so 5K rows in the **list** tab paint instantly
|
|
50
|
+
even without pagination. `ListPageBoardTemplate` slices per column with the same
|
|
51
|
+
threshold.
|
|
52
|
+
|
|
53
|
+
## When to turn on pagination
|
|
54
|
+
|
|
55
|
+
`HubTable` accepts a `pagination={true}` prop that wires:
|
|
56
|
+
|
|
57
|
+
- The Properties drawer's **Show pagination** toggle.
|
|
58
|
+
- `CountSyncer` so filter changes reset to page 1.
|
|
59
|
+
- `PaginationBar` glued to the bottom of the table card (sticky at viewport bottom on
|
|
60
|
+
overflow).
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<HubTable
|
|
64
|
+
rows={items}
|
|
65
|
+
columns={columns}
|
|
66
|
+
pagination
|
|
67
|
+
paginationInitialPageSize={25}
|
|
68
|
+
paginationPageSizeOptions={[10, 25, 50, 100]}
|
|
69
|
+
// …
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Rule of thumb: **once the table no longer fits on one screen, enable pagination.** That's
|
|
74
|
+
typically around 100 rows for product grids with avatars / two-line cells. Below that,
|
|
75
|
+
scroll-everything feels lighter than chrome.
|
|
76
|
+
|
|
77
|
+
## Beyond 50K — server mode
|
|
78
|
+
|
|
79
|
+
`useTableState` accepts a `paginationOverride: { page, pageSize }` so the hub owner can
|
|
80
|
+
lift page state out of the table. Pair that with a `fetcher(page, pageSize, filters,
|
|
81
|
+
sort) => Promise<{ rows, total }>` and you have classic server-side pagination without
|
|
82
|
+
touching any DS primitive.
|
|
83
|
+
|
|
84
|
+
Skeleton (planned, not implemented today):
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
function MyHubClient() {
|
|
88
|
+
const [page, setPage] = useState(1)
|
|
89
|
+
const [pageSize, setPageSize] = useState(25)
|
|
90
|
+
const [filters, setFilters] = useState<FilterStateShape>(EMPTY_FILTERS)
|
|
91
|
+
const [sort, setSort] = useState<SortState>(DEFAULT_SORT)
|
|
92
|
+
|
|
93
|
+
// Server fetches one page at a time, returns 25 rows + total count
|
|
94
|
+
const { data, isLoading } = useQuery({
|
|
95
|
+
queryKey: ["my-hub", page, pageSize, filters, sort],
|
|
96
|
+
queryFn: () => fetchMyHubPage({ page, pageSize, filters, sort }),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<HubTable
|
|
101
|
+
rows={data?.rows ?? []}
|
|
102
|
+
paginationOverride={{ page, pageSize }}
|
|
103
|
+
// …filters / sort wired the same way
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
What stays the same:
|
|
110
|
+
|
|
111
|
+
- `HubTable` composition (toolbar + filter chips + Properties + view tabs).
|
|
112
|
+
- `ColumnDef` shape, including `filter:` blocks.
|
|
113
|
+
- The Properties drawer Display / Filter / Sort / Columns / Conditional rules panels.
|
|
114
|
+
|
|
115
|
+
What changes:
|
|
116
|
+
|
|
117
|
+
- The filter / sort / page state is **lifted** out of `useTableState`'s internal reducer
|
|
118
|
+
and into the hub client (so it can be threaded into the fetcher).
|
|
119
|
+
- A loading row state in `DataTable` (already supported via `emptyState`; we'd add a
|
|
120
|
+
skeleton variant when the data array is empty but a fetch is in-flight).
|
|
121
|
+
|
|
122
|
+
## Follow-up: row virtualization for `DataTable`
|
|
123
|
+
|
|
124
|
+
For "infinite scroll" dense grids (Notion / Airtable feel) without pagination, add
|
|
125
|
+
`@tanstack/react-virtual` to `DataTable`'s `<tbody>`:
|
|
126
|
+
|
|
127
|
+
- The package is already a dependency (used by `DataRowList`).
|
|
128
|
+
- The mechanics are very similar to `DataRowList` — measure visible viewport height,
|
|
129
|
+
render only the `<tr>` rows in the visible window, padding the head/tail with empty
|
|
130
|
+
rows of the right pixel height.
|
|
131
|
+
- Caveats: row pinning (left / right sticky columns) needs care; row-detail "expanded"
|
|
132
|
+
rows complicate height estimation; `groupable` rows would need flat indexing.
|
|
133
|
+
|
|
134
|
+
This is documented as a follow-up. Not on the critical path because pagination handles
|
|
135
|
+
the same scaling concern with simpler ergonomics.
|
|
136
|
+
|
|
137
|
+
## What we do NOT do
|
|
138
|
+
|
|
139
|
+
- **No infinite-scroll-by-default on the table grid.** Pagination is explicit, keyboard
|
|
140
|
+
reachable, and links well. Infinite scroll on a primary product grid hurts deep-link
|
|
141
|
+
reachability and keyboard navigation.
|
|
142
|
+
- **No per-cell async hydration.** Every cell render reads from the row prop synchronously.
|
|
143
|
+
Async cell content (e.g. user avatar from a separate endpoint) is a column-level
|
|
144
|
+
concern — the column's `cell:` renderer can `useQuery` for that data, but it should not
|
|
145
|
+
block the row from painting.
|
|
146
|
+
- **No client-side fetch waterfalls.** When the dataset moves to server mode, fetch the
|
|
147
|
+
page once with the filter / sort / page key. Don't fan out per-row fetches.
|
|
148
|
+
|
|
149
|
+
## See also
|
|
150
|
+
|
|
151
|
+
- [`exxat-data-tables.mdc`](../../.cursor/rules/exxat-data-tables.mdc) — `HubTable` is the
|
|
152
|
+
only product-data-list stack.
|
|
153
|
+
- [`exxat-centralized-list-dataset.mdc`](../../.cursor/rules/exxat-centralized-list-dataset.mdc)
|
|
154
|
+
— one `useTableState` row bag, every view.
|
|
155
|
+
- [`data-views-pattern.md`](./data-views-pattern.md) — connected-view architecture.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Library hub header — folder scope + Customize folder
|
|
2
|
+
|
|
3
|
+
**Audience:** Engineers extending the library library hub (`LibraryClient`, `LibraryPageHeader`, URL scope).
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
The library uses **`ListPageTemplate`** with multiple **view tabs** (table, panel, tree, …). **`LibraryNewFolderSheet`** (customize mode) is also used inside **`LibraryTable`** for some views (e.g. panel columns). If **Customize folder** exists only there, users on **table** or other tabs **cannot** open the sheet from a consistent chrome entry point when the URL is scoped to a folder (`?scope=folder&folderId=…`).
|
|
8
|
+
|
|
9
|
+
## Pattern
|
|
10
|
+
|
|
11
|
+
1. **`LibraryPageHeader`** exposes optional **`onCustomizeFolder?: () => void`**. When **`navState.scope === "folder"`** and **`navState.folderId`** is set, the hub client passes a callback that opens customize mode for the matching **`LibraryFolder`**.
|
|
12
|
+
2. **`LibraryClient`** (or equivalent hub client) mounts **`LibraryNewFolderSheet`** **once** beside **`SecondaryPanelHubTemplate` / `ListPageTemplate`**, with local state for **`open`** and **`customizingFolder`**. Saving updates **`folders`** the same way as table-embedded customize flows.
|
|
13
|
+
3. The header **⋯ More** menu order stays aligned with **§4.7**: **Invite people** (when collaboration variant) → **Customize folder** (when folder-scoped) → **Export** → **Show / hide metric section** (when applicable).
|
|
14
|
+
|
|
15
|
+
## References
|
|
16
|
+
|
|
17
|
+
| Piece | Location |
|
|
18
|
+
|-------|-----------|
|
|
19
|
+
| Header prop + menu item | `components/library-page-header.tsx` |
|
|
20
|
+
| Client wiring + sheet | `components/library-client.tsx` |
|
|
21
|
+
| URL scope | `lib/library-nav.ts` (`parseLibraryNav`, `LibraryNavState`) |
|
|
22
|
+
| Sheet UI | `components/library-new-folder-sheet.tsx` |
|
|
23
|
+
|
|
24
|
+
**Cursor rule:** `.cursor/rules/exxat-library-hub-header.mdc`
|
|
25
|
+
**Handbook:** `AGENTS.md` §4.6 (folder-scoped hub chrome).
|
|
@@ -55,7 +55,7 @@ node packages/ui/scripts/migrate-NNNN-<slug>.mjs
|
|
|
55
55
|
- [ ] No remaining matches for `<old pattern>` in code search (excluding this
|
|
56
56
|
migration file)
|
|
57
57
|
- [ ] Visual regression sweep on affected hubs (Placements / Team /
|
|
58
|
-
Compliance /
|
|
58
|
+
Compliance / Library)
|
|
59
59
|
|
|
60
60
|
## References
|
|
61
61
|
|