@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
|
@@ -22,7 +22,7 @@ description: >
|
|
|
22
22
|
- **App root:** `apps/web/app/(app)/` — route group that wraps all authenticated pages
|
|
23
23
|
- **Single source of truth:** `apps/web/AGENTS.md` for full prose explanations; this skill is the actionable summary
|
|
24
24
|
- **Companion skills (narrow topics):** `exxat-fontawesome-icons`, `exxat-mono-ids`, `exxat-primary-nav-secondary-panel`, `exxat-centralized-list-dataset`, `exxat-list-page-view-shells`, `exxat-dedicated-search-surfaces`, `exxat-accessibility`, `exxat-board-cards`, `exxat-collaboration-access` — live under `.cursor/skills/`; vetted copies ship with **`@exxatdesignux/ui`** in `consumer-extras/cursor-skills/` after **`pnpm --filter @exxatdesignux/ui vendor:consumer-extras`**.
|
|
25
|
-
- **
|
|
25
|
+
- **Library folder-scoped header (rule + doc):** **`.cursor/rules/exxat-library-hub-header.mdc`** and **`docs/library-hub-header-pattern.md`** — pair with **`exxat-primary-nav-secondary-panel`** when URL **`scope=folder`** drives the hub title.
|
|
26
26
|
- **Consumer repos (npm install of `@exxatdesignux/ui`):** After a version bump, read **`node_modules/@exxatdesignux/ui/CHANGELOG.md`**, run **`npx --package=@exxatdesignux/ui@latest exxat-ui sync-extras`** so **`docs/exxat-ds/consumer-upgrade-checklist.md`** and Cursor skills match the tarball, and diff the host app against **`node_modules/@exxatdesignux/ui/template/`** for anything new to port (routes, re-exports, AGENTS). Use **`exxat-ui changelog`**, **`exxat-ui update`**, and **`exxat-ui doctor`** for CLI guidance.
|
|
27
27
|
|
|
28
28
|
---
|
|
@@ -105,15 +105,15 @@ A primary nav row that owns sub-routes has **three possible shapes** — pick ex
|
|
|
105
105
|
|
|
106
106
|
| Shape | When to use | Where it lives |
|
|
107
107
|
|---|---|---|
|
|
108
|
-
| **A. Collapsible children** (e.g.
|
|
108
|
+
| **A. Collapsible children** (e.g. Library → All / My / Favorites / Folders) | Small finite child list that benefits from inline browsing (≤ 40 items, no extra page chrome). | `NavLinkItem.children` rendered by **`CollapsibleNavItem`** in `app-sidebar.tsx`. |
|
|
109
109
|
| **B. Secondary panel** (separate nested rail) | Same nav row needs **scoped search / tree / metrics** alongside the hub content. | `NavLinkItem.secondaryPanel = "<id>"` + `PANELS[id]` — see companion skill `exxat-primary-nav-secondary-panel`. |
|
|
110
|
-
| **C. Both A + B on one row** (
|
|
110
|
+
| **C. Both A + B on one row** (Library does this) | Most cases when a hub has a sub-list AND a rich rail. The sidebar still shows the collapsible children; clicking the parent route also opens the secondary panel via `useAutoPanel`. | Combine A + B; the active-state and animation rules in §3.2 still apply to the sidebar children. |
|
|
111
111
|
|
|
112
112
|
#### Active-state rules — **the single biggest mistake to avoid**
|
|
113
113
|
|
|
114
114
|
1. **Expanded sidebar (full rail):** parent stays **visually neutral** when a child is active — **never double-highlight**. `isCollapsibleParentMenuButtonActive` returns `false` if `anyChildActive` so the active child row carries `data-active` alone.
|
|
115
115
|
2. **Collapsed sidebar (icon rail):** the parent icon is the **only** affordance, so it lights up when **any child** route is active. Implementation: `iconRailActive = isAnyChildActive` is fed into `SidebarMenuButton.isActive` and selects `item.iconActive` (`fa-solid`) over `item.icon` (`fa-light`).
|
|
116
|
-
3. **Tooltip / aria copy** in icon mode names the parent (e.g. "
|
|
116
|
+
3. **Tooltip / aria copy** in icon mode names the parent (e.g. "Library — open subpages"); the **popover** content lists children with their own active state so users see which sub-route is selected.
|
|
117
117
|
|
|
118
118
|
```tsx
|
|
119
119
|
// Inside CollapsibleNavItem
|
|
@@ -296,7 +296,7 @@ Align with **`apps/web/AGENTS.md` §6.4**, **`docs/data-views-pattern.md`**, **`
|
|
|
296
296
|
| `ColumnDef` from `@/components/data-table/types` | Column type |
|
|
297
297
|
| `FilterFieldDef`, `FilterOperator`, `ConditionalRule` from `@/components/table-properties/types` | Filter types |
|
|
298
298
|
|
|
299
|
-
**Board (kanban) cards:** Use **`ListPageBoardCard`** and related parts from **`components/data-views/list-page-board-card.tsx`**; **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** from **`board-card-primitives.tsx`**. **List hub** status (Team, Compliance,
|
|
299
|
+
**Board (kanban) cards:** Use **`ListPageBoardCard`** and related parts from **`components/data-views/list-page-board-card.tsx`**; **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** from **`board-card-primitives.tsx`**. **List hub** status (Team, Compliance, Library, …): maps in **`lib/list-status-badges.ts`**; render with **`ListHubStatusBadge`** (**`surface="table"`** in table/list, **`surface="board"`** on cards); semantic tints **`LIST_HUB_STATUS_TINT_*`** for new domains; no **`uppercase`**. **Placements** uses **`StatusBadge`** in **`placements-table-cells.tsx`** (wrapper over **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`**). **Full rules:** **`apps/web/AGENTS.md` §4.4**, **`.cursor/rules/exxat-board-cards.mdc`**, **`.cursor/skills/exxat-board-cards/SKILL.md`**.
|
|
300
300
|
|
|
301
301
|
**Minimum required features on any data list page:**
|
|
302
302
|
- Search (wire `searchable={displayOptions.showToolbarSearch}`)
|
|
@@ -401,9 +401,9 @@ Use `PageHeader` from `@/components/page-header` for the content-area header (be
|
|
|
401
401
|
|
|
402
402
|
When a hub is **shared**, use **`PageHeader` `variant="collaboration"`**: **empty roster** → outline **Add collaborator**; **non-empty** → face rail (faces / **`+N`** open the invite sheet). **Invite people** also lives under the entity header **⋯ More** and opens **`InviteCollaboratorsDrawer`** via **`CollaborationAccessFlow`** when possible. Library access (Owner / Editor / Commenter / Viewer) comes from **`lib/collaborator-access.ts`**; directory tags (Faculty, Program coordinator, Director) use **`PageHeaderCollaborator.roles`**.
|
|
403
403
|
|
|
404
|
-
**
|
|
404
|
+
**Library library — folder URL scope:** When **`?scope=folder&folderId=`** applies, **⋯ More** must also offer **Customize folder** (**`LibraryPageHeader`** **`onCustomizeFolder`**) and the **`LibraryNewFolderSheet`** must be mounted on **`LibraryClient`** so it works on every **`ListPageTemplate`** view tab. **`.cursor/rules/exxat-library-hub-header.mdc`** · **`docs/library-hub-header-pattern.md`** (app: **`apps/web/docs/...`**).
|
|
405
405
|
|
|
406
|
-
**Handbook:** `apps/web/AGENTS.md` §4.7 · **Doc:** `docs/collaboration-access-pattern.md` · **Skill:** `.cursor/skills/exxat-collaboration-access/SKILL.md` · **Reference:**
|
|
406
|
+
**Handbook:** `apps/web/AGENTS.md` §4.7 · **Doc:** `docs/collaboration-access-pattern.md` · **Skill:** `.cursor/skills/exxat-collaboration-access/SKILL.md` · **Reference:** Library header + client.
|
|
407
407
|
|
|
408
408
|
---
|
|
409
409
|
|
|
@@ -30,7 +30,7 @@ user-invocable: true
|
|
|
30
30
|
|
|
31
31
|
- `apps/web/components/key-metrics.tsx` — `flatMetricsHairlineClass`, `flatBandStyle`
|
|
32
32
|
- `apps/web/app/globals.css` — `--key-metrics-flat-*`
|
|
33
|
-
- `
|
|
33
|
+
- `library-client.tsx`, `dashboard-tabs.tsx` — reference usage
|
|
34
34
|
|
|
35
35
|
## Pair with
|
|
36
36
|
|
|
@@ -21,7 +21,7 @@ description: >-
|
|
|
21
21
|
- Pass **`maxWidthClassName={LIST_PAGE_VIEW_FRAME_MAX_WIDE}`** when the view includes toolbar rows + breadcrumbs + grid.
|
|
22
22
|
2. **Do not double-gutter** — If **`DataTable`** already provides horizontal inset for the **table** view, do **not** wrap that branch in **`ListPageViewFrame`**. Use the frame on **sibling** view branches (folder, panel, etc.) only.
|
|
23
23
|
3. **Reuse before inventing** — Prefer **`FolderGridView`**, **`FinderPanelView`**, **`ListPageBoardTemplate`**, **`ListPageViewFrame`**. If a new pattern appears twice, **promote** it to **`components/data-views/`** with domain-agnostic props.
|
|
24
|
-
4. **Entity-specific → generic** — If logic is “any tree + any row type”, build **`components/data-views/<generic-name>.tsx`** and keep **`
|
|
24
|
+
4. **Entity-specific → generic** — If logic is “any tree + any row type”, build **`components/data-views/<generic-name>.tsx`** and keep **`library-*`** (or similar) as a thin composition + mock types.
|
|
25
25
|
|
|
26
26
|
## Checklist
|
|
27
27
|
|
|
@@ -39,11 +39,11 @@ Always include **`tabular-nums`** with **`font-mono`** so fixed-width digits ali
|
|
|
39
39
|
|
|
40
40
|
| Surface | File |
|
|
41
41
|
|---------|------|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
| New question subtitle | `components/new-
|
|
42
|
+
| Library table | `components/library-table.tsx` — `row.questionId` |
|
|
43
|
+
| Library list | `components/library-list-view.tsx` |
|
|
44
|
+
| New question subtitle | `components/new-library-item-form.tsx` — `questionId` in `PageHeader` subtitle |
|
|
45
45
|
| Sites record id | `components/sites-table.tsx` — `row.id` |
|
|
46
|
-
| OS folder tiles | `components/
|
|
46
|
+
| OS folder tiles | `components/library-os-folder-view.tsx` |
|
|
47
47
|
|
|
48
48
|
## Review checklist
|
|
49
49
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: exxat-primary-nav-secondary-panel
|
|
3
|
-
description: Exxat DS pattern — one primary sidebar row opens a nested SecondaryPanel (
|
|
3
|
+
description: Exxat DS pattern — one primary sidebar row opens a nested SecondaryPanel (Library). NavLinkItem.secondaryPanel id, PANELS registry, useAutoPanel on hub, URL scope + same useTableState rows. Use when adding hub scoped nav (All/My/tree) beside content.
|
|
4
4
|
user-invocable: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,10 +13,10 @@ user-invocable: true
|
|
|
13
13
|
|
|
14
14
|
1. **`lib/mock/navigation.tsx`** — set **`secondaryPanel: "<id>"`** on the primary **`NavLinkItem`**; **`url`** = hub route.
|
|
15
15
|
2. **`components/secondary-panel.tsx`** — add **`PANELS["<id>"]`** → panel shell (title, optional search) + secondary nav component.
|
|
16
|
-
3. **Hub client** — mount **`*PanelActivator`** with **`useAutoPanel("<id>")`** (same id) for the lifetime of the route (e.g. `
|
|
17
|
-
4. **Data** — keep **one** **`useTableState`** / **`tableState.rows`**; drive scope from **URL** + small helpers (see **`lib/
|
|
18
|
-
5. **Folder-scoped hub header (
|
|
19
|
-
6. **Collapse control** — the nested rail header uses **`collapseActiveSecondaryPanel()`** (angles-left icon), not “close”, so the panel stays dismissed until **`openPanel`** runs again (nav, scope hook, or hub re-entry). Layout effects that auto-call **`openPanel`** must respect **`secondaryPanelAutoReopenSuppressed`** (see **`app/(app)/
|
|
16
|
+
3. **Hub client** — mount **`*PanelActivator`** with **`useAutoPanel("<id>")`** (same id) for the lifetime of the route (e.g. `LibraryPanelActivator`).
|
|
17
|
+
4. **Data** — keep **one** **`useTableState`** / **`tableState.rows`**; drive scope from **URL** + small helpers (see **`lib/library-nav.ts`**).
|
|
18
|
+
5. **Folder-scoped hub header (Library library)** — When **`scope === "folder"`** in the URL, **`LibraryPageHeader`** **⋯ More** includes **Customize folder**; mount **`LibraryNewFolderSheet`** on **`LibraryClient`** so it works on **all** **`ListPageTemplate`** view tabs — **`.cursor/rules/exxat-library-hub-header.mdc`**, **`docs/library-hub-header-pattern.md`**.
|
|
19
|
+
6. **Collapse control** — the nested rail header uses **`collapseActiveSecondaryPanel()`** (angles-left icon), not “close”, so the panel stays dismissed until **`openPanel`** runs again (nav, scope hook, or hub re-entry). Layout effects that auto-call **`openPanel`** must respect **`secondaryPanelAutoReopenSuppressed`** (see **`app/(app)/library/layout.tsx`** + **`SecondaryPanelProvider`**).
|
|
20
20
|
7. **Surface elevation** — secondary panel = **level 1** (lighter than sidebar, darker than page). Use **`--secondary-panel-bg`** on **`NestedSecondaryPanelShell`**; derive from **`--brand-tint*`** per active product (**One** indigo, **Prism** rose). See **`docs/shell-surface-elevation-pattern.md`**.
|
|
21
21
|
|
|
22
22
|
## MUST NOT
|
|
@@ -30,7 +30,7 @@ user-invocable: true
|
|
|
30
30
|
|
|
31
31
|
`SecondaryPanelProvider` reads **`useSidebarReflowZoom()`** (zoom ≥ 200% or very short viewport — same WCAG 1.4.10 signal the primary sidebar uses). On entering high zoom it sets `secondaryPanelCompact = true` so the 16 rem rail drops to the 3 rem icon variant and frees up content space. The user can still re-expand manually (via the icon rail's "Show labels" affordance or any `openPanel` trigger); the next zoom-out → zoom-in cycle re-collapses it. `openPanel` itself opens directly in compact mode when high zoom is already active so newly-navigated panels don't flash expanded.
|
|
32
32
|
|
|
33
|
-
Custom panel content (anything you register under `PANELS[id]`) should **read `secondaryPanelCompact` from the provider context** and render an icon-only layout in that branch — `
|
|
33
|
+
Custom panel content (anything you register under `PANELS[id]`) should **read `secondaryPanelCompact` from the provider context** and render an icon-only layout in that branch — `LibraryPanel` / `LibrarySecondaryNav` are the reference.
|
|
34
34
|
|
|
35
35
|
## Pair with
|
|
36
36
|
|
|
@@ -45,5 +45,5 @@ Custom panel content (anything you register under `PANELS[id]`) should **read `s
|
|
|
45
45
|
- `app/globals.css` — `--secondary-panel-bg`, `--sidebar`, product **`theme-one`** / **`theme-prism`** blocks.
|
|
46
46
|
- `contexts/product-context.tsx` — `accentOverrideActive`, theme class on `<html>`.
|
|
47
47
|
- **`docs/shell-surface-elevation-pattern.md`**
|
|
48
|
-
- `components/
|
|
49
|
-
- **Folder-scoped header customize:** `components/
|
|
48
|
+
- `components/library-secondary-nav.tsx` + `lib/library-nav.ts`.
|
|
49
|
+
- **Folder-scoped header customize:** `components/library-page-header.tsx`, `components/library-client.tsx` — **`docs/library-hub-header-pattern.md`**, **`.cursor/rules/exxat-library-hub-header.mdc`**.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exxat-token-economy
|
|
3
|
+
description: >-
|
|
4
|
+
Cut AI token usage by ~50% on Exxat DS work — task → minimum-file-set table,
|
|
5
|
+
five-question pre-flight that catches the top rule violations before
|
|
6
|
+
generation, canonical primitive aliases (no grep needed), tiny scaffolds
|
|
7
|
+
for hub client / column def / KPI item, and a deny-list of files the
|
|
8
|
+
assistant should NOT open. Read this BEFORE opening any other DS doc.
|
|
9
|
+
user-invocable: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Exxat DS — token economy (read this first)
|
|
13
|
+
|
|
14
|
+
**Why this exists.** Every other DS skill / rule / pattern doc is a deep dive on
|
|
15
|
+
one concern. When the user just wants to build a screen, the assistant tends to:
|
|
16
|
+
|
|
17
|
+
1. Re-read `AGENTS.md` (~80 KB) on every turn.
|
|
18
|
+
2. `grep` to "find the Button" when the import path is already known.
|
|
19
|
+
3. Regenerate hub scaffolding it could copy from a reference page.
|
|
20
|
+
4. Read 6 rule files speculatively when only 2 apply.
|
|
21
|
+
5. Re-explain "we use HubTable because…" in 200 tokens.
|
|
22
|
+
|
|
23
|
+
This skill is the **pre-flight** for any DS work. Read it first; load other docs
|
|
24
|
+
only when this skill explicitly points to one. **Target: ≥ 50% fewer input
|
|
25
|
+
tokens per design turn vs. the naive "open AGENTS.md and grep" loop.**
|
|
26
|
+
|
|
27
|
+
## When to use this skill
|
|
28
|
+
|
|
29
|
+
- **First** turn of any new DS feature, hub, or design change.
|
|
30
|
+
- Any time the assistant is about to open `AGENTS.md` or a `*-pattern.md` doc — read this skill instead and follow §1's file-set table.
|
|
31
|
+
- Whenever the user complains about cost, latency, or "you keep re-reading the same files".
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## §1 — Task → minimum file set
|
|
36
|
+
|
|
37
|
+
Open **only** these files. Skip everything else unless one of these files cites it.
|
|
38
|
+
|
|
39
|
+
| Task | Read (minimum) | Skip (do not open) |
|
|
40
|
+
|------|----------------|--------------------|
|
|
41
|
+
| **Build a new hub page** (table + KPIs + view tabs) | `apps/web/components/columns-showcase.tsx` (or any reference hub), `docs/exxat-ds/handbook/HANDBOOK.md` §"How to build a hub" | `AGENTS.md`, `hub-table.tsx` source, all `*-pattern.md` |
|
|
42
|
+
| **Add a column / cell pattern** | `apps/web/components/columns-showcase.tsx` (the live catalog) | `data-table/index.tsx`, the whole `types.ts` |
|
|
43
|
+
| **Add a board / kanban view** | `apps/web/components/placements-board-card.tsx`, `exxat-board-cards.mdc` only | All other rules |
|
|
44
|
+
| **Add a KPI strip** | `docs/exxat-ds/handbook/reference-implementations.md` § KPI flat band, `exxat-kpi-max-four.mdc`, `exxat-kpi-trends.mdc` | `key-metrics.tsx` source |
|
|
45
|
+
| **Write empty-state / error / button copy** | `docs/exxat-ds/handbook/voice-and-tone.md` | All rules |
|
|
46
|
+
| **Theme / token tweak** | `apps/web/app/globals.css`, `packages/ui/tokens/hooks-index.json` | All pattern docs |
|
|
47
|
+
| **Status chip color/icon** | `apps/web/lib/list-status-badges.ts` | Accessibility rule (already covered by the map) |
|
|
48
|
+
| **Bug fix** | `rg` for the symbol, read only the matching file | Everything else |
|
|
49
|
+
| **Architectural change** (only when the user explicitly says so) | `AGENTS.md`, `HANDBOOK.md` | — |
|
|
50
|
+
|
|
51
|
+
**Heuristic.** If a file is over 25 KB and you're not modifying that exact file,
|
|
52
|
+
don't read it. Use this skill body + the reference page in `columns-showcase.tsx`
|
|
53
|
+
instead.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## §2 — Five-question pre-flight (before you generate code)
|
|
58
|
+
|
|
59
|
+
Answer **yes / no / N/A** to each. A **no** means re-plan; you'll save a regeneration cycle.
|
|
60
|
+
|
|
61
|
+
1. **`HubTable`, not raw `<table>`?** — every hub-style grid in `ListPageTemplate.renderContent` uses `HubTable<TRow>` from `@/components/data-views`. Filters + Properties drawer + view-type tiles + bulk actions come free.
|
|
62
|
+
2. **`view` + `onViewChange` plumbed?** — if the page has view tabs, pass both through `client → table → drawer toolbar → TablePropertiesDrawer`. Otherwise the drawer ships table-only copy on a Board tab.
|
|
63
|
+
3. **Color + icon on every status chip?** — `ListHubStatusBadge` + a tint from `lib/list-status-badges.ts` + an FA icon. Color alone fails WCAG 1.4.1.
|
|
64
|
+
4. **≤ 4 KPIs on the primary strip?** — `KEY_METRICS_KPI_COUNT_MAX = 4`. A fifth becomes a `MetricInsight` or a chart.
|
|
65
|
+
5. **No toasts for product feedback?** — use `LocalBanner` / `SystemBanner` / inline status. Toasts are reserved for build-tool messages.
|
|
66
|
+
|
|
67
|
+
If all five are **yes**, generate. If any is **no**, either narrow the requirements
|
|
68
|
+
with **one** clarifying question or fix the gap silently and note it in your response.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## §3 — Canonical primitive aliases (no grep needed)
|
|
73
|
+
|
|
74
|
+
When the user says "X", reach for "Y". Save the search.
|
|
75
|
+
|
|
76
|
+
### Page chrome & overlays
|
|
77
|
+
|
|
78
|
+
| User says | Use | Path |
|
|
79
|
+
|----------|-----|------|
|
|
80
|
+
| button, action, CTA | `Button` | `@/components/ui/button` |
|
|
81
|
+
| input, text field, form field | `Input`, `FormField` | `@/components/ui/{input,form}` |
|
|
82
|
+
| avatar | `AvatarInitials` | `@/components/ui/avatar` |
|
|
83
|
+
| chip, badge, status, tag | `Badge`, `ListHubStatusBadge`, `StatusBadge` | `@/components/{ui/badge,list-hub-status-badge}` |
|
|
84
|
+
| dropdown, menu, ⋯ | `DropdownMenu` family | `@/components/ui/dropdown-menu` |
|
|
85
|
+
| tooltip, hint | `Tip` (or `Tooltip`) | `@/components/ui/tip` |
|
|
86
|
+
| sheet, drawer | `Sheet` family, `ExportDrawer`, `TablePropertiesDrawer` | `@/components/ui/sheet` |
|
|
87
|
+
| dialog, modal, confirm | `Dialog` family | `@/components/ui/dialog` |
|
|
88
|
+
| table, list, grid (product data) | `HubTable` inside `ListPageTemplate` | `@/components/data-views` |
|
|
89
|
+
| KPI, metric, stat | `KeyMetrics` + `MetricItem` | `@/components/key-metrics` |
|
|
90
|
+
| board, kanban | `ListPageBoardCard`, `ListPageBoardTemplate` | `@/components/data-views/list-page-board-card` |
|
|
91
|
+
| icon | FA `<i class="fa-light fa-{name}" aria-hidden />` | (Kit script in `app/layout.tsx`) |
|
|
92
|
+
| keyboard shortcut hint | `Kbd variant="bare"` inside buttons; `tile` in tooltips | `@/components/ui/kbd` |
|
|
93
|
+
| toggle, switch | `ToggleSwitch` | `@/components/ui/toggle-switch` |
|
|
94
|
+
|
|
95
|
+
### Table cell renderers (ALL importable — do NOT re-implement)
|
|
96
|
+
|
|
97
|
+
Every cell renderer below is exported from **`@/components/data-views`** (re-exported from `apps/web/components/data-views/table-cells.tsx`). Live catalog: `apps/web/components/columns-showcase.tsx` at route `/columns`.
|
|
98
|
+
|
|
99
|
+
| User says | Use | Notes |
|
|
100
|
+
|----------|-----|-------|
|
|
101
|
+
| progress bar, % complete, weeks done | `ProgressCell` | `value`, `max?`, `tone?: "auto" \| "success" \| "warning" \| "danger" \| "info"`, `label?` |
|
|
102
|
+
| currency, money, price, cost | `CurrencyCell` | `value`, `currency?` (default USD), `locale?`, right-aligned `tabular-nums` |
|
|
103
|
+
| numeric count, attempts, downloads | `NumericCell` | `value`, `fractionDigits?`, right-aligned |
|
|
104
|
+
| rating, stars, score | `RatingCell` | `value`, `max?` (5), `showValue?` — color **and** glyph (WCAG 1.4.1) |
|
|
105
|
+
| signal bars, low/med/high, difficulty | `SignalBarsCell` | `level`, `max?` (3), `tone?`, `label` (required, accessible) |
|
|
106
|
+
| toggle, published/draft, enabled/disabled | `BooleanToggleCell` | `checked`, `onChange(next)`, `labelOn?`, `labelOff?`; stops row click propagation |
|
|
107
|
+
| attachments, files count, paperclip | `AttachmentCountCell` | `count` — muted dash on `0` |
|
|
108
|
+
| external link, source, view in new tab | `ExternalLinkCell` | `url`, `label?` — host extracted; `Tip` shows full URL |
|
|
109
|
+
| relative time, "3 hours ago", recency | `RelativeTimeCell` | `iso`, `now?` (override for deterministic snapshots) |
|
|
110
|
+
| face rail, reviewers, assignees +N | `PeopleAvatarRailCell` | `people: PersonStub[]`, `visibleMax?` (3), `size?: "sm" \| "md"`; **non-overlapping** avatars |
|
|
111
|
+
| type pill, category, kind with icon | `PillCell` | `label`, `icon?: "fa-..."` — outlined, single-line |
|
|
112
|
+
| tag list +N, keywords, labels | `TagListCell` | `tags: string[]`, `visibleMax?` (2), `formatLabel?` (default `#${tag}`) |
|
|
113
|
+
| row actions, ⋯, overflow menu | `RowActionsCell<TRow>` | `row`, `actions: RowActionDef<TRow>[]` (`label`, `icon`, `onSelect`, `variant?`, `shortcut?`, `disabled?`) |
|
|
114
|
+
|
|
115
|
+
### Out of band — only when none of the above fits
|
|
116
|
+
|
|
117
|
+
If the user asks for **anything outside this list**: first scan `columns-showcase.tsx`
|
|
118
|
+
+ the `reference-implementations.md` index — both name every existing primitive.
|
|
119
|
+
Only propose a new shared component if neither covers it AND the user has approved
|
|
120
|
+
bespoke work (`exxat-reuse-before-custom.mdc`).
|
|
121
|
+
|
|
122
|
+
**Hard rule.** Do NOT inline-implement a progress bar, currency formatter, rating,
|
|
123
|
+
relative-time, attachment chip, external link, face rail, type pill, tag list, or
|
|
124
|
+
row-actions menu inside a `ColumnDef['cell']`. Import the named cell. If you find
|
|
125
|
+
yourself writing `Intl.NumberFormat`, `<a target="_blank">`, or a `[1,2,3,4,5].map(…)` star
|
|
126
|
+
loop inside a `cell:`, stop — you're re-deriving a shipped primitive.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## §4 — Tiny scaffolds (copy verbatim, don't re-derive)
|
|
131
|
+
|
|
132
|
+
The boilerplate shapes you'd otherwise regenerate.
|
|
133
|
+
|
|
134
|
+
### Hub client (replace `Entity`, KPIs, columns)
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
"use client"
|
|
138
|
+
import * as React from "react"
|
|
139
|
+
import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
|
|
140
|
+
import { PageHeader } from "@/components/page-header"
|
|
141
|
+
import { KeyMetrics, type MetricItem } from "@/components/key-metrics"
|
|
142
|
+
import { ListPageTemplate, type ViewTab, HubTable } from "@/components/data-views"
|
|
143
|
+
|
|
144
|
+
const ENTITY_TABS: ViewTab[] = [{ id: "all", label: "All", viewType: "table", icon: "fa-table", filterId: "all" }]
|
|
145
|
+
const ENTITY_SUPPORTED_VIEWS = ["table", "list", "board"] as const
|
|
146
|
+
|
|
147
|
+
export function EntityClient({ rows }) {
|
|
148
|
+
const [tabs, setTabs] = React.useState<ViewTab[]>(ENTITY_TABS)
|
|
149
|
+
const [activeTabId, setActiveTabId] = React.useState(ENTITY_TABS[0].id)
|
|
150
|
+
return (
|
|
151
|
+
<PrimaryPageTemplate siteHeader={{ title: "Entity" }}>
|
|
152
|
+
<ListPageTemplate
|
|
153
|
+
defaultTabs={ENTITY_TABS} tabs={tabs} onTabsChange={setTabs}
|
|
154
|
+
activeTabId={activeTabId} onActiveTabChange={setActiveTabId}
|
|
155
|
+
supportedViewTypes={ENTITY_SUPPORTED_VIEWS}
|
|
156
|
+
getTabCount={() => rows.length}
|
|
157
|
+
header={<PageHeader title="Entity" subtitle="…" />}
|
|
158
|
+
metrics={<KeyMetrics variant="flat" metrics={ENTITY_KPIS} showHeader={false} metricsSingleRow />}
|
|
159
|
+
renderContent={(tab, updateTab) => (
|
|
160
|
+
<HubTable rows={rows} columns={useColumns()}
|
|
161
|
+
view={tab.viewType} onViewChange={v => updateTab({ viewType: v })}
|
|
162
|
+
supportedViewTypes={ENTITY_SUPPORTED_VIEWS}
|
|
163
|
+
hubLabel="Entity" lifecycleTabLabel="Entity"
|
|
164
|
+
getRowId={r => r.id} getRowSelectionLabel={r => r.name}
|
|
165
|
+
defaultSort={{ key: "name", dir: "asc" }}
|
|
166
|
+
renderers={{}}
|
|
167
|
+
/>
|
|
168
|
+
)}
|
|
169
|
+
/>
|
|
170
|
+
</PrimaryPageTemplate>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `MetricItem` shape (KPI strip cell)
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
{ id: "active", label: "Active", value: 142, delta: "+8 vs last week",
|
|
179
|
+
trend: "up", trendPolarity: "higher_is_better",
|
|
180
|
+
metricVariant: "hero", description: "currently in placement" }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- `trend: "neutral"` + empty `delta` → no trend chip renders. Don't fake a `+0`.
|
|
184
|
+
- `trendPolarity: "lower_is_better"` for error counts, defects, overdue.
|
|
185
|
+
- `metricVariant: "hero"` for **one** cell per strip — the headline number.
|
|
186
|
+
- Max **4 cells**.
|
|
187
|
+
|
|
188
|
+
### `ColumnDef<TRow>` shape
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
{ key: "name", label: "Name", width: 240, minWidth: 180,
|
|
192
|
+
defaultPin: "left", sortable: true, sortKey: "name",
|
|
193
|
+
filter: { type: "text", icon: "fa-user", operators: ["contains"] },
|
|
194
|
+
cell: row => <span className="truncate">{row.name}</span> }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `key: "select"` + `defaultPin: "left"` + `lockPin: true` → checkbox column.
|
|
198
|
+
- `key: "actions"` + `defaultPin: "right"` + `lockPin: true` → ⋯ overflow column.
|
|
199
|
+
- `filter: { type: "select", options }` for categorical columns.
|
|
200
|
+
|
|
201
|
+
### Default `cell:` should call a named primitive
|
|
202
|
+
|
|
203
|
+
The right `cell:` body for the common patterns is **one import + one component**, not an inline JSX block:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { ProgressCell, CurrencyCell, RatingCell, BooleanToggleCell,
|
|
207
|
+
RelativeTimeCell, AttachmentCountCell, ExternalLinkCell,
|
|
208
|
+
PeopleAvatarRailCell, PillCell, TagListCell, NumericCell,
|
|
209
|
+
RowActionsCell, type RowActionDef } from "@/components/data-views"
|
|
210
|
+
|
|
211
|
+
// Examples — cell:'s body is one call.
|
|
212
|
+
cell: row => <ProgressCell value={row.completion} />
|
|
213
|
+
cell: row => <CurrencyCell value={row.cost} />
|
|
214
|
+
cell: row => <RatingCell value={row.rating} />
|
|
215
|
+
cell: row => <BooleanToggleCell checked={row.published} onChange={next => onTogglePublished(row, next)} />
|
|
216
|
+
cell: row => <RelativeTimeCell iso={row.lastActivityAt} />
|
|
217
|
+
cell: row => <AttachmentCountCell count={row.attachmentCount} />
|
|
218
|
+
cell: row => <ExternalLinkCell url={row.sourceUrl} />
|
|
219
|
+
cell: row => <PeopleAvatarRailCell people={row.reviewers} />
|
|
220
|
+
cell: row => <PillCell label={TYPE_LABEL[row.type]} icon={TYPE_ICON[row.type]} />
|
|
221
|
+
cell: row => <TagListCell tags={row.tags} />
|
|
222
|
+
cell: row => <NumericCell value={row.attempts} />
|
|
223
|
+
cell: row => <RowActionsCell row={row} actions={ROW_ACTIONS} />
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
If `cell:` exceeds **one line** for any pattern above, you're re-deriving — go back and import.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## §5 — Deny list (do NOT open unless asked)
|
|
231
|
+
|
|
232
|
+
Treat these as expensive — skip unless the user explicitly names them or this skill points to them:
|
|
233
|
+
|
|
234
|
+
- `AGENTS.md` — only when the task is "change the architecture or the rules themselves".
|
|
235
|
+
- `packages/ui/src/components/data-views/hub-table.tsx` — the API is documented; don't read the implementation.
|
|
236
|
+
- `apps/web/.next/`, `.turbo/`, `node_modules/` — never.
|
|
237
|
+
- All 29 `.cursor/rules/exxat-*.mdc` at once — §1 already points to the 1-2 that apply.
|
|
238
|
+
- Test files (`*.test.*`, `__tests__/`) — irrelevant for design.
|
|
239
|
+
- Build configs (`tsup.config.ts`, `next.config.ts`, `turbo.json`) — irrelevant for design.
|
|
240
|
+
- `packages/ui/tokens/hooks-index.json` (~163 KB) — only when the task is a token rename / audit.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## §6 — When to ask vs. when to assume
|
|
245
|
+
|
|
246
|
+
One clarifying question costs ~50 tokens. A wrong implementation costs hundreds (read, generate, user feedback, regenerate). Ask when:
|
|
247
|
+
|
|
248
|
+
1. The task name is ambiguous between two patterns (e.g. "add a panel" → drawer or split panel?).
|
|
249
|
+
2. Two existing reference hubs handle the same concern differently (use the user's existing precedent if they have one).
|
|
250
|
+
3. The user mentions a screen that doesn't have a clear primitive in §3.
|
|
251
|
+
|
|
252
|
+
Don't ask when:
|
|
253
|
+
|
|
254
|
+
- The user has cited a specific file or component — go.
|
|
255
|
+
- The pre-flight (§2) all answers yes — go.
|
|
256
|
+
- The task fits exactly one row in §1's task map — go.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## §7 — Output discipline (your turn budget)
|
|
261
|
+
|
|
262
|
+
Your **response** also costs tokens. Keep it lean:
|
|
263
|
+
|
|
264
|
+
- **No "let me explain the design system" preambles.** The user has the package installed; they know.
|
|
265
|
+
- **No reading-aloud of file paths.** Cite with `code` ticks; don't re-narrate.
|
|
266
|
+
- **No restating the prompt.** Jump straight to the action.
|
|
267
|
+
- **Group tool calls in parallel** when independent (reading 3 files = 1 message, not 3).
|
|
268
|
+
- **Stop generating** as soon as the pre-flight (§2) says you have what you need. Don't gold-plate.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## See also
|
|
273
|
+
|
|
274
|
+
- **Handbook** — `docs/exxat-ds/handbook/HANDBOOK.md` (5 principles + how-to-build-a-hub)
|
|
275
|
+
- **Reference implementations** — `docs/exxat-ds/handbook/reference-implementations.md` (find the file to copy)
|
|
276
|
+
- **Voice & tone** — `docs/exxat-ds/handbook/voice-and-tone.md` (user-facing copy)
|
|
277
|
+
- **Cell patterns catalog** — `apps/web/components/columns-showcase.tsx` (live at `/columns`)
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
> **Start here.** One page. Read in 10 minutes. Links out to everything else.
|
|
4
4
|
>
|
|
5
5
|
> **Audience:** designers, engineers, contributors, AI agents — anyone shipping UI in the Exxat product.
|
|
6
|
+
>
|
|
7
|
+
> **Working with an AI assistant?** Read [`.cursor/skills/exxat-token-economy/SKILL.md`](../../../.cursor/skills/exxat-token-economy/SKILL.md) **first** (or `.claude/skills/exxat-token-economy/SKILL.md` for Claude Code). It's a one-page pre-flight that cuts token usage by ~50%: a task → minimum-file-set table, the five-question rule check, and tiny scaffolds that mean the assistant never has to re-read this handbook for the common case.
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
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
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
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` |
|
|
14
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` |
|
|
15
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` |
|
|
16
17
|
| **Collaboration variant** | The `PageHeader` flavor that exposes the face rail + Invite-people overflow item for shared hubs. | `exxat-collaboration-access.mdc` |
|
|
@@ -42,7 +43,7 @@
|
|
|
42
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` |
|
|
43
44
|
| **Reference page** | A canonical full implementation of a hub or pattern in `apps/web/components/<entity>-*.tsx`. Listed in `docs/reference-implementations.md`. Copy from these before inventing. | `docs/reference-implementations.md` |
|
|
44
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 |
|
|
45
|
-
| **Secondary panel** | A scoped navigation rail (e.g. "
|
|
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` |
|
|
46
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` |
|
|
47
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` |
|
|
48
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` |
|
|
@@ -25,20 +25,47 @@ If you find yourself diverging from the reference page, ask **why** before shipp
|
|
|
25
25
|
| Full hub: table + board + dashboard + list + paginated + conditional rules + dashboard customize | `apps/web/components/placements-table.tsx` + `placements-client.tsx` | [`list-page-template`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/list-page-template.md), [`data-table`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/data-table.md), [`board-card`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/board-card.md), [`key-metrics`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/key-metrics.md) | [`exxat-data-tables`](../../../.cursor/rules/exxat-data-tables.mdc), [`exxat-list-page-connected-views`](../../../.cursor/rules/exxat-list-page-connected-views.mdc), [`exxat-centralized-list-dataset`](../../../.cursor/rules/exxat-centralized-list-dataset.mdc) | [`data-views-pattern`](../data-views-pattern.md) |
|
|
26
26
|
| Hub with dashboard customize (canvas layout edit) | `apps/web/components/team-table.tsx` + `team-client.tsx` | [`list-page-template`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/list-page-template.md), [`key-metrics`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/key-metrics.md) | [`exxat-list-page-connected-views`](../../../.cursor/rules/exxat-list-page-connected-views.mdc) | [`data-views-pattern`](../data-views-pattern.md) |
|
|
27
27
|
| Hub with finder / split-panel view | `apps/web/components/sites-table.tsx` + `sites-client.tsx` | [`list-page-template`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/list-page-template.md) | [`exxat-list-page-view-shells`](../../../.cursor/rules/exxat-list-page-view-shells.mdc) | [`data-views-pattern`](../data-views-pattern.md) |
|
|
28
|
-
| Hub with secondary panel scope (folder rail) | `apps/web/components/
|
|
28
|
+
| Hub with secondary panel scope (folder rail) | `apps/web/components/library-table.tsx` + `library-client.tsx` | [`list-page-template`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/list-page-template.md) | [`exxat-primary-nav-secondary-panel`](../../../.cursor/rules/exxat-primary-nav-secondary-panel.mdc), [`exxat-library-hub-header`](../../../.cursor/rules/exxat-library-hub-header.mdc) | [`library-hub-header-pattern`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/library-hub-header-pattern.md) |
|
|
29
29
|
| Hub with secondary panel scope (token categories) — **smallest** secondary-panel reference | `apps/web/components/tokens-themes-client.tsx` + `tokens-secondary-nav.tsx` | [`list-page-template`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/list-page-template.md) | [`exxat-primary-nav-secondary-panel`](../../../.cursor/rules/exxat-primary-nav-secondary-panel.mdc) | [`shell-surface-elevation-pattern`](../shell-surface-elevation-pattern.md) |
|
|
30
|
-
| Single-view showcase hub (table only) — **18 SaaS cell patterns
|
|
30
|
+
| Single-view showcase hub (table only) — **18 SaaS cell patterns**, each rendered by an **importable named cell** from `@/components/data-views` (see "Cell primitives" table below). Categories: select, primary identity + ID + favorite, avatar + name + email, avatar group +N, type pill, difficulty signal, status chip, inline toggle, tag list, rating stars, progress bar, currency, numeric, attachment count, external link, relative time, absolute date, row actions overflow. | `apps/web/components/columns-showcase.tsx` + `columns-client.tsx` | [`list-page-template`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/list-page-template.md), [`data-table`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/data-table.md) | [`exxat-data-tables`](../../../.cursor/rules/exxat-data-tables.mdc) | — |
|
|
31
31
|
|
|
32
32
|
> **First-time hub builder:** start with `sites-table.tsx` (smallest complete hub) or `columns-showcase.tsx` (single-view, easiest).
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
+
## Cell primitives (importable)
|
|
37
|
+
|
|
38
|
+
Every cell renderer below is exported from `@/components/data-views` (re-exported from `apps/web/components/data-views/table-cells.tsx`). The live catalog page is `/columns`. **Do not re-implement these inside a `ColumnDef['cell']`** — import the name.
|
|
39
|
+
|
|
40
|
+
| Cell | Renders | Import |
|
|
41
|
+
|------|---------|--------|
|
|
42
|
+
| `ProgressCell` | Track + filled bar with auto-tone in thirds; `value`, `max`, `tone`, `label` | `@/components/data-views` |
|
|
43
|
+
| `CurrencyCell` | Right-aligned `tabular-nums`; `value`, `currency` (USD), `locale`, `maximumFractionDigits` | same |
|
|
44
|
+
| `NumericCell` | Right-aligned plain count; `value`, `fractionDigits` | same |
|
|
45
|
+
| `RatingCell` | N of `max` FA stars + value; color + glyph paired (WCAG 1.4.1) | same |
|
|
46
|
+
| `SignalBarsCell` | Wi-Fi-style ordinal bars; `level`, `max`, `tone`, `label` (required) | same |
|
|
47
|
+
| `BooleanToggleCell` | Inline `ToggleSwitch` with `checked` + `onChange(next)`; stops row click propagation | same |
|
|
48
|
+
| `AttachmentCountCell` | Paperclip + count chip; muted dash on `0` | same |
|
|
49
|
+
| `ExternalLinkCell` | Truncated host + new-tab icon; `url`, `label?`; `Tip` shows full URL | same |
|
|
50
|
+
| `RelativeTimeCell` | "3 hours ago" with `Tip(absolute)`; `iso`, `now?` (deterministic snapshots) | same |
|
|
51
|
+
| `PeopleAvatarRailCell` | Face rail with `+N` overflow; `people: PersonStub[]`, **non-overlapping** | same |
|
|
52
|
+
| `PillCell` | Outlined badge + leading FA icon; `label`, `icon?` | same |
|
|
53
|
+
| `TagListCell` | Soft badges with `+N` overflow; `tags`, `visibleMax?`, `formatLabel?` | same |
|
|
54
|
+
| `RowActionsCell<TRow>` | `⋯` overflow dropdown; `row`, `actions: RowActionDef<TRow>[]` (label, icon, onSelect, variant, shortcut, disabled) | same |
|
|
55
|
+
| `EMPTY_DASH` | Aria-hidden `—` placeholder for null/undefined cells | same |
|
|
56
|
+
|
|
57
|
+
**Anti-references** (do NOT copy):
|
|
58
|
+
- ❌ Inlining `Intl.NumberFormat`, `[1,2,3,4,5].map(s => …)` star loops, raw `<a target="_blank">`, `new URL(…).hostname`, or `Intl.RelativeTimeFormat` inside a `cell:`. Import the named cell.
|
|
59
|
+
- ❌ Re-implementing `RowActionsCell` per hub with a custom `DropdownMenu`. The generic `RowActionsCell<TRow>` covers `Open / Edit / Duplicate / Archive`-style menus.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
36
63
|
## Hub chrome
|
|
37
64
|
|
|
38
65
|
| Pattern | Reference page | Blueprint | Rule(s) |
|
|
39
66
|
|---|---|---|---|
|
|
40
67
|
| Page header — primary hub | `apps/web/components/placements-page-header.tsx` | [`page-header`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/page-header.md) | [`exxat-collaboration-access`](../../../.cursor/rules/exxat-collaboration-access.mdc), [`exxat-mono-ids`](../../../.cursor/rules/exxat-mono-ids.mdc) |
|
|
41
|
-
| Page header — collaboration variant (face rail + invite) | `apps/web/components/
|
|
68
|
+
| Page header — collaboration variant (face rail + invite) | `apps/web/components/library-page-header.tsx` | [`page-header`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/page-header.md) | [`exxat-collaboration-access`](../../../.cursor/rules/exxat-collaboration-access.mdc) |
|
|
42
69
|
| Page header — entity / record (no view tabs) | `apps/web/components/page-header.tsx` (variants) | [`page-header`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/page-header.md) | — |
|
|
43
70
|
| KPI flat band on a hub | `apps/web/components/placements-client.tsx` | [`key-metrics`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/key-metrics.md) | [`exxat-kpi-flat-band`](../../../.cursor/rules/exxat-kpi-flat-band.mdc), [`exxat-kpi-max-four`](../../../.cursor/rules/exxat-kpi-max-four.mdc), [`exxat-kpi-trends`](../../../.cursor/rules/exxat-kpi-trends.mdc) |
|
|
44
71
|
| Properties drawer wiring | `apps/web/components/placements-table.tsx` | — | [`exxat-table-properties-drawer`](../../../.cursor/rules/exxat-table-properties-drawer.mdc) |
|
|
@@ -52,7 +79,7 @@ If you find yourself diverging from the reference page, ask **why** before shipp
|
|
|
52
79
|
|---|---|---|---|
|
|
53
80
|
| Board card (kanban) | `apps/web/components/placements-board-view.tsx`, `team-table.tsx` (`renderListRow`) | [`board-card`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/board-card.md) | [`exxat-board-cards`](../../../.cursor/rules/exxat-board-cards.mdc), [`exxat-card-vs-list-rows`](../../../.cursor/rules/exxat-card-vs-list-rows.mdc) |
|
|
54
81
|
| List row (single-column) | `apps/web/components/team-table.tsx` `renderListRow` | [`board-card`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/board-card.md) (row layout) | [`exxat-card-vs-list-rows`](../../../.cursor/rules/exxat-card-vs-list-rows.mdc) |
|
|
55
|
-
| Dashboard view with charts + KPI band | `apps/web/components/placements-dashboard-charts-section.tsx` | [`key-metrics`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/key-metrics.md) | [`exxat-dashboard-view-charts`](
|
|
82
|
+
| Dashboard view with charts + KPI band | `apps/web/components/placements-dashboard-charts-section.tsx` | [`key-metrics`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/blueprints/key-metrics.md) | [`exxat-dashboard-view-charts`](../../../.cursor/rules/exxat-dashboard-view-charts.mdc) |
|
|
56
83
|
| Folder / finder split view | `apps/web/components/data-views/finder-panel-view.tsx` (used in `sites-table.tsx`) | — | [`exxat-list-page-view-shells`](../../../.cursor/rules/exxat-list-page-view-shells.mdc) |
|
|
57
84
|
|
|
58
85
|
---
|
|
@@ -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
|
|