@exxatdesignux/ui 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/consumer-extras/cursor-rules/exxat-data-tables.mdc +8 -6
  3. package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +2 -1
  4. package/consumer-extras/cursor-rules/exxat-hub-supported-views.mdc +54 -0
  5. package/consumer-extras/cursor-rules/exxat-nav-single-active.mdc +31 -0
  6. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +8 -3
  7. package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +15 -5
  8. package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +11 -4
  9. package/consumer-extras/handbook/HANDBOOK.md +1 -1
  10. package/consumer-extras/handbook/reference-implementations.md +2 -2
  11. package/consumer-extras/patterns/data-views-pattern.md +6 -0
  12. package/consumer-extras/patterns/hub-supported-views-pattern.md +53 -0
  13. package/dist/components/data-table/index.js +13 -9
  14. package/dist/components/data-table/index.js.map +1 -1
  15. package/dist/components/data-table/pagination.js +13 -9
  16. package/dist/components/data-table/pagination.js.map +1 -1
  17. package/dist/components/data-views/hub-table.d.ts +8 -4
  18. package/dist/components/data-views/hub-table.js +25 -10
  19. package/dist/components/data-views/hub-table.js.map +1 -1
  20. package/dist/components/data-views/index.d.ts +1 -1
  21. package/dist/components/data-views/index.js +25 -10
  22. package/dist/components/data-views/index.js.map +1 -1
  23. package/dist/components/data-views/list-page-connected-view-body.d.ts +1 -1
  24. package/dist/components/data-views/list-page-connected-view-body.js +1 -0
  25. package/dist/components/data-views/list-page-connected-view-body.js.map +1 -1
  26. package/dist/components/table-properties/drawer-button.js +1 -0
  27. package/dist/components/table-properties/drawer-button.js.map +1 -1
  28. package/dist/components/table-properties/drawer.js +1 -0
  29. package/dist/components/table-properties/drawer.js.map +1 -1
  30. package/dist/components/table-properties/index.d.ts +1 -1
  31. package/dist/components/table-properties/index.js +1 -0
  32. package/dist/components/table-properties/index.js.map +1 -1
  33. package/dist/components/templates/index.d.ts +1 -1
  34. package/dist/components/templates/index.js +12 -2
  35. package/dist/components/templates/index.js.map +1 -1
  36. package/dist/components/templates/list-page.d.ts +4 -2
  37. package/dist/components/templates/list-page.js +12 -2
  38. package/dist/components/templates/list-page.js.map +1 -1
  39. package/dist/{data-list-view-registry-CyBoBML4.d.ts → data-list-view-registry-BstmlfQ3.d.ts} +16 -1
  40. package/dist/index.d.ts +2 -1
  41. package/dist/index.js +135 -13
  42. package/dist/index.js.map +1 -1
  43. package/dist/lib/data-list-view-registry.d.ts +1 -1
  44. package/dist/lib/data-list-view-registry.js +17 -1
  45. package/dist/lib/data-list-view-registry.js.map +1 -1
  46. package/dist/lib/data-list-view-surface.d.ts +1 -1
  47. package/dist/lib/data-list-view-surface.js +1 -0
  48. package/dist/lib/data-list-view-surface.js.map +1 -1
  49. package/dist/lib/list-page-table-properties.d.ts +1 -1
  50. package/dist/lib/list-page-table-properties.js +1 -0
  51. package/dist/lib/list-page-table-properties.js.map +1 -1
  52. package/dist/lib/nav-active.d.ts +38 -0
  53. package/dist/lib/nav-active.js +104 -0
  54. package/dist/lib/nav-active.js.map +1 -0
  55. package/package.json +1 -1
  56. package/src/components/data-table/index.tsx +25 -17
  57. package/src/components/data-views/hub-table.tsx +9 -3
  58. package/src/components/templates/list-page.tsx +9 -3
  59. package/src/index.ts +1 -0
  60. package/src/lib/data-list-view-registry.ts +31 -0
  61. package/src/lib/nav-active.ts +162 -0
  62. package/template/.claude/skills/exxat-ds-skill/SKILL.md +2 -1
  63. package/template/AGENTS.md +16 -1
  64. package/template/components/columns-client.tsx +3 -2
  65. package/template/components/columns-showcase.tsx +22 -18
  66. package/template/components/exxat-product-logo.tsx +1 -1
  67. package/template/components/library-table.tsx +62 -23
  68. package/template/components/new-library-item-form.tsx +0 -7
  69. package/template/components/product-wordmark.tsx +1 -1
  70. package/template/components/sidebar/app-sidebar.tsx +14 -106
  71. package/template/components/sidebar/secondary-nav.tsx +22 -4
  72. package/template/components/tokens-hub-auxiliary-views.tsx +301 -0
  73. package/template/components/tokens-themes-client.tsx +44 -16
  74. package/template/docs/HANDBOOK.md +1 -1
  75. package/template/docs/data-views-pattern.md +6 -0
  76. package/template/docs/glossary.md +2 -1
  77. package/template/docs/hub-supported-views-pattern.md +53 -0
  78. package/template/docs/reference-implementations.md +2 -2
  79. package/template/lib/full-hub-supported-views.ts +8 -0
  80. package/template/lib/library-supported-views.ts +5 -12
  81. package/template/package.json +1 -0
  82. package/tokens/hooks-index.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - **List hubs default to seven connected views.** `FULL_HUB_SUPPORTED_VIEWS` (table, list, board, dashboard, folder, panel, tree-panel) is exported from `lib/data-list-view-registry` and used as the default `supportedViewTypes` on `HubTable` and `ListPageTemplate`. `PRIMARY_HUB_SUPPORTED_VIEWS` remains for documented four-view exceptions.
8
+ - **`prepare` builds `dist/` when missing** (`scripts/ensure-dist.mjs`) so fresh installs resolve `@exxatdesignux/ui` subpath imports without a manual build step.
9
+ - **`nav-active` helpers** (`lib/nav-active.ts`) for single-active sidebar / secondary nav links.
10
+ - **Vendored agent docs:** `exxat-hub-supported-views.mdc` and `hub-supported-views-pattern.md` in `consumer-extras` so npm consumers get Add view parity guardrails.
11
+
3
12
  ## 0.5.2
4
13
 
5
14
  ### Patch Changes
@@ -7,7 +7,7 @@ alwaysApply: true
7
7
 
8
8
  ## Use one stack for product data lists
9
9
 
10
- For **any app screen that shows a browsable, filterable grid of records** (lists, directories, placements, tokens, columns showcase, etc.):
10
+ For **any app screen that shows a browsable, filterable grid of records** (directories, tokens, columns showcase, question banks, etc.):
11
11
 
12
12
  1. **Inside `ListPageTemplate` (every primary hub + every showcase page that mounts a hub-style grid) MUST use `HubTable`** from `@/components/data-views`. `HubTable` is the **canonical** wrapper that wires `useTableState`, the toolbar (**search + filter chips + filter dropdown + sort**), the **Table properties drawer**, view-type tiles, bulk-actions, and conditional rules in one place. Pages that drop down to raw `<DataTable>` silently lose filters and Properties — **MUST NOT** ship a hub or showcase that way.
13
13
  2. **Outside `ListPageTemplate`** (rare — e.g. embedded mini-grids, modal sub-tables, drawer body lists), use `DataTable` from `@/components/data-table` directly. Even then, prefer composing a small `toolbarSlot` with `TablePropertiesDrawerButton` whenever the grid is more than a handful of rows.
@@ -17,14 +17,13 @@ For **any app screen that shows a browsable, filterable grid of records** (lists
17
17
  6. **Table properties:** Always reachable. `HubTable` mounts `TablePropertiesDrawerButton` in `toolbarSlot`; on **`ListPageTemplate`** pages with **table / list / board / dashboard** tabs it **MUST** also receive **`currentView`** and **`onViewChange`** (see **`apps/web/AGENTS.md` §4.2** and **`.cursor/rules/exxat-table-properties-drawer.mdc`**) so Properties matches the selected view.
18
18
  7. **Dropdown menus:** `DropdownMenuContent` uses the shared **`@exxatdesignux/ui`** default (**intrinsic `w-max`**, **`min-w-52`**, capped **`max-w`**) for view settings, row ⋯, column menus, and filter pickers — **pure CSS**, no **`ResizeObserver`**. Override only for deliberate narrow/wide rails (e.g. pagination **`w-20`**, account trigger-width, school switcher **`!w-max min-w-72 …`**). See **`docs/data-views-pattern.md`** (“Dropdown menus”).
19
19
  8. **Cell renderers MUST come from `@/components/data-views` (`table-cells.tsx`).** The DS ships **`ProgressCell`**, **`CurrencyCell`**, **`NumericCell`**, **`RatingCell`**, **`SignalBarsCell`**, **`BooleanToggleCell`**, **`AttachmentCountCell`**, **`ExternalLinkCell`**, **`RelativeTimeCell`**, **`PeopleAvatarRailCell`**, **`PillCell`**, **`TagListCell`**, and a generic **`RowActionsCell<TRow>`**. A `ColumnDef['cell']` for these patterns is a **one-liner** that calls the named cell. **MUST NOT** inline `Intl.NumberFormat`, raw `<a target="_blank">`, `[1,2,3,4,5].map(s => …)` star loops, paperclip + count chips, custom face-rail `AvatarGroup`s, or per-hub `DropdownMenu` overflow menus inside `cell:` — those are signals you're re-deriving a shipped primitive. Catalog: `apps/web/components/columns-showcase.tsx` (`/columns`). Skill: `.cursor/skills/exxat-token-economy/SKILL.md` §3.
20
+ 9. **Add view parity** — **`FULL_HUB_SUPPORTED_VIEWS`** + a real renderer per view — **`.cursor/rules/exxat-hub-supported-views.mdc`**, **`docs/exxat-ds/patterns/hub-supported-views-pattern.md`**.
20
21
 
21
22
  **Reference implementations:**
22
23
 
23
- - `components/placements-table.tsx` full hub: paged table, board, dashboard, list, conditional rules, dashboard customize, bulk actions.
24
- - `components/sites-table.tsx` — table + finder/board.
25
- - `components/team-table.tsx` — table + dashboard + list.
26
- - `components/columns-showcase.tsx` — minimal hub (single `table` view); good reference for showcase pages.
27
- - `components/tokens-themes-client.tsx` — minimal hub + secondary panel scope.
24
+ - `components/library-table.tsx` + `components/library-client.tsx` **canonical** seven-view hub (All questions).
25
+ - `components/columns-showcase.tsx` — cell-pattern catalog via **`LibraryTable`** (`columnDefs` + folder state); same Add view as Library.
26
+ - `components/tokens-themes-client.tsx` + `components/tokens-hub-auxiliary-views.tsx` tokens hub with **`FULL_HUB_SUPPORTED_VIEWS`**.
28
27
 
29
28
  ## Do not
30
29
 
@@ -32,12 +31,15 @@ For **any app screen that shows a browsable, filterable grid of records** (lists
32
31
  - Do **not** mount raw `<DataTable>` inside `ListPageTemplate.renderContent` — use `HubTable`. Raw `<DataTable>` does not ship the Properties drawer or filter chips; users lose discoverability.
33
32
  - Do **not** introduce a second “table component” pattern for the same product surfaces (splitting search/filters/properties across incompatible implementations).
34
33
  - Do **not** inline-implement progress bars, currency formatters, rating stars, relative-time helpers, attachment chips, external-link wrappers, face rails, type pills, tag lists, or row-action dropdowns inside a `ColumnDef['cell']`. Import the named cell from `@/components/data-views` instead. New hub with novel cell needs MUST extend `table-cells.tsx` (and ship the catalog entry in `columns-showcase.tsx`), not fork inline JSX.
34
+ - Do **not** trim **`supportedViewTypes`** to table-only or **`PRIMARY_HUB_SUPPORTED_VIEWS`** on hubs that should match Library — see **`exxat-hub-supported-views.mdc`**.
35
35
 
36
36
  ## Exceptions
37
37
 
38
38
  - **Tiny, read-only tables inside charts or analytics cards** (e.g. chart figure captions / summary matrices) may use minimal markup when they are not primary data-list experiences — still prefer tokens and accessibility, but the full hub stack is not required there.
39
39
  - **Drawer body and dialog sub-grids** (small, secondary, scoped to a transient flow) — raw `<DataTable>` is acceptable; still expose search if rows > ~10.
40
+ - **Documented narrow allowlists** in `lib/*-supported-views.ts` when product truly omits folder/panel/tree (comment required).
40
41
 
41
42
  ## See also
42
43
 
43
44
  - **`apps/web/AGENTS.md`** — full MUST/MUST NOT, list-page template, primary hubs, checklist.
45
+ - **`.cursor/rules/exxat-hub-supported-views.mdc`** — Add view menu + renderer matrix.
@@ -40,6 +40,7 @@ Before implementing or reviewing **list / table / board / dashboard / data-heavy
40
40
  25. **No SLDS leakage** — **`.cursor/rules/exxat-no-slds-leakage.mdc`** — no `slds-*` classes, no `<lightning-*>` markup, no SLDS token names, no synthetic-shadow assumptions. ESLint catches via `exxat-ds/no-slds-classes` + `exxat-ds/no-lightning-elements` (from **`@exxatdesignux/eslint-plugin`**).
41
41
  26. **Blueprints + selection guide** — **`apps/web/docs/blueprints/`** + **`apps/web/docs/component-selection-guide.md`** — framework-agnostic specs + decision tree across the whole DS. Start there before composing a new hub.
42
42
  27. **Migrations** — **`apps/web/docs/migrations/`** — every deprecation gets a numbered entry (`NNNN-<slug>.md`). Token deprecations surface in `tokens/hooks-index.json` as `deprecated: true` and are lint-flagged.
43
+ 28. **Hub supported views (Add view parity)** — **`exxat-hub-supported-views.mdc`** + **`docs/exxat-ds/patterns/hub-supported-views-pattern.md`** — **`FULL_HUB_SUPPORTED_VIEWS`** (seven views, same as Library); sync **`ListPageTemplate`** + **`HubTable`**; real renderers per view; **`LibraryTable`** for **`LibraryItem`** catalogs; **`tokens-hub-auxiliary-views.tsx`** for tokens.
43
44
 
44
45
  ## Also read
45
46
 
@@ -50,7 +51,7 @@ Before implementing or reviewing **list / table / board / dashboard / data-heavy
50
51
  - **`apps/web/docs/blueprints/`** — framework-agnostic specs (start: `page-header.md`, `data-table.md`).
51
52
  - **`apps/web/docs/migrations/`** — token rename + removal history.
52
53
  - **`packages/ui/tokens/hooks-index.json`** — machine-readable token index (regenerate via `pnpm --filter @exxatdesignux/ui tokens:index`).
53
- - `.cursor/rules/exxat-data-tables.mdc`, `exxat-list-page-connected-views.mdc`, **`exxat-centralized-list-dataset.mdc`**, **`exxat-list-page-view-shells.mdc`**, **`exxat-fontawesome-icons.mdc`**, **`exxat-mono-ids.mdc`**, **`exxat-primary-nav-secondary-panel.mdc`**, **`exxat-library-hub-header.mdc`**, **`exxat-collaboration-access.mdc`**, **`exxat-dedicated-search-surfaces.mdc`**, **`exxat-kpi-trends.mdc`**, **`exxat-kpi-flat-band.mdc`**, **`exxat-drawer-vs-dialog.mdc`**, **`exxat-card-vs-list-rows.mdc`**, **`exxat-kpi-max-four.mdc`**, **`exxat-reuse-before-custom.mdc`**, `exxat-table-properties-drawer.mdc`, **`exxat-board-cards.mdc`**, **`exxat-page-vs-drawer.mdc`**, **`exxat-no-toast.mdc`**, **`exxat-command-menu.mdc`**, `exxat-kbd-shortcuts.mdc`, `exxat-accessibility.mdc` at repo root; **`apps/web/.cursor/rules/exxat-dashboard-view-charts.mdc`** for Data tab charts.
54
+ - `.cursor/rules/exxat-data-tables.mdc`, **`exxat-hub-supported-views.mdc`**, `exxat-list-page-connected-views.mdc`, **`exxat-centralized-list-dataset.mdc`**, **`exxat-list-page-view-shells.mdc`**, **`exxat-fontawesome-icons.mdc`**, **`exxat-mono-ids.mdc`**, **`exxat-primary-nav-secondary-panel.mdc`**, **`exxat-library-hub-header.mdc`**, **`exxat-collaboration-access.mdc`**, **`exxat-dedicated-search-surfaces.mdc`**, **`exxat-kpi-trends.mdc`**, **`exxat-kpi-flat-band.mdc`**, **`exxat-drawer-vs-dialog.mdc`**, **`exxat-card-vs-list-rows.mdc`**, **`exxat-kpi-max-four.mdc`**, **`exxat-reuse-before-custom.mdc`**, `exxat-table-properties-drawer.mdc`, **`exxat-board-cards.mdc`**, **`exxat-page-vs-drawer.mdc`**, **`exxat-no-toast.mdc`**, **`exxat-command-menu.mdc`**, `exxat-kbd-shortcuts.mdc`, `exxat-accessibility.mdc` at repo root; **`apps/web/.cursor/rules/exxat-dashboard-view-charts.mdc`** for Data tab charts.
54
55
  - **`apps/web/docs/kpi-flat-band-pattern.md`**, **`apps/web/docs/shell-surface-elevation-pattern.md`** — flat KPI strip + sidebar/secondary/page OKLCH stack.
55
56
  - **`apps/web/docs/library-hub-header-pattern.md`** — folder-scoped library header **Customize folder**.
56
57
  - **`apps/web/docs/collaboration-access-pattern.md`** — shared hub face rail + invite sheet.
@@ -0,0 +1,54 @@
1
+ ---
2
+ description: List-page hubs — FULL_HUB_SUPPORTED_VIEWS, Add view parity, and real renderers (never trimmed allowlists or placeholder list rows)
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Exxat DS — hub supported views (Add view parity)
7
+
8
+ **Canonical reference:** `components/library-table.tsx` + `components/library-client.tsx` (All questions / Library).
9
+
10
+ **Registry:** `FULL_HUB_SUPPORTED_VIEWS` in `@exxatdesignux/ui/lib/data-list-view-registry` (re-export `@/lib/data-list-view-registry`).
11
+
12
+ ## Default allowlist (seven views)
13
+
14
+ Every **`ListPageTemplate`** + **`HubTable`** hub that mounts a product data grid **MUST** expose the same **Add view** menu as Library unless product documents a narrower exception in the hub’s `*-supported-views.ts` file:
15
+
16
+ | # | View | Render kind | How to implement |
17
+ |---|------|-------------|------------------|
18
+ | 1 | Table | `data-table` | Default `HubTable` / custom `columnDefs` |
19
+ | 2 | List | `list-with-toolbar` | **`ListPageBoardCard`** `layout="row"` via `renderListRow` — same as `library-table.tsx` |
20
+ | 3 | Board | `board-with-toolbar` | `renderers["board-with-toolbar"]` or `renderBoardCard` + `boardGroups` |
21
+ | 4 | Dashboard | `dashboard-with-toolbar` | `renderers["dashboard-with-toolbar"]` with **`KeyMetrics`** / charts on `state.rows` |
22
+ | 5 | Folder | `folder-with-toolbar` | `renderers["folder-with-toolbar"]` (e.g. `LibraryOsFolderView`, `FolderGridView`) |
23
+ | 6 | List & details | `panel-with-toolbar` | `renderers["panel-with-toolbar"]` (Miller columns — see `library-table.tsx`) |
24
+ | 7 | Tree & details | `tree-panel-with-toolbar` | `renderers["tree-panel-with-toolbar"]` (e.g. `HubTreePanelView`) |
25
+
26
+ `HubTable` and `ListPageTemplate` default to **`FULL_HUB_SUPPORTED_VIEWS`** when `supportedViewTypes` is omitted.
27
+
28
+ **`PRIMARY_HUB_SUPPORTED_VIEWS`** (table, list, board, dashboard only) is for hubs that **document** they intentionally omit folder/panel/tree — not the default for new work.
29
+
30
+ ## MUST (every new or touched hub)
31
+
32
+ 1. **Sync allowlists** — Pass the **same** `supportedViewTypes` to **`ListPageTemplate`** and **`HubTable`** (or only omit on both so the default applies).
33
+ 2. **Implement every allowed view** — Each entry in `supportedViewTypes` needs a renderer (explicit `renderers` map and/or `renderListRow` / `renderBoardCard` + `boardGroups`). Dev warns when a view is allowed but missing; users see **“does not implement X view”** if you ship the allowlist without bodies.
34
+ 3. **List rows use DS chrome** — **`ListPageBoardCard`** (Library list pattern). **MUST NOT** ship a bare two-line `renderListRow` (title + mono id only).
35
+ 4. **Library-shaped rows** — Hubs on **`LibraryItem`** with a custom table (e.g. Column types) **MUST** delegate non-table views to **`LibraryTable`** (`columnDefs` + `hubLabels` + `folders` state) — do not reimplement folder/panel/tree in isolation.
36
+ 5. **Token hubs** — Use **`FULL_HUB_SUPPORTED_VIEWS`** + **`components/tokens-hub-auxiliary-views.tsx`** (`renderTokenListRow`, `buildTokensHubRenderers`) — **MUST NOT** use `supportedViewTypes={["table"]}`.
37
+
38
+ ## MUST NOT
39
+
40
+ - Pass **`PRIMARY_HUB_SUPPORTED_VIEWS`** or `["table", "list", "board", "dashboard"]` on a hub that should match Library Add view parity.
41
+ - Pass **`supportedViewTypes={["table"]}`** on any primary hub or showcase inside **`ListPageTemplate`**.
42
+ - Copy **`COLUMNS_SUPPORTED_VIEWS = PRIMARY_…`** without implementing folder/panel/tree.
43
+ - Invent a new per-page allowlist constant without a matching renderer for every listed view.
44
+
45
+ ## Narrow exceptions (document in code comment)
46
+
47
+ - Hubs that genuinely lack calendar/folder/panel/tree (e.g. legacy Placements-only four views) — export `ENTITY_SUPPORTED_VIEWS` in `lib/*-supported-views.ts` and **comment why** it is narrower than `FULL_HUB_SUPPORTED_VIEWS`.
48
+ - Calendar is **not** in `FULL_HUB_SUPPORTED_VIEWS`; add only when the hub implements `calendar-with-toolbar`.
49
+
50
+ ## See also
51
+
52
+ - **`.cursor/rules/exxat-data-tables.mdc`**, **`exxat-list-page-connected-views.mdc`**, **`exxat-list-page-view-shells.mdc`**
53
+ - **`docs/exxat-ds/patterns/hub-supported-views-pattern.md`** (vendored from monorepo `apps/web/docs/`)
54
+ - **`docs/exxat-ds/patterns/data-views-pattern.md`**
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: Sidebar and secondary nav — exactly one active item per route (longest path match)
3
+ globs: "**/app-sidebar.tsx,**/secondary-nav.tsx,**/navigation.tsx,**/lib/nav-active.ts"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Exxat DS — single active nav item (MUST)
8
+
9
+ Only **one** primary sidebar row, collapsible child, or secondary-panel link may show the active/selected state for the current route.
10
+
11
+ ## MUST
12
+
13
+ 1. **Use shared helpers** from `@exxatdesignux/ui/lib/nav-active` (or the app copy synced from the DS template):
14
+ - `collectNavUrls` + `buildNavHashClaims` for the full nav tree
15
+ - `isNavHrefActive(pathname, url, allNavUrls, { locationHash, hashClaimsByPath })` for sidebar `SidebarMenuButton` / `isActive`
16
+ - `resolveActiveNavHref(pathname, allLinkHrefs)` for secondary-panel links — compare the returned href to each link’s `href` (longest prefix wins)
17
+ 2. **Never** mark a nav item active with bare `pathname === href` or `pathname.startsWith(href + "/")` when other nav targets share that prefix (e.g. `/dashboard` vs `/dashboard/students`).
18
+ 3. **Hash disambiguation** — when multiple items share the same path (e.g. `/settings` vs `/settings#appearance`), use `buildNavHashClaims` so the no-fragment row defers to the hash-specific row.
19
+ 4. **Collapsible parents** — when any child is active, the **parent** row stays visually neutral in the expanded sidebar; only the child gets `data-active` (icon rail may still highlight the parent — see `isCollapsibleParentMenuButtonActive` in `app-sidebar.tsx`).
20
+ 5. **Keep `ListPageTemplate.supportedViewTypes` and `HubTable.supportedViewTypes` in sync** on the same hub — do not pass `supportedViewTypes={["table"]}` on a primary hub unless that hub truly implements only table.
21
+
22
+ ## MUST NOT
23
+
24
+ - Duplicate bespoke `isNavActive` / `isActive` logic that ignores longest-prefix matching.
25
+ - Ship a new nav surface (sidebar, secondary rail, hub scope list) without wiring the helpers above.
26
+
27
+ ## Reference
28
+
29
+ - `packages/ui/src/lib/nav-active.ts`
30
+ - `packages/ui/template/components/sidebar/app-sidebar.tsx`
31
+ - `packages/ui/template/components/sidebar/secondary-nav.tsx`
@@ -251,15 +251,20 @@ For "System" variants, use the **`SplitSystemSvg`** helper. It renders `ChromeIl
251
251
  Any **primary nav destination** that shows a list of records **must** use this composition (same as Placements / Team):
252
252
 
253
253
  ```
254
- ListPageTemplate
254
+ ListPageTemplate (supportedViewTypes = FULL_HUB_SUPPORTED_VIEWS — seven views)
255
255
  ├── PageHeader (title, subtitle with count, primary CTA, ⋯ more menu)
256
256
  ├── KeyMetrics (flat variant, single row)
257
257
  └── renderContent()
258
- └── DataTable + useTableState + TablePropertiesDrawer
258
+ └── HubTable + useTableState + TablePropertiesDrawer + renderers per view
259
259
  ```
260
260
 
261
+ **Add view parity (binding):** `.cursor/rules/exxat-hub-supported-views.mdc`, `apps/web/docs/hub-supported-views-pattern.md`. **MUST NOT** use `supportedViewTypes={["table"]}` or four-view-only allowlists without a documented exception. List view **MUST** use **`ListPageBoardCard`** (`library-table.tsx`).
262
+
261
263
  **Reference implementations:**
262
- - `components/team-client.tsx` + `components/team-table.tsx` — canonical pattern
264
+ - `components/library-client.tsx` + `components/library-table.tsx` — **canonical seven-view hub** (All questions)
265
+ - `components/columns-showcase.tsx` — custom table via **`LibraryTable`** + same seven views
266
+ - `components/tokens-themes-client.tsx` + `components/tokens-hub-auxiliary-views.tsx`
267
+ - `components/team-client.tsx` + `components/team-table.tsx` — entity hub pattern
263
268
  - `components/placements-client.tsx` + `components/placements-table.tsx` — Placements (most complete)
264
269
 
265
270
  **Files to create for a new hub page `Foo`:**
@@ -1,16 +1,26 @@
1
1
  # Data Table Pattern — Full Implementation Guide
2
2
 
3
- Reference implementation: `components/team-table.tsx` (Team) and `components/placements-table.tsx` (Placements).
3
+ **Primary hub stack:** `ListPageTemplate` → **`HubTable`** (not raw `DataTable` inside hubs).
4
+
5
+ **Add view parity:** `FULL_HUB_SUPPORTED_VIEWS` on **`ListPageTemplate`** + **`HubTable`**; implement every allowed view — **`.cursor/rules/exxat-hub-supported-views.mdc`**, **`apps/web/docs/hub-supported-views-pattern.md`**.
6
+
7
+ **Reference implementations:**
8
+ - `components/library-table.tsx` + `library-client.tsx` — canonical seven-view hub
9
+ - `components/columns-showcase.tsx` — `LibraryTable` + custom `columnDefs`
10
+ - `components/tokens-themes-client.tsx` + `tokens-hub-auxiliary-views.tsx`
11
+ - `components/team-table.tsx`, `components/placements-table.tsx` — entity hubs
4
12
 
5
13
  ---
6
14
 
7
15
  ## Stack Summary
8
16
 
9
17
  ```
10
- DataTable base table component
11
- └── useTableState manages sort/filter/column/group state
12
- └── toolbarSlotrenders the properties button + drawer
13
- └── TablePropertiesDrawer columns, density, filters, sort, conditional rules
18
+ HubTable (inside ListPageTemplate) canonical hub wrapper
19
+ └── DataTable base table component
20
+ └── useTableStatesort/filter/column/group state
21
+ └── toolbarSlot properties button + filter chips + search
22
+ └── TablePropertiesDrawer
23
+ └── renderListRow / renderers ← list, board, dashboard, folder, panel, tree
14
24
  ```
15
25
 
16
26
  All imports:
@@ -38,7 +38,7 @@ Open **only** these files. Skip everything else unless one of these files cites
38
38
 
39
39
  | Task | Read (minimum) | Skip (do not open) |
40
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` |
41
+ | **Build a new hub page** (table + KPIs + view tabs) | `apps/web/components/library-table.tsx` + `library-client.tsx`, `exxat-hub-supported-views.mdc` (or `hub-supported-views-pattern.md`) | `AGENTS.md`, `hub-table.tsx` source, all `*-pattern.md` |
42
42
  | **Add a column / cell pattern** | `apps/web/components/columns-showcase.tsx` (the live catalog) | `data-table/index.tsx`, the whole `types.ts` |
43
43
  | **Add a board / kanban view** | `apps/web/components/placements-board-card.tsx`, `exxat-board-cards.mdc` only | All other rules |
44
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 |
@@ -63,8 +63,9 @@ Answer **yes / no / N/A** to each. A **no** means re-plan; you'll save a regener
63
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
64
  4. **≤ 4 KPIs on the primary strip?** — `KEY_METRICS_KPI_COUNT_MAX = 4`. A fifth becomes a `MetricInsight` or a chart.
65
65
  5. **No toasts for product feedback?** — use `LocalBanner` / `SystemBanner` / inline status. Toasts are reserved for build-tool messages.
66
+ 6. **Seven views + real bodies?** — `FULL_HUB_SUPPORTED_VIEWS` on **`ListPageTemplate`** + **`HubTable`** (sync both); every allowed view has a renderer; list uses **`ListPageBoardCard`** — not `["table"]` / `PRIMARY_HUB_SUPPORTED_VIEWS` / empty `renderers={}`.
66
67
 
67
- If all five are **yes**, generate. If any is **no**, either narrow the requirements
68
+ If all six are **yes**, generate. If any is **no**, either narrow the requirements
68
69
  with **one** clarifying question or fix the gap silently and note it in your response.
69
70
 
70
71
  ---
@@ -141,8 +142,11 @@ import { PageHeader } from "@/components/page-header"
141
142
  import { KeyMetrics, type MetricItem } from "@/components/key-metrics"
142
143
  import { ListPageTemplate, type ViewTab, HubTable } from "@/components/data-views"
143
144
 
145
+ import { FULL_HUB_SUPPORTED_VIEWS } from "@/lib/data-list-view-registry"
146
+
144
147
  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
148
+ /** Seven views (Library parity) unless product documents a narrower list in lib/entity-supported-views.ts */
149
+ const ENTITY_SUPPORTED_VIEWS = FULL_HUB_SUPPORTED_VIEWS
146
150
 
147
151
  export function EntityClient({ rows }) {
148
152
  const [tabs, setTabs] = React.useState<ViewTab[]>(ENTITY_TABS)
@@ -163,7 +167,10 @@ export function EntityClient({ rows }) {
163
167
  hubLabel="Entity" lifecycleTabLabel="Entity"
164
168
  getRowId={r => r.id} getRowSelectionLabel={r => r.name}
165
169
  defaultSort={{ key: "name", dir: "asc" }}
166
- renderers={{}}
170
+ renderListRow={row => /* ListPageBoardCard layout="row" — copy library-table.tsx */}
171
+ renderers={{
172
+ /* board-with-toolbar, dashboard-with-toolbar, folder/panel/tree — see library-table.tsx or tokens-hub-auxiliary-views.tsx */
173
+ }}
167
174
  />
168
175
  )}
169
176
  />
@@ -33,7 +33,7 @@ This is the **happy path** for the most common task: "I have an entity (placemen
33
33
  | 5 | Compose the page client with `PrimaryPageTemplate` → `ListPageTemplate` (KPIs in `metrics`, view tabs in `defaultTabs`, the `HubTable` in `renderContent`). | `apps/web/components/<entity>-client.tsx` | `exxat-list-page-connected-views.mdc` |
34
34
  | 6 | Add to nav (`lib/mock/navigation.tsx`). If the hub needs scoped sub-navigation (e.g. categories), declare `secondaryPanel: "<id>"` and register the panel. | `apps/web/lib/mock/navigation.tsx`, `apps/web/components/sidebar/secondary-panel.tsx` | `exxat-primary-nav-secondary-panel.mdc` |
35
35
 
36
- **Reference page to copy:** `apps/web/components/sites-table.tsx` (the smallest complete hub) or `apps/web/components/columns-showcase.tsx` (single-view hub with a `HubTable`).
36
+ **Reference pages to copy:** `apps/web/components/library-table.tsx` + `library-client.tsx` (seven-view hub), `columns-showcase.tsx` (`LibraryTable` + custom columns), `tokens-themes-client.tsx` + `tokens-hub-auxiliary-views.tsx`. See **`hub-supported-views-pattern.md`** (vendored under `docs/exxat-ds/patterns/`).
37
37
 
38
38
  > **Stop signs.** If you find yourself building a parallel table stack, a second metrics strip, a custom filter row, or pasting raw `<DataTable>` into `renderContent` — **stop and re-read** `.cursor/rules/exxat-reuse-before-custom.mdc`.
39
39
 
@@ -27,9 +27,9 @@ If you find yourself diverging from the reference page, ask **why** before shipp
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
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**, 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) | — |
30
+ | Cell-pattern catalog — **18 SaaS cell patterns** via custom `columnDefs`; **seven views** via **`LibraryTable`** (same Add view as Library). | `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), [`hub-supported-views-pattern`](https://github.com/ExxatDesign/Exxat-DS-Workspace/blob/main/apps/web/docs/hub-supported-views-pattern.md) | [`exxat-data-tables`](../../../.cursor/rules/exxat-data-tables.mdc), [`exxat-hub-supported-views`](../../../.cursor/rules/exxat-hub-supported-views.mdc) | — |
31
31
 
32
- > **First-time hub builder:** start with `sites-table.tsx` (smallest complete hub) or `columns-showcase.tsx` (single-view, easiest).
32
+ > **First-time hub builder:** copy **`library-table.tsx`** + **`library-client.tsx`**; use **`columns-showcase.tsx`** for custom columns; **`tokens-themes-client.tsx`** + **`tokens-hub-auxiliary-views.tsx`** for tokens. Read **`hub-supported-views-pattern.md`** before changing Add view.
33
33
 
34
34
  ---
35
35
 
@@ -6,6 +6,12 @@
6
6
 
7
7
  This document describes how list pages combine **views**, **toolbar** behavior, **filters**, **properties**, and **persistence** in this codebase.
8
8
 
9
+ ## Add view parity (seven views)
10
+
11
+ **Binding:** `.cursor/rules/exxat-hub-supported-views.mdc`. **Detail:** `docs/hub-supported-views-pattern.md`.
12
+
13
+ Every list hub **should** use **`FULL_HUB_SUPPORTED_VIEWS`** (table, list, board, dashboard, folder, panel, tree-panel) on both **`ListPageTemplate`** and **`HubTable`**, with a working renderer per view. **Library** (`library-table.tsx`) is the reference. Do not ship table-only or four-view allowlists on showcase/catalog hubs unless product documents an exception.
14
+
9
15
  ## Reuse existing components (required)
10
16
 
11
17
  **Prefer composing what already exists** over rebuilding one-off tabs, search, filters, or property panels. The **Placements** flow is the reference implementation; new list/table/board pages should wire the same building blocks with new data and column definitions.
@@ -0,0 +1,53 @@
1
+ # Hub supported views pattern (Add view parity)
2
+
3
+ > **Agents:** `.cursor/rules/exxat-hub-supported-views.mdc` (binding). **Reference hub:** Library / All questions (`library-table.tsx`, `library-client.tsx`).
4
+
5
+ ## Problem this solves
6
+
7
+ `ListPageTemplate` filters **Add view** and Properties view tiles from `supportedViewTypes`. If Column types passes four views but Library passes seven, users see inconsistent menus. If a hub allows Board but has no `board-with-toolbar` renderer, users see **“does not implement Board view”**.
8
+
9
+ ## Canonical allowlist
10
+
11
+ Import from the registry (single source of truth):
12
+
13
+ ```ts
14
+ import { FULL_HUB_SUPPORTED_VIEWS } from "@/lib/data-list-view-registry"
15
+ // or
16
+ import { LIBRARY_SUPPORTED_VIEWS } from "@/lib/library-supported-views" // alias of FULL
17
+ ```
18
+
19
+ Seven views: **table**, **list**, **board**, **dashboard**, **folder**, **panel**, **tree-panel**.
20
+
21
+ `HubTable` and `ListPageTemplate` use this list when `supportedViewTypes` is omitted.
22
+
23
+ `PRIMARY_HUB_SUPPORTED_VIEWS` (four views) remains for hubs that intentionally omit folder/panel/tree — document that in `lib/<entity>-supported-views.ts`.
24
+
25
+ ## Wiring checklist
26
+
27
+ 1. **`ListPageTemplate`** — `supportedViewTypes={FULL_HUB_SUPPORTED_VIEWS}` (or entity alias).
28
+ 2. **`HubTable`** — same allowlist (or omit on both).
29
+ 3. **List** — `renderListRow` returning **`ListPageBoardCard`** `layout="row"` (copy from `library-table.tsx`).
30
+ 4. **Board** — `renderBoardCard` + `boardGroups` and/or `renderers["board-with-toolbar"]`.
31
+ 5. **Dashboard** — `renderers["dashboard-with-toolbar"]` with **`KeyMetrics`** on filtered rows.
32
+ 6. **Folder / panel / tree** — explicit `renderers` entries (Library reference) or **`LibraryTable`** for `LibraryItem` rows.
33
+
34
+ ## Special cases in this app
35
+
36
+ | Hub | Pattern |
37
+ |-----|---------|
38
+ | **Library / All questions** | `LibraryTable` + `LIBRARY_SUPPORTED_VIEWS` — reference |
39
+ | **Column types** | `LibraryTable` with `columnDefs` + `hubLabels` + `DEFAULT_LIBRARY_FOLDERS` — custom table, shared other views |
40
+ | **Tokens & themes** | `FULL_HUB_SUPPORTED_VIEWS` + `tokens-hub-auxiliary-views.tsx` |
41
+ | **New entity hub** | Start from `library-table.tsx` or token/columns references; never table-only unless approved |
42
+
43
+ ## Anti-patterns
44
+
45
+ - `supportedViewTypes={["table"]}` on a `ListPageTemplate` hub.
46
+ - `COLUMNS_SUPPORTED_VIEWS = PRIMARY_HUB_SUPPORTED_VIEWS` without board/folder/panel/tree renderers.
47
+ - Minimal `renderListRow` with only stem + `questionId` (not product list UI).
48
+
49
+ ## See also
50
+
51
+ - `packages/ui/src/lib/data-list-view-registry.ts`
52
+ - `docs/exxat-ds/patterns/data-views-pattern.md`
53
+ - `apps/web/lib/hub-connected-view-renderers.ts` — `defineHubViewRenderers` dev warnings
@@ -2256,13 +2256,15 @@ function useBulkBarFixedToTableScrollEl(scrollRef, active, fullWidth) {
2256
2256
  const scheduled = rafThrottle(apply);
2257
2257
  const ro = new ResizeObserver(scheduled);
2258
2258
  ro.observe(el);
2259
+ el.addEventListener("scroll", scheduled, { passive: true });
2259
2260
  window.addEventListener("resize", scheduled, { passive: true });
2260
- window.addEventListener("scroll", scheduled, { passive: true, capture: true });
2261
+ window.addEventListener("scroll", scheduled, { passive: true });
2261
2262
  return () => {
2262
2263
  scheduled.cancel();
2263
2264
  ro.disconnect();
2265
+ el.removeEventListener("scroll", scheduled);
2264
2266
  window.removeEventListener("resize", scheduled);
2265
- window.removeEventListener("scroll", scheduled, { capture: true });
2267
+ window.removeEventListener("scroll", scheduled);
2266
2268
  };
2267
2269
  }, [active, fullWidth, scrollRef]);
2268
2270
  return style;
@@ -2429,11 +2431,13 @@ function DataTableInner({
2429
2431
  };
2430
2432
  update();
2431
2433
  const scheduled = rafThrottle(update);
2432
- window.addEventListener("scroll", scheduled, { passive: true, capture: true });
2434
+ wrapEl.addEventListener("scroll", scheduled, { passive: true });
2435
+ window.addEventListener("scroll", scheduled, { passive: true });
2433
2436
  window.addEventListener("resize", scheduled, { passive: true });
2434
2437
  return () => {
2435
2438
  scheduled.cancel();
2436
- window.removeEventListener("scroll", scheduled, { capture: true });
2439
+ wrapEl.removeEventListener("scroll", scheduled);
2440
+ window.removeEventListener("scroll", scheduled);
2437
2441
  window.removeEventListener("resize", scheduled);
2438
2442
  };
2439
2443
  }, [showColumnHeaders, rows.length, displayCols.length]);
@@ -2870,11 +2874,11 @@ function DataTableInner({
2870
2874
  const rowPy = rowHeight === "compact" ? "py-1" : rowHeight === "comfortable" ? "py-4" : "py-2.5";
2871
2875
  const cs = cellStyle(col.key);
2872
2876
  const tdBase = cn(
2873
- `px-3 ${rowPy} align-middle`,
2877
+ `px-3 ${rowPy} align-middle max-w-0`,
2874
2878
  showGridlines && !isEdgePin && "border-e border-border last:border-e-0",
2875
2879
  "border-b border-border group-last/row:border-b-0",
2876
2880
  isPinned && [
2877
- "z-20 pinned-cell",
2881
+ "z-20 pinned-cell relative",
2878
2882
  "bg-dt-row-bg",
2879
2883
  "group-data-[state=selected]/row:bg-dt-row-selected",
2880
2884
  "group-hover/row:bg-dt-row-hover",
@@ -2924,17 +2928,17 @@ function DataTableInner({
2924
2928
  wrap && "[&_.truncate]:!whitespace-normal [&_.truncate]:!overflow-visible [&_.truncate]:!text-clip"
2925
2929
  ),
2926
2930
  style: tdStyle,
2927
- children: col.cell(row, {
2931
+ children: /* @__PURE__ */ jsx("div", { className: "min-w-0 overflow-hidden", children: col.cell(row, {
2928
2932
  rowIndex,
2929
2933
  selected: isSelected,
2930
2934
  onSelect: (checked) => checked ? setSelected((prev) => /* @__PURE__ */ new Set([...prev, rowId])) : toggleRow(rowId)
2931
- })
2935
+ }) })
2932
2936
  },
2933
2937
  col.key
2934
2938
  );
2935
2939
  }
2936
2940
  const rawVal = String(row[col.key] ?? "");
2937
- return /* @__PURE__ */ jsx("td", { className: cn(tdBase, "text-sm text-foreground/80"), style: tdStyle, children: /* @__PURE__ */ jsx("span", { className: wrap ? "whitespace-normal" : "block truncate", title: !wrap ? rawVal : void 0, children: rawVal }) }, col.key);
2941
+ return /* @__PURE__ */ jsx("td", { className: cn(tdBase, "text-sm text-foreground/80"), style: tdStyle, children: /* @__PURE__ */ jsx("div", { className: "min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx("span", { className: wrap ? "whitespace-normal" : "block truncate", title: !wrap ? rawVal : void 0, children: rawVal }) }) }, col.key);
2938
2942
  })
2939
2943
  },
2940
2944
  String(rowId)