@exxatdesignux/ui 0.5.11 → 0.5.13
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 +45 -0
- package/bin/cli.mjs +70 -1
- package/bin/init.mjs +18 -4
- package/bin/sync-extras.mjs +28 -4
- package/consumer-extras/README.md +41 -5
- package/consumer-extras/cursor-rules/exxat-accessibility.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +5 -3
- package/consumer-extras/cursor-rules/exxat-breadcrumbs-no-back.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-card-vs-list-rows.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +4 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-command-menu.mdc +3 -2
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +5 -3
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +7 -0
- package/consumer-extras/cursor-rules/exxat-drawer-vs-dialog.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-fontawesome-icons.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-hub-supported-views.mdc +6 -4
- package/consumer-extras/cursor-rules/exxat-kbd-shortcuts.mdc +6 -5
- package/consumer-extras/cursor-rules/exxat-kpi-flat-band.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-kpi-max-four.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-kpi-trends.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-list-page-connected-views.mdc +6 -2
- package/consumer-extras/cursor-rules/exxat-list-page-view-shells.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-nav-single-active.mdc +4 -3
- package/consumer-extras/cursor-rules/exxat-no-image-pixel-copy.mdc +25 -14
- package/consumer-extras/cursor-rules/exxat-no-slds-leakage.mdc +8 -2
- package/consumer-extras/cursor-rules/exxat-no-toast.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-no-vaul.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-page-header-actions.mdc +6 -4
- package/consumer-extras/cursor-rules/exxat-page-vs-drawer.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-sidebar-shell.mdc +13 -7
- package/consumer-extras/cursor-rules/exxat-table-properties-drawer.mdc +5 -3
- package/consumer-extras/cursor-rules/exxat-table-row-preview.mdc +1 -0
- package/consumer-extras/cursor-rules/exxat-tabs-chrome.mdc +6 -4
- package/consumer-extras/cursor-rules/exxat-token-discipline.mdc +6 -0
- package/consumer-extras/cursor-rules/exxat-ux-discovery-protocol.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-ux-principles.mdc +1 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-kpi-trends/SKILL.md +5 -3
- package/consumer-extras/patterns/command-menu-pattern.md +2 -2
- package/consumer-extras/patterns/consumer-upgrade-checklist.md +1 -1
- package/consumer-extras/patterns/jobs/README.md +1 -1
- package/consumer-extras/patterns/perf-memory-pattern.md +115 -150
- package/consumer-extras/scripts/dev-guard.mjs +156 -0
- package/consumer-extras/templates/README.md +23 -0
- package/consumer-extras/templates/handoff.md +190 -0
- package/package.json +2 -3
- package/{template → template-vite}/.claude/skills/exxat-ds-skill/SKILL.md +184 -23
- package/template-vite/.cursor/rules/exxat-accessibility.mdc +40 -0
- package/template-vite/.cursor/rules/exxat-board-cards.mdc +28 -0
- package/template-vite/.cursor/rules/exxat-breadcrumbs-no-back.mdc +22 -0
- package/template-vite/.cursor/rules/exxat-card-vs-list-rows.mdc +22 -0
- package/template-vite/.cursor/rules/exxat-centralized-list-dataset.mdc +46 -0
- package/template-vite/.cursor/rules/exxat-collaboration-access.mdc +33 -0
- package/{template → template-vite}/.cursor/rules/exxat-command-menu.mdc +5 -5
- package/template-vite/.cursor/rules/exxat-data-tables.mdc +47 -0
- package/template-vite/.cursor/rules/exxat-dedicated-search-surfaces.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-drawer-vs-dialog.mdc +23 -0
- package/template-vite/.cursor/rules/exxat-ds-agents.mdc +87 -0
- package/template-vite/.cursor/rules/exxat-fontawesome-icons.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-hub-supported-views.mdc +56 -0
- package/{template → template-vite}/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -0
- package/template-vite/.cursor/rules/exxat-kpi-flat-band.mdc +29 -0
- package/template-vite/.cursor/rules/exxat-kpi-max-four.mdc +22 -0
- package/template-vite/.cursor/rules/exxat-kpi-trends.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-library-hub-header.mdc +29 -0
- package/template-vite/.cursor/rules/exxat-list-page-connected-views.mdc +28 -0
- package/template-vite/.cursor/rules/exxat-list-page-view-shells.mdc +32 -0
- package/{template → template-vite}/.cursor/rules/exxat-mono-ids.mdc +1 -0
- package/template-vite/.cursor/rules/exxat-nav-single-active.mdc +32 -0
- package/template-vite/.cursor/rules/exxat-no-image-pixel-copy.mdc +46 -0
- package/template-vite/.cursor/rules/exxat-no-slds-leakage.mdc +84 -0
- package/{template → template-vite}/.cursor/rules/exxat-no-toast.mdc +2 -2
- package/template-vite/.cursor/rules/exxat-no-vaul.mdc +26 -0
- package/template-vite/.cursor/rules/exxat-page-header-actions.mdc +33 -0
- package/{template → template-vite}/.cursor/rules/exxat-page-vs-drawer.mdc +5 -3
- package/template-vite/.cursor/rules/exxat-person-identity-display.mdc +48 -0
- package/template-vite/.cursor/rules/exxat-primary-nav-secondary-panel.mdc +53 -0
- package/template-vite/.cursor/rules/exxat-reuse-before-custom.mdc +37 -0
- package/template-vite/.cursor/rules/exxat-sidebar-shell.mdc +41 -0
- package/template-vite/.cursor/rules/exxat-table-properties-drawer.mdc +79 -0
- package/template-vite/.cursor/rules/exxat-table-row-preview.mdc +25 -0
- package/template-vite/.cursor/rules/exxat-tabs-chrome.mdc +33 -0
- package/template-vite/.cursor/rules/exxat-token-discipline.mdc +109 -0
- package/template-vite/.cursor/rules/exxat-ux-discovery-protocol.mdc +202 -0
- package/template-vite/.cursor/rules/exxat-ux-principles.mdc +187 -0
- package/template-vite/.cursor/skills/exxat-accessibility/SKILL.md +282 -0
- package/template-vite/.cursor/skills/exxat-board-cards/SKILL.md +68 -0
- package/template-vite/.cursor/skills/exxat-card-vs-list-rows/SKILL.md +20 -0
- package/template-vite/.cursor/skills/exxat-centralized-list-dataset/SKILL.md +99 -0
- package/template-vite/.cursor/skills/exxat-collaboration-access/SKILL.md +35 -0
- package/template-vite/.cursor/skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
- package/template-vite/.cursor/skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/SKILL.md +893 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/references/accessibility.md +142 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/references/coach-marks.md +169 -0
- package/template-vite/.cursor/skills/exxat-ds-skill/references/data-table-pattern.md +392 -0
- package/template-vite/.cursor/skills/exxat-fontawesome-icons/SKILL.md +31 -0
- package/template-vite/.cursor/skills/exxat-kpi-flat-band/SKILL.md +38 -0
- package/template-vite/.cursor/skills/exxat-kpi-max-four/SKILL.md +19 -0
- package/template-vite/.cursor/skills/exxat-kpi-trends/SKILL.md +29 -0
- package/template-vite/.cursor/skills/exxat-list-page-view-shells/SKILL.md +36 -0
- package/template-vite/.cursor/skills/exxat-mono-ids/SKILL.md +56 -0
- package/template-vite/.cursor/skills/exxat-primary-nav-secondary-panel/SKILL.md +49 -0
- package/template-vite/.cursor/skills/exxat-senior-ux/SKILL.md +198 -0
- package/template-vite/.cursor/skills/exxat-token-economy/SKILL.md +287 -0
- package/template-vite/.cursor/skills/exxat-ux-audit/SKILL.md +303 -0
- package/{template → template-vite}/components/ask-leo-sidebar.tsx +10 -8
- package/{template → template-vite}/components/command-menu.tsx +1 -1
- package/{template → template-vite}/components/data-views/library-folder-tree-branch.tsx +1 -1
- package/{template → template-vite}/components/dedicated-search-recents.tsx +1 -1
- package/{template → template-vite}/components/dedicated-search-url-composer.tsx +1 -1
- package/{template → template-vite}/components/exxat-product-logo.tsx +3 -3
- package/{template → template-vite}/components/library-client.tsx +1 -1
- package/{template → template-vite}/components/library-hub-client.tsx +2 -2
- package/{template → template-vite}/components/library-secondary-nav.tsx +2 -2
- package/{template → template-vite}/components/library-table.tsx +35 -27
- package/{template → template-vite}/components/new-library-item-form.tsx +1 -1
- package/{template → template-vite}/components/page-breadcrumb-trail.tsx +1 -1
- package/{template → template-vite}/components/settings-client.tsx +1 -1
- package/{template → template-vite}/components/sidebar/app-sidebar.tsx +2 -2
- package/{template → template-vite}/components/sidebar/nav-main.tsx +1 -1
- package/{template → template-vite}/components/sidebar/nav-user.tsx +1 -1
- package/{template → template-vite}/components/sidebar/secondary-nav.tsx +1 -1
- package/{template → template-vite}/components/system-banner-slot.tsx +1 -1
- package/{template → template-vite}/components/templates/discovery-hub-template.tsx +2 -2
- package/{template → template-vite}/components/templates/new-focus-template.tsx +1 -1
- package/{template → template-vite}/components/tokens-secondary-nav.tsx +2 -2
- package/{template → template-vite}/components/tokens-themes-client.tsx +1 -1
- package/{template → template-vite}/hooks/use-secondary-panel-hub-nav.ts +1 -1
- package/template-vite/index.html +49 -0
- package/template-vite/lib/next-compat.tsx +98 -0
- package/{template → template-vite}/package.json +15 -27
- package/template-vite/scripts/port-next-imports.mjs +70 -0
- package/template-vite/src/App.tsx +103 -0
- package/template-vite/src/main.tsx +50 -0
- package/{template/app/(app)/error.tsx → template-vite/src/pages/_error.tsx} +12 -24
- package/{template/app/(app)/loading.tsx → template-vite/src/pages/_loading.tsx} +4 -2
- package/template-vite/src/pages/_not-found.tsx +17 -0
- package/template-vite/src/pages/dashboard.tsx +48 -0
- package/{template/app/(app)/help/page.tsx → template-vite/src/pages/help.tsx} +3 -2
- package/{template/app/(app)/library/layout.tsx → template-vite/src/pages/library/_layout.tsx} +18 -16
- package/{template/app/(app)/library/all/page.tsx → template-vite/src/pages/library/all.tsx} +1 -1
- package/{template/app/(app)/library/new/page.tsx → template-vite/src/pages/library/new.tsx} +12 -18
- package/template-vite/src/routes.tsx +72 -0
- package/template-vite/src/styles/globals.css +25 -0
- package/{template → template-vite}/tsconfig.json +5 -14
- package/template-vite/vite.config.ts +52 -0
- package/consumer-extras/cursor-rules/exxat-dashboard-view-charts.mdc +0 -53
- package/template/.agents/skills/shadcn/SKILL.md +0 -242
- package/template/.agents/skills/shadcn/agents/openai.yml +0 -5
- package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
- package/template/.agents/skills/shadcn/cli.md +0 -257
- package/template/.agents/skills/shadcn/customization.md +0 -202
- package/template/.agents/skills/shadcn/evals/evals.json +0 -47
- package/template/.agents/skills/shadcn/mcp.md +0 -94
- package/template/.agents/skills/shadcn/rules/base-vs-radix.md +0 -306
- package/template/.agents/skills/shadcn/rules/composition.md +0 -195
- package/template/.agents/skills/shadcn/rules/forms.md +0 -192
- package/template/.agents/skills/shadcn/rules/icons.md +0 -101
- package/template/.agents/skills/shadcn/rules/styling.md +0 -162
- package/template/.cursor/rules/exxat-accessibility.mdc +0 -33
- package/template/.cursor/rules/exxat-data-tables.mdc +0 -32
- package/template/.cursor/rules/exxat-ds-agents.mdc +0 -26
- package/template/.cursor/rules/exxat-list-page-connected-views.mdc +0 -16
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +0 -40
- package/template/.nvmrc +0 -1
- package/template/.prettierignore +0 -7
- package/template/Logo/Exxat_Prism.svg +0 -39
- package/template/Logo/Exxat_one.svg +0 -36
- package/template/app/(app)/dashboard/loading.tsx +0 -18
- package/template/app/(app)/dashboard/page.tsx +0 -36
- package/template/app/(app)/layout.tsx +0 -77
- package/template/app/global-error.tsx +0 -63
- package/template/app/globals.css +0 -20
- package/template/app/layout.tsx +0 -133
- package/template/app/page.tsx +0 -9
- package/template/docs/HANDBOOK.md +0 -187
- package/template/docs/blueprints/README.md +0 -86
- package/template/docs/blueprints/_template.md +0 -91
- package/template/docs/blueprints/board-card.md +0 -123
- package/template/docs/blueprints/data-table.md +0 -139
- package/template/docs/blueprints/key-metrics.md +0 -128
- package/template/docs/blueprints/list-page-template.md +0 -123
- package/template/docs/blueprints/page-header.md +0 -130
- package/template/docs/card-vs-rows-pattern.md +0 -36
- package/template/docs/collaboration-access-pattern.md +0 -116
- package/template/docs/command-menu-pattern.md +0 -45
- package/template/docs/component-selection-guide.md +0 -224
- package/template/docs/components-audit-2026-05.md +0 -158
- package/template/docs/consumer-upgrade-checklist.md +0 -52
- package/template/docs/data-views-pattern.md +0 -185
- package/template/docs/drawer-vs-dialog-pattern.md +0 -50
- package/template/docs/glossary.md +0 -59
- package/template/docs/hub-supported-views-pattern.md +0 -53
- package/template/docs/jobs/README.md +0 -59
- package/template/docs/jobs/record-detail.md +0 -177
- package/template/docs/kpi-flat-band-pattern.md +0 -57
- package/template/docs/kpi-strip-max-four-pattern.md +0 -30
- package/template/docs/kpi-trend-pattern.md +0 -58
- package/template/docs/large-dataset-strategy.md +0 -155
- package/template/docs/library-hub-header-pattern.md +0 -25
- package/template/docs/migrations/0001-brand-deep-alias-stabilization.md +0 -95
- package/template/docs/migrations/0002-exxat-token-namespace.md +0 -154
- package/template/docs/migrations/0003-globals-css-canonical.md +0 -110
- package/template/docs/migrations/README.md +0 -100
- package/template/docs/migrations/_template.md +0 -64
- package/template/docs/modern-saas-patterns.md +0 -165
- package/template/docs/perf-memory-pattern.md +0 -206
- package/template/docs/reference-implementations.md +0 -153
- package/template/docs/shell-surface-elevation-pattern.md +0 -52
- package/template/docs/token-taxonomy.md +0 -416
- package/template/docs/voice-and-tone.md +0 -262
- package/template/ecosystem.config.cjs +0 -32
- package/template/next.config.mjs +0 -216
- package/template/postcss.config.mjs +0 -8
- package/template/public/favicon/favicon.ico +0 -0
- package/template/tests/setup.ts +0 -26
- package/template/vitest.config.ts +0 -18
- /package/{template → template-vite}/.cursor/rules/exxat-dashboard-view-charts.mdc +0 -0
- /package/{template → template-vite}/.prettierrc +0 -0
- /package/{template → template-vite}/AGENTS.md +0 -0
- /package/{template → template-vite}/README.md +0 -0
- /package/{template → template-vite}/components/.gitkeep +0 -0
- /package/{template → template-vite}/components/ask-leo-composer.tsx +0 -0
- /package/{template → template-vite}/components/brand-color-picker.tsx +0 -0
- /package/{template → template-vite}/components/chart-area-interactive.tsx +0 -0
- /package/{template → template-vite}/components/charts-overview.tsx +0 -0
- /package/{template → template-vite}/components/collaboration-access-flow.tsx +0 -0
- /package/{template → template-vite}/components/columns-client.tsx +0 -0
- /package/{template → template-vite}/components/columns-showcase.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-promo-banner.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-quota-progress-card.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-report-charts.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-section-heading.tsx +0 -0
- /package/{template → template-vite}/components/dashboard-tabs.tsx +0 -0
- /package/{template → template-vite}/components/data-table/filter-date-calendar.tsx +0 -0
- /package/{template → template-vite}/components/data-table/filter-text-value-input.tsx +0 -0
- /package/{template → template-vite}/components/data-table/index.tsx +0 -0
- /package/{template → template-vite}/components/data-table/pagination.tsx +0 -0
- /package/{template → template-vite}/components/data-table/types.ts +0 -0
- /package/{template → template-vite}/components/data-table/use-table-state.test.ts +0 -0
- /package/{template → template-vite}/components/data-table/use-table-state.ts +0 -0
- /package/{template → template-vite}/components/data-views/board-card-primitives.tsx +0 -0
- /package/{template → template-vite}/components/data-views/data-row-list.tsx +0 -0
- /package/{template → template-vite}/components/data-views/finder-panel-view.tsx +0 -0
- /package/{template → template-vite}/components/data-views/folder-grid-view.tsx +0 -0
- /package/{template → template-vite}/components/data-views/hub-table.tsx +0 -0
- /package/{template → template-vite}/components/data-views/index.ts +0 -0
- /package/{template → template-vite}/components/data-views/list-page-board-card.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-board-template.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-connected-view-body.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-split-details-placeholder.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-split-hub-chrome.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-split-hub-tokens.ts +0 -0
- /package/{template → template-vite}/components/data-views/list-page-tree-column-header.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-tree-panel-shell.tsx +0 -0
- /package/{template → template-vite}/components/data-views/list-page-view-frame.tsx +0 -0
- /package/{template → template-vite}/components/data-views/os-folder-glyph.tsx +0 -0
- /package/{template → template-vite}/components/data-views/outline-tree-menu.tsx +0 -0
- /package/{template → template-vite}/components/data-views/table-cells.tsx +0 -0
- /package/{template → template-vite}/components/dev-chunk-load-recovery.tsx +0 -0
- /package/{template → template-vite}/components/export-drawer.test.tsx +0 -0
- /package/{template → template-vite}/components/export-drawer.tsx +0 -0
- /package/{template → template-vite}/components/folder-details-shell.tsx +0 -0
- /package/{template → template-vite}/components/form-layout-01.tsx +0 -0
- /package/{template → template-vite}/components/hub-tree-panel-view.tsx +0 -0
- /package/{template → template-vite}/components/invite-collaborators-drawer.tsx +0 -0
- /package/{template → template-vite}/components/key-metrics-ask-leo-bridge.tsx +0 -0
- /package/{template → template-vite}/components/key-metrics.tsx +0 -0
- /package/{template → template-vite}/components/leo-insight-indicator.tsx +0 -0
- /package/{template → template-vite}/components/leo-typing-dots.tsx +0 -0
- /package/{template → template-vite}/components/library-board-view.tsx +0 -0
- /package/{template → template-vite}/components/library-dashboard-charts.tsx +0 -0
- /package/{template → template-vite}/components/library-favorite-button.tsx +0 -0
- /package/{template → template-vite}/components/library-new-folder-sheet.tsx +0 -0
- /package/{template → template-vite}/components/library-os-folder-view.tsx +0 -0
- /package/{template → template-vite}/components/library-page-header.tsx +0 -0
- /package/{template → template-vite}/components/library-panel-activator.tsx +0 -0
- /package/{template → template-vite}/components/list-hub-status-badge.tsx +0 -0
- /package/{template → template-vite}/components/list-page-dashboard-charts.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/getting-started.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/index.ts +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-01.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-02.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-03.tsx +0 -0
- /package/{template → template-vite}/components/onboarding/onboarding-04.tsx +0 -0
- /package/{template → template-vite}/components/page-header.tsx +0 -0
- /package/{template → template-vite}/components/product-switcher.tsx +0 -0
- /package/{template → template-vite}/components/product-wordmark.tsx +0 -0
- /package/{template → template-vite}/components/settings-appearance-card.tsx +0 -0
- /package/{template → template-vite}/components/settings-form-row.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/app-sidebar-dynamic.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/index.ts +0 -0
- /package/{template → template-vite}/components/sidebar/nav-documents.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/nav-secondary.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/secondary-panel.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/sidebar-auto-collapse.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/sidebar-auto-open.tsx +0 -0
- /package/{template → template-vite}/components/sidebar/sidebar-shell.tsx +0 -0
- /package/{template → template-vite}/components/site-header.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/column-row.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/draggable-list.ts +0 -0
- /package/{template → template-vite}/components/table-properties/drawer-button.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/drawer.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/filter-card.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/index.ts +0 -0
- /package/{template → template-vite}/components/table-properties/sort-card.tsx +0 -0
- /package/{template → template-vite}/components/table-properties/types.ts +0 -0
- /package/{template → template-vite}/components/task-list-panel.tsx +0 -0
- /package/{template → template-vite}/components/task-priority-badge.tsx +0 -0
- /package/{template → template-vite}/components/templates/dedicated-search-landing-template.tsx +0 -0
- /package/{template → template-vite}/components/templates/dedicated-search-results-template.tsx +0 -0
- /package/{template → template-vite}/components/templates/list-page.tsx +0 -0
- /package/{template → template-vite}/components/templates/nested-secondary-panel-shell.tsx +0 -0
- /package/{template → template-vite}/components/templates/primary-page-template.tsx +0 -0
- /package/{template → template-vite}/components/templates/secondary-panel-hub-template.tsx +0 -0
- /package/{template → template-vite}/components/theme-color-sync.tsx +0 -0
- /package/{template → template-vite}/components/theme-provider.tsx +0 -0
- /package/{template → template-vite}/components/tinted-icon-disc.tsx +0 -0
- /package/{template → template-vite}/components/tokens-hub-auxiliary-views.tsx +0 -0
- /package/{template → template-vite}/components/tokens-themes-section.tsx +0 -0
- /package/{template → template-vite}/components/ui/accordion.tsx +0 -0
- /package/{template → template-vite}/components/ui/ai-thinking-surface.tsx +0 -0
- /package/{template → template-vite}/components/ui/alert-dialog.tsx +0 -0
- /package/{template → template-vite}/components/ui/avatar.tsx +0 -0
- /package/{template → template-vite}/components/ui/badge.tsx +0 -0
- /package/{template → template-vite}/components/ui/banner.tsx +0 -0
- /package/{template → template-vite}/components/ui/breadcrumb.tsx +0 -0
- /package/{template → template-vite}/components/ui/button.tsx +0 -0
- /package/{template → template-vite}/components/ui/calendar.tsx +0 -0
- /package/{template → template-vite}/components/ui/card.tsx +0 -0
- /package/{template → template-vite}/components/ui/chart.tsx +0 -0
- /package/{template → template-vite}/components/ui/checkbox.tsx +0 -0
- /package/{template → template-vite}/components/ui/coach-mark.tsx +0 -0
- /package/{template → template-vite}/components/ui/collapsible.tsx +0 -0
- /package/{template → template-vite}/components/ui/command.tsx +0 -0
- /package/{template → template-vite}/components/ui/context-menu.tsx +0 -0
- /package/{template → template-vite}/components/ui/date-picker-field.tsx +0 -0
- /package/{template → template-vite}/components/ui/dialog.tsx +0 -0
- /package/{template → template-vite}/components/ui/dot-pattern.tsx +0 -0
- /package/{template → template-vite}/components/ui/drag-handle-grip.tsx +0 -0
- /package/{template → template-vite}/components/ui/dropdown-menu.tsx +0 -0
- /package/{template → template-vite}/components/ui/field.tsx +0 -0
- /package/{template → template-vite}/components/ui/form.tsx +0 -0
- /package/{template → template-vite}/components/ui/hover-card.tsx +0 -0
- /package/{template → template-vite}/components/ui/input-group.tsx +0 -0
- /package/{template → template-vite}/components/ui/input-mask.tsx +0 -0
- /package/{template → template-vite}/components/ui/input.tsx +0 -0
- /package/{template → template-vite}/components/ui/kbd.tsx +0 -0
- /package/{template → template-vite}/components/ui/label.tsx +0 -0
- /package/{template → template-vite}/components/ui/leo-icon.tsx +0 -0
- /package/{template → template-vite}/components/ui/payment-card-fields.tsx +0 -0
- /package/{template → template-vite}/components/ui/popover.tsx +0 -0
- /package/{template → template-vite}/components/ui/radio-group.tsx +0 -0
- /package/{template → template-vite}/components/ui/resizable.tsx +0 -0
- /package/{template → template-vite}/components/ui/scroll-area.tsx +0 -0
- /package/{template → template-vite}/components/ui/select.tsx +0 -0
- /package/{template → template-vite}/components/ui/selection-tile-grid.tsx +0 -0
- /package/{template → template-vite}/components/ui/separator.tsx +0 -0
- /package/{template → template-vite}/components/ui/sheet.tsx +0 -0
- /package/{template → template-vite}/components/ui/sidebar.tsx +0 -0
- /package/{template → template-vite}/components/ui/skeleton.tsx +0 -0
- /package/{template → template-vite}/components/ui/slider.tsx +0 -0
- /package/{template → template-vite}/components/ui/sonner.tsx +0 -0
- /package/{template → template-vite}/components/ui/status-badge.tsx +0 -0
- /package/{template → template-vite}/components/ui/table.tsx +0 -0
- /package/{template → template-vite}/components/ui/tabs.tsx +0 -0
- /package/{template → template-vite}/components/ui/textarea.tsx +0 -0
- /package/{template → template-vite}/components/ui/tip.tsx +0 -0
- /package/{template → template-vite}/components/ui/toggle-group.tsx +0 -0
- /package/{template → template-vite}/components/ui/toggle-switch.tsx +0 -0
- /package/{template → template-vite}/components/ui/toggle.tsx +0 -0
- /package/{template → template-vite}/components/ui/tooltip.tsx +0 -0
- /package/{template → template-vite}/components/ui/view-segmented-control.tsx +0 -0
- /package/{template → template-vite}/components.json +0 -0
- /package/{template → template-vite}/contexts/chart-variant-context.tsx +0 -0
- /package/{template → template-vite}/contexts/command-menu-context.tsx +0 -0
- /package/{template → template-vite}/contexts/dashboard-view-context.tsx +0 -0
- /package/{template → template-vite}/contexts/product-context.tsx +0 -0
- /package/{template → template-vite}/contexts/system-banner-context.tsx +0 -0
- /package/{template → template-vite}/eslint.config.mjs +0 -0
- /package/{template → template-vite}/fontawesome-subset.manifest.json +0 -0
- /package/{template → template-vite}/hooks/.gitkeep +0 -0
- /package/{template → template-vite}/hooks/use-app-theme.ts +0 -0
- /package/{template → template-vite}/hooks/use-coach-mark.ts +0 -0
- /package/{template → template-vite}/hooks/use-location-hash.ts +0 -0
- /package/{template → template-vite}/hooks/use-mobile.ts +0 -0
- /package/{template → template-vite}/hooks/use-mod-key-label.ts +0 -0
- /package/{template → template-vite}/hooks/use-sidebar-reflow-zoom.ts +0 -0
- /package/{template → template-vite}/lib/.gitkeep +0 -0
- /package/{template → template-vite}/lib/ask-leo-route-context.ts +0 -0
- /package/{template → template-vite}/lib/chart-keyboard-selection.test.ts +0 -0
- /package/{template → template-vite}/lib/chart-keyboard-selection.ts +0 -0
- /package/{template → template-vite}/lib/chart-line-dash.ts +0 -0
- /package/{template → template-vite}/lib/chunk-load-error.ts +0 -0
- /package/{template → template-vite}/lib/coach-mark-registry.ts +0 -0
- /package/{template → template-vite}/lib/collaborator-access.ts +0 -0
- /package/{template → template-vite}/lib/command-menu-config.ts +0 -0
- /package/{template → template-vite}/lib/command-menu-search-data.ts +0 -0
- /package/{template → template-vite}/lib/conditional-rule-match.ts +0 -0
- /package/{template → template-vite}/lib/dashboard-customize-coach-mark.ts +0 -0
- /package/{template → template-vite}/lib/dashboard-layout-merge.ts +0 -0
- /package/{template → template-vite}/lib/data-list-display-options.ts +0 -0
- /package/{template → template-vite}/lib/data-list-persistence.ts +0 -0
- /package/{template → template-vite}/lib/data-list-view-registry.ts +0 -0
- /package/{template → template-vite}/lib/data-list-view-surface.ts +0 -0
- /package/{template → template-vite}/lib/data-list-view.ts +0 -0
- /package/{template → template-vite}/lib/data-view-dashboard-storage.ts +0 -0
- /package/{template → template-vite}/lib/date-filter.ts +0 -0
- /package/{template → template-vite}/lib/dedicated-search-recents.ts +0 -0
- /package/{template → template-vite}/lib/dedicated-search-url.ts +0 -0
- /package/{template → template-vite}/lib/dev-log.test.ts +0 -0
- /package/{template → template-vite}/lib/dev-log.ts +0 -0
- /package/{template → template-vite}/lib/discovery-hub.ts +0 -0
- /package/{template → template-vite}/lib/editable-target.ts +0 -0
- /package/{template → template-vite}/lib/exxat-palette.json +0 -0
- /package/{template → template-vite}/lib/exxat-palette.ts +0 -0
- /package/{template → template-vite}/lib/floating-sheet-panel.ts +0 -0
- /package/{template → template-vite}/lib/full-hub-supported-views.ts +0 -0
- /package/{template → template-vite}/lib/hub-connected-view-renderers.ts +0 -0
- /package/{template → template-vite}/lib/initials-from-name.ts +0 -0
- /package/{template → template-vite}/lib/library-authoring.ts +0 -0
- /package/{template → template-vite}/lib/library-dedicated-search.ts +0 -0
- /package/{template → template-vite}/lib/library-hub-search.ts +0 -0
- /package/{template → template-vite}/lib/library-nav.ts +0 -0
- /package/{template → template-vite}/lib/library-recent-searches.ts +0 -0
- /package/{template → template-vite}/lib/library-supported-views.ts +0 -0
- /package/{template → template-vite}/lib/list-hub-supported-views.ts +0 -0
- /package/{template → template-vite}/lib/list-page-table-properties.ts +0 -0
- /package/{template → template-vite}/lib/list-status-badges.ts +0 -0
- /package/{template → template-vite}/lib/logo-dev.ts +0 -0
- /package/{template → template-vite}/lib/mailto.ts +0 -0
- /package/{template → template-vite}/lib/mock/dashboard.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-folders.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-header-collaborators.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-inspector.ts +0 -0
- /package/{template → template-vite}/lib/mock/library-kpi.ts +0 -0
- /package/{template → template-vite}/lib/mock/library.ts +0 -0
- /package/{template → template-vite}/lib/mock/navigation.tsx +0 -0
- /package/{template → template-vite}/lib/motion-ui.ts +0 -0
- /package/{template → template-vite}/lib/product-brand.ts +0 -0
- /package/{template → template-vite}/lib/raf-throttle.ts +0 -0
- /package/{template → template-vite}/lib/row-height.ts +0 -0
- /package/{template → template-vite}/lib/sidebar-state-cookie.ts +0 -0
- /package/{template → template-vite}/lib/stock-portrait.ts +0 -0
- /package/{template → template-vite}/lib/table-state-lifecycle.ts +0 -0
- /package/{template → template-vite}/lib/utils.test.ts +0 -0
- /package/{template → template-vite}/lib/utils.ts +0 -0
- /package/{template → template-vite}/public/.gitkeep +0 -0
- /package/{template → template-vite}/public/Illustration/Rotation.svg +0 -0
- /package/{template → template-vite}/public/avatars/user.svg +0 -0
- /package/{template/public → template-vite/public/favicon}/favicon.ico +0 -0
- /package/{template/app → template-vite/public}/favicon.ico +0 -0
- /package/{template → template-vite}/public/folders/icons8-folder-windows-11.svg +0 -0
- /package/{template → template-vite}/public/logos/exxat-one.svg +0 -0
- /package/{template → template-vite}/public/logos/exxat-prism.svg +0 -0
- /package/{template → template-vite}/public/mock-schools/emory.svg +0 -0
- /package/{template → template-vite}/public/mock-schools/rush.svg +0 -0
- /package/{template → template-vite}/scripts/fontawesome-subset-audit.mjs +0 -0
- /package/{template → template-vite}/scripts/pm2-startup-macos.sh +0 -0
- /package/{template → template-vite}/skills-lock.json +0 -0
- /package/{template/app/(app)/columns/page.tsx → template-vite/src/pages/columns.tsx} +0 -0
- /package/{template/app/(app)/library/find/page.tsx → template-vite/src/pages/library/find.tsx} +0 -0
- /package/{template/app/(app)/library/page.tsx → template-vite/src/pages/library/index.tsx} +0 -0
- /package/{template/app/(app)/library/list/page.tsx → template-vite/src/pages/library/list.tsx} +0 -0
- /package/{template/app/(app)/settings/page.tsx → template-vite/src/pages/settings.tsx} +0 -0
- /package/{template/app/(app)/tokens-themes/page.tsx → template-vite/src/pages/tokens-themes.tsx} +0 -0
- /package/{template → template-vite}/stores/app-store.ts +0 -0
- /package/{template → template-vite}/types/react-payment-inputs.d.ts +0 -0
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Exxat DS — Jobs library
|
|
2
|
-
|
|
3
|
-
> **What:** Canonical reference per **job-to-be-done**, not per component.
|
|
4
|
-
> **Why:** Components answer "how to render". Jobs answer "what to build".
|
|
5
|
-
> **Who:** Humans + AI agents writing design briefs
|
|
6
|
-
> ([`exxat-ux-discovery-protocol.mdc`](../../../.cursor/rules/exxat-ux-discovery-protocol.mdc)).
|
|
7
|
-
|
|
8
|
-
A **job** is what the user is trying to accomplish — not the screen they end
|
|
9
|
-
up on. Two products with the same component stack can ship different jobs;
|
|
10
|
-
two products with different stacks can ship the same job. **The job is the
|
|
11
|
-
contract.**
|
|
12
|
-
|
|
13
|
-
## When to use this library
|
|
14
|
-
|
|
15
|
-
- The user prompt names a screen ("student detail", "settings page", "compose
|
|
16
|
-
form"). Map it to a **job** first, then pick the reference.
|
|
17
|
-
- Before building, the design brief MUST cite the relevant job doc as a
|
|
18
|
-
reference.
|
|
19
|
-
- If no job doc matches, **write one** (even short) as part of the work.
|
|
20
|
-
|
|
21
|
-
## Job → screen mapping
|
|
22
|
-
|
|
23
|
-
| Job | Doc | Canonical references |
|
|
24
|
-
|-----|-----|----------------------|
|
|
25
|
-
| **Review a record's full state** | [`record-detail.md`](./record-detail.md) | Students / Placements / Library detail |
|
|
26
|
-
| **Triage a list of records** (find at-risk / actionable) | *future* | `PlacementsClient`, `ComplianceClient` |
|
|
27
|
-
| **Compose / create a new record** | *future* | `new-library-item-form.tsx` |
|
|
28
|
-
| **Configure preferences or workspace settings** | *future* | `app/(app)/settings/page.tsx` |
|
|
29
|
-
| **Scan many metrics for anomalies** | *future* | Dashboard route, `DashboardTabs` |
|
|
30
|
-
| **Search / find anything** | *future* | `CommandMenu`, `DedicatedSearch*` |
|
|
31
|
-
| **Onboarding / first-run setup** | *future* | `getting-started.tsx`, `CoachMark` |
|
|
32
|
-
| **Approve / review submitted work** | *future* | (TBD) |
|
|
33
|
-
|
|
34
|
-
## How a job doc is structured
|
|
35
|
-
|
|
36
|
-
Every job doc must include:
|
|
37
|
-
|
|
38
|
-
1. **The job** — one paragraph; user pain, decision, action.
|
|
39
|
-
2. **When this job applies** — triggers, scale, frequency.
|
|
40
|
-
3. **Pattern: route vs sheet vs inline** — with a decision table.
|
|
41
|
-
4. **Information architecture** — ASCII diagram of the IA.
|
|
42
|
-
5. **Layers, in priority order** — identity → status → groups → activity →
|
|
43
|
-
related.
|
|
44
|
-
6. **When to use tabs / subsections.**
|
|
45
|
-
7. **Navigation — the way back** (P1 enforcement).
|
|
46
|
-
8. **Actions** — primary, overflow, inline.
|
|
47
|
-
9. **States** — loading, empty, error, stale.
|
|
48
|
-
10. **Accessibility** — A11y concerns specific to the job.
|
|
49
|
-
11. **Modern SaaS analogues** — cite by product + Mx codes.
|
|
50
|
-
12. **Anti-patterns** — what NOT to do.
|
|
51
|
-
13. **Quick checklist** for the post-build audit.
|
|
52
|
-
|
|
53
|
-
## See also
|
|
54
|
-
|
|
55
|
-
- [`../modern-saas-patterns.md`](../modern-saas-patterns.md) — the canon
|
|
56
|
-
- [`../component-selection-guide.md`](../component-selection-guide.md) —
|
|
57
|
-
decision tree from job → composition
|
|
58
|
-
- [`../blueprints/`](../blueprints/) — framework-agnostic specs
|
|
59
|
-
- [`../../../.cursor/skills/exxat-senior-ux/SKILL.md`](../../../.cursor/skills/exxat-senior-ux/SKILL.md)
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# Job: Review a record's full state
|
|
2
|
-
|
|
3
|
-
> **Surface type:** Standalone route OR side-panel sheet.
|
|
4
|
-
> **Examples in product:** Student profile, Placement record, Library item,
|
|
5
|
-
> Site profile, Team member profile.
|
|
6
|
-
|
|
7
|
-
## 1. The job
|
|
8
|
-
|
|
9
|
-
The user lands on a single record and needs to **understand its current
|
|
10
|
-
state** in order to **decide what to do next**. Decisions can include:
|
|
11
|
-
contact someone, edit a field, change status, place / unplace, archive,
|
|
12
|
-
follow up later, or simply confirm correctness before another action.
|
|
13
|
-
|
|
14
|
-
This job is **not**: edit (separate flow), bulk action (lives on the list),
|
|
15
|
-
configure workspace settings.
|
|
16
|
-
|
|
17
|
-
## 2. When this job applies
|
|
18
|
-
|
|
19
|
-
- The record has > ~5 displayable fields.
|
|
20
|
-
- The user reaches it from a list, search, deep link, or notification.
|
|
21
|
-
- The user's next action depends on the record's state (status, ownership,
|
|
22
|
-
data freshness).
|
|
23
|
-
- The record may have children (placements, attachments, comments) that
|
|
24
|
-
matter to the decision.
|
|
25
|
-
|
|
26
|
-
## 3. Pattern: route vs sheet vs inline
|
|
27
|
-
|
|
28
|
-
| Pattern | When | DS comp |
|
|
29
|
-
|---------|------|---------|
|
|
30
|
-
| **Standalone route** (`/students/[id]`) | Deep link from email / notifications / reports; user may keep tab open; record has > 10 fields | `PrimaryPageTemplate` + `PageHeader` |
|
|
31
|
-
| **Sheet over hub** | User is triaging from a list and may open many records in sequence | `Sheet` over `ListPageTemplate` |
|
|
32
|
-
| **Inline expansion** (`HoverCard`) | Read-only quick preview; < 5 fields | `HoverCard` on row hover |
|
|
33
|
-
|
|
34
|
-
Pick **route** by default. Pick **sheet** if the user's primary path is
|
|
35
|
-
triaging from a list and they typically open many in a single session.
|
|
36
|
-
|
|
37
|
-
## 4. Information architecture
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
┌─ SiteHeader (breadcrumb only) ──────────────────────────────────┐
|
|
41
|
-
│ Dashboard › Students › Jordan Lee │ ← ancestors + title (P1, P2)
|
|
42
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
43
|
-
┌─ Identity row ──────────────────────────────────────────────────┐
|
|
44
|
-
│ [Avatar] Jordan Lee [Edit] [⋯] │ ← name, ID, primary action + overflow
|
|
45
|
-
│ STU-2026-1042 · jordan.lee@example.edu │
|
|
46
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
47
|
-
┌─ Status row (above the fold, P13) ──────────────────────────────┐
|
|
48
|
-
│ ● Active ✓ Compliant ⊕ Placed at Sinai │
|
|
49
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
50
|
-
┌─ Field groups (2-col card grid) ────────────────────────────────┐
|
|
51
|
-
│ Program │ Academic │
|
|
52
|
-
│ Placement │ Compliance │
|
|
53
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
54
|
-
┌─ Activity timeline (M7, optional) ──────────────────────────────┐
|
|
55
|
-
│ Today · Compliance status updated by Maria │
|
|
56
|
-
│ Tue · Placement assigned · Sinai Hospital │
|
|
57
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Layers, in priority order
|
|
61
|
-
|
|
62
|
-
1. **Identity** — name, system ID (mono), avatar, primary contact link.
|
|
63
|
-
2. **Status** — every status badge that drives a decision; visible without
|
|
64
|
-
scrolling (P13).
|
|
65
|
-
3. **Field groups** — 2-column card grid by default. Group by **conceptual
|
|
66
|
-
coherence** (Program / Academic / Placement / Compliance), not by table
|
|
67
|
-
structure.
|
|
68
|
-
4. **Activity timeline** — if the record changes over time and the history
|
|
69
|
-
matters (M7).
|
|
70
|
-
5. **Related lists** — placements, comments, attachments — if they exist for
|
|
71
|
-
this entity.
|
|
72
|
-
|
|
73
|
-
### When to use section tabs
|
|
74
|
-
|
|
75
|
-
- **Don't tab** if total field count is ≤ ~20 and groups are ≤ 4. Single
|
|
76
|
-
scroll is faster.
|
|
77
|
-
- **Do tab** if the record has rich children (placements history,
|
|
78
|
-
supervisors, contracts, conversation threads) and the user typically
|
|
79
|
-
deep-dives into one section.
|
|
80
|
-
- Tabs use `Tabs` + `TabsList` `w-fit` `variant="line"` — never full-width
|
|
81
|
-
(`exxat-tabs-chrome.mdc`).
|
|
82
|
-
|
|
83
|
-
## 5. Navigation — the way back
|
|
84
|
-
|
|
85
|
-
**Exactly one path back** (P1).
|
|
86
|
-
|
|
87
|
-
| Reached from | Way back |
|
|
88
|
-
|--------------|----------|
|
|
89
|
-
| List route | `SiteHeader` breadcrumb (`Students`) |
|
|
90
|
-
| Deep link / email | `SiteHeader` breadcrumb to the canonical parent |
|
|
91
|
-
| Sheet from hub | `Sheet` close (X / Esc) |
|
|
92
|
-
| Notification | Breadcrumb to canonical parent (not "Back to notification") |
|
|
93
|
-
|
|
94
|
-
**Never** add a body-level "Back to <parent>" button when the breadcrumb is
|
|
95
|
-
present (`exxat-breadcrumbs-no-back.mdc`).
|
|
96
|
-
|
|
97
|
-
## 6. Actions
|
|
98
|
-
|
|
99
|
-
| Slot | Component | What goes here |
|
|
100
|
-
|------|-----------|----------------|
|
|
101
|
-
| Primary | `PageHeader.primaryAction` (`Button variant="default" size="lg"`) | The single most common next action — "Edit", "Place", "Approve" |
|
|
102
|
-
| Overflow | `Button variant="outline" size="icon-lg"` → `DropdownMenu` | Export, Archive, Duplicate, Share, dangerous-but-rare actions |
|
|
103
|
-
| Inline | Icon-only buttons with tooltip | Copy email, dial phone, open site |
|
|
104
|
-
| Status flip | `Sheet` or inline `Select` | Status changes that are audited (multi-step) |
|
|
105
|
-
|
|
106
|
-
Exactly **one** filled CTA per surface (P3).
|
|
107
|
-
|
|
108
|
-
## 7. States
|
|
109
|
-
|
|
110
|
-
| State | What to show |
|
|
111
|
-
|-------|--------------|
|
|
112
|
-
| **Loading** | `Skeleton` matching the IA shape (identity → status → cards) — M9 |
|
|
113
|
-
| **Empty / not found** | `EmptyState` + "Return to <list>" outline button |
|
|
114
|
-
| **Error** | `LocalBanner` inside the body; identity row still renders if available |
|
|
115
|
-
| **Stale data** | Subtle "Updated <relative>" in identity meta |
|
|
116
|
-
|
|
117
|
-
## 8. Accessibility
|
|
118
|
-
|
|
119
|
-
- One `<h1>` (P2) — typically `PageHeader.title`.
|
|
120
|
-
- Status row is a `role="list"`; each badge is `role="listitem"` (already in
|
|
121
|
-
`ListHubStatusBadge`).
|
|
122
|
-
- Inline mailto / tel links are real `<a>`s, not buttons.
|
|
123
|
-
- Tab order: breadcrumb → identity → primary action → overflow → status →
|
|
124
|
-
fields → activity → related.
|
|
125
|
-
- Icon-only actions carry `aria-label` + a tooltip with the same text.
|
|
126
|
-
|
|
127
|
-
## 9. Modern SaaS analogues
|
|
128
|
-
|
|
129
|
-
| Product | What to study |
|
|
130
|
-
|---------|---------------|
|
|
131
|
-
| **Linear** issue detail | Identity + status + properties + activity; sheet variant for triage |
|
|
132
|
-
| **Stripe Dashboard** customer / charge | Identity + status badges + grouped data + audit log |
|
|
133
|
-
| **Notion** page-as-database row | Inline editing (M5), property panel |
|
|
134
|
-
| **Plain / Pylon** ticket | Conversation-as-document for support records |
|
|
135
|
-
|
|
136
|
-
Cite these in the design brief by name + Mx codes:
|
|
137
|
-
`Linear issue detail (M1, M4, M7)`.
|
|
138
|
-
|
|
139
|
-
## 10. Anti-patterns
|
|
140
|
-
|
|
141
|
-
| Anti-pattern | Use instead |
|
|
142
|
-
|--------------|-------------|
|
|
143
|
-
| `<h1>` + `PageHeader title` + breadcrumb leaf all repeating the name | One carrier: `PageHeader title` + ancestors-only breadcrumb (P2) |
|
|
144
|
-
| "Back to <parent>" button alongside the breadcrumb | Breadcrumb only (P1) |
|
|
145
|
-
| Two filled CTAs in the header ("Save" + "Submit") | One filled, others outline (P3) |
|
|
146
|
-
| Status only in detail (hidden on list / breadcrumb count) | Status visible everywhere the record appears (M4) |
|
|
147
|
-
| Status communicated by color only | Color + icon + label (`ListHubStatusBadge`) |
|
|
148
|
-
| Full-width section tab bar | `Tabs` `w-fit` (`exxat-tabs-chrome.mdc`) |
|
|
149
|
-
| Centered modal for "Edit name" | Inline edit (M5) or `Sheet` (M3) |
|
|
150
|
-
| Spinner overlay for initial load | `Skeleton` matched to IA (M9) |
|
|
151
|
-
| Activity timeline buried in a tab nobody opens | Show inline below cards, or remove it |
|
|
152
|
-
| New shared "ProfileHero" component invented per entity | Compose: `PageHeader` + identity-row primitives (P8) |
|
|
153
|
-
| `toast()` on save | Inline button label change + `LocalBanner` if persistent |
|
|
154
|
-
|
|
155
|
-
## 11. Quick checklist (post-build audit)
|
|
156
|
-
|
|
157
|
-
- [ ] Breadcrumb shows ancestors + title; no duplicate name.
|
|
158
|
-
- [ ] No body "Back to <parent>" button.
|
|
159
|
-
- [ ] One H1.
|
|
160
|
-
- [ ] Status row visible without scrolling.
|
|
161
|
-
- [ ] One filled primary action; overflow has the rest.
|
|
162
|
-
- [ ] 2-col card grid for fields (or tabs if ≥ 4 sections / 20+ fields).
|
|
163
|
-
- [ ] `Skeleton` matches the IA on load; empty state designed for "not found".
|
|
164
|
-
- [ ] Tab order: breadcrumb → identity → primary → overflow → fields.
|
|
165
|
-
- [ ] All status chips use `ListHubStatusBadge` + `lib/list-status-badges.ts`.
|
|
166
|
-
- [ ] No `toast()`, no Vaul, no pixel-copy of legacy.
|
|
167
|
-
|
|
168
|
-
## 12. Reference
|
|
169
|
-
|
|
170
|
-
- [`../../../.cursor/skills/exxat-senior-ux/SKILL.md`](../../../.cursor/skills/exxat-senior-ux/SKILL.md)
|
|
171
|
-
- [`../../../.cursor/rules/exxat-ux-discovery-protocol.mdc`](../../../.cursor/rules/exxat-ux-discovery-protocol.mdc)
|
|
172
|
-
- [`../../../.cursor/rules/exxat-ux-principles.mdc`](../../../.cursor/rules/exxat-ux-principles.mdc)
|
|
173
|
-
- [`../../../.cursor/rules/exxat-breadcrumbs-no-back.mdc`](../../../.cursor/rules/exxat-breadcrumbs-no-back.mdc)
|
|
174
|
-
- [`../../../.cursor/rules/exxat-tabs-chrome.mdc`](../../../.cursor/rules/exxat-tabs-chrome.mdc)
|
|
175
|
-
- [`../modern-saas-patterns.md`](../modern-saas-patterns.md)
|
|
176
|
-
- [`../blueprints/page-header.md`](../blueprints/page-header.md)
|
|
177
|
-
- [`../component-selection-guide.md`](../component-selection-guide.md) §1
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# KPI flat band (`KeyMetrics` `variant="flat"`)
|
|
2
|
-
|
|
3
|
-
> **Component:** `components/key-metrics.tsx` — **`flatMetricsHairlineClass`**, **`flatBandStyle`**.
|
|
4
|
-
> **Tokens:** `app/globals.css` — `--key-metrics-flat-*`.
|
|
5
|
-
> **Cursor:** `.cursor/rules/exxat-kpi-flat-band.mdc` · `.cursor/skills/exxat-kpi-flat-band/SKILL.md`
|
|
6
|
-
> **Related:** `docs/kpi-strip-max-four-pattern.md`, `docs/kpi-trend-pattern.md`
|
|
7
|
-
|
|
8
|
-
## Intent
|
|
9
|
-
|
|
10
|
-
List hubs and the main dashboard mix view use **`KeyMetrics variant="flat"`** as a **metrics strip without a surface**: users see KPI copy and deltas on the **page canvas**, with a **brand-colored glow** under the band only. This is **not** a card, tinted panel, or `gap-px` grid fill.
|
|
11
|
-
|
|
12
|
-
## MUST
|
|
13
|
-
|
|
14
|
-
1. **No band surface** — The `<section>` background is **only** `var(--key-metrics-flat-band-radial)`. **Do not** stack `--key-metrics-flat-band-linear`, opaque gradients, or `box-shadow` fills that read as a grey/lavender box.
|
|
15
|
-
2. **Transparent cells** — `metricsCellSurfaceClassName` is **`bg-transparent`** for `variant="flat"`. **Do not** use `bg-background`, `bg-card`, or `gap-px` + `bg-border` / `bg-foreground/*` on the grid (that paints tile surfaces).
|
|
16
|
-
3. **Hairlines = borders only** — Use **`flatMetricsHairlineClass(itemCount, metricsHalfWidthLayout)`** in `key-metrics.tsx`:
|
|
17
|
-
- **2 tiles:** `border-r` on the first cell only.
|
|
18
|
-
- **4 tiles, wide strip (default):** `border-r` on cells 1–3 (verticals between all columns); **no** horizontal rule.
|
|
19
|
-
- **4 tiles, narrow `@container` (< 30rem, 2×2 grid):** odd-column `border-r` + `border-b` on the top row only (via `@[max-width:29.99rem]` overrides).
|
|
20
|
-
4. **Divider color (OKLCH)** — `--key-metrics-flat-divider: color-mix(in oklch, var(--sidebar-border) 55%, transparent)`; apply on children with `[&>*]:border-[color:var(--key-metrics-flat-divider)]`. Dividers follow **active product** hue (`--sidebar-border`), not neutral grey alone.
|
|
21
|
-
5. **Glow (OKLCH)** — Radial stops use `color-mix(in oklch, var(--brand-color) …%, transparent)` so **Exxat One / Prism / Assessment / `theme-custom`** each tint correctly. **Do not** hardcode rose/indigo literals on theme blocks unless documenting a one-off.
|
|
22
|
-
6. **List page usage** — Prefer **`showHeader={false}`**, **`metricsSingleRow`** when four KPIs share one row; pass **`insight`** only when the insight rail is product-required (same row uses `lg:grid-cols-[3fr_2fr]`).
|
|
23
|
-
7. **Cap at four tiles** — See **`docs/kpi-strip-max-four-pattern.md`**.
|
|
24
|
-
|
|
25
|
-
## MUST NOT
|
|
26
|
-
|
|
27
|
-
- Add **`--key-metrics-flat-band-linear`** back into `flatBandStyle` or hub inline styles (e.g. library hub hero).
|
|
28
|
-
- Use **`variant="card"`** on **`ListPageTemplate`** metrics when the design calls for a **flat strip** on the page background.
|
|
29
|
-
- Duplicate KPI numbers in ad-hoc **`Card`** grids on the same hub.
|
|
30
|
-
- Set **`variant="mutedSuffix"`** on product wordmarks to grey out the **suffix** in dark mode — suffix stays **Exxat pink** (`wordmarkColor`); see **`lib/product-brand.ts`**.
|
|
31
|
-
|
|
32
|
-
## Tokens (`app/globals.css`)
|
|
33
|
-
|
|
34
|
-
| Token | Role |
|
|
35
|
-
|--------|------|
|
|
36
|
-
| `--key-metrics-flat-band-radial` | Bottom brand glow (only layer on flat `<section>`) |
|
|
37
|
-
| `--key-metrics-flat-band-shadow` | **`none`** for flat band (no faux surface lift) |
|
|
38
|
-
| `--key-metrics-flat-cell-bg` | **`transparent`** |
|
|
39
|
-
| `--key-metrics-flat-divider` | OKLCH hairline between cells |
|
|
40
|
-
|
|
41
|
-
Dark mode (`.dark`): same rules — transparent cells, radial glow only, no linear fill to `--background`.
|
|
42
|
-
|
|
43
|
-
## Reference implementations
|
|
44
|
-
|
|
45
|
-
- `components/library-client.tsx` — `KeyMetrics variant="flat" metricsSingleRow`
|
|
46
|
-
- `components/dashboard-tabs.tsx` — mix view flat band + insight
|
|
47
|
-
- `components/library-hub-client.tsx`, `columns-client.tsx`, `tokens-themes-client.tsx` — list hub metrics slot
|
|
48
|
-
|
|
49
|
-
## Insight rail (flat + side-by-side)
|
|
50
|
-
|
|
51
|
-
When **`insight`** is shown beside KPIs, the insight **`Card`** may keep its own surface; the **KPI grid** stays transparent. **Do not** add `lg:border-l` on the insight column for flat band — the insight card ring is the separator (`key-metrics.tsx`).
|
|
52
|
-
|
|
53
|
-
## See also
|
|
54
|
-
|
|
55
|
-
- **`docs/kpi-strip-max-four-pattern.md`**
|
|
56
|
-
- **`docs/kpi-trend-pattern.md`**
|
|
57
|
-
- **`docs/shell-surface-elevation-pattern.md`** — sidebar / secondary panel / page stack
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# KPI strip — maximum four tiles
|
|
2
|
-
|
|
3
|
-
> **Code:** `lib/dashboard-layout-merge.ts` — **`KEY_METRICS_KPI_COUNT_MIN`**, **`KEY_METRICS_KPI_COUNT_MAX`** (4), **`clampKeyMetricsKpiCount`**. **Component:** `KeyMetrics` in `components/key-metrics.tsx`.
|
|
4
|
-
|
|
5
|
-
## Rule
|
|
6
|
-
|
|
7
|
-
On **primary list hubs** (`ListPageTemplate` metrics slot) and on **dashboard “key metrics” cards** (Data tab chart bundles), **show at most four** `MetricItem` tiles at once.
|
|
8
|
-
|
|
9
|
-
## Why four
|
|
10
|
-
|
|
11
|
-
- **Scanning** — More than four headline numbers compete; users miss deltas and period context.
|
|
12
|
-
- **Layout** — `KeyMetrics` wraps to multiple rows; four keeps one or two clean rows on common breakpoints (including **`metricsHalfWidthLayout`** on span-1 cards).
|
|
13
|
-
- **Persistence** — Dashboard layout already stores **`keyMetricsKpiCount`** in **`1…4`**; list-page KPI helpers should **not** return a fifth tile expecting it to display.
|
|
14
|
-
|
|
15
|
-
## Implementation
|
|
16
|
-
|
|
17
|
-
1. **KPI builders** (`lib/mock/*-kpi.ts`) — Return **≤ 4** items, or **`.slice(0, 4)`** after prioritizing (hero total + top three drivers). Merge extras into **`MetricInsight`** copy instead of a fifth tile when possible.
|
|
18
|
-
2. **Dashboard canvas** — Never raise **`KEY_METRICS_KPI_COUNT_MAX`**; use **`clampKeyMetricsKpiCount`** when reading saved JSON.
|
|
19
|
-
3. **Full-page dashboards** — If more summaries are needed, add **sections** (charts, tables, secondary cards), not a fifth KPI in the same strip.
|
|
20
|
-
|
|
21
|
-
## MUST NOT
|
|
22
|
-
|
|
23
|
-
- Ship **five+** `MetricItem` entries in a single **`KeyMetrics`** band meant as the **primary** KPI row for a hub or the **key-metrics** dashboard card.
|
|
24
|
-
- Duplicate the same metric as two tiles to pad count — prefer **insight rail** or **`MetricInsight`**.
|
|
25
|
-
|
|
26
|
-
## See also
|
|
27
|
-
|
|
28
|
-
- **`docs/kpi-trend-pattern.md`** — deltas, arrows, **`trendPolarity`**.
|
|
29
|
-
- **`docs/kpi-flat-band-pattern.md`** — **`variant="flat"`** presentation (orthogonal to tile count).
|
|
30
|
-
- **`.cursor/rules/exxat-kpi-max-four.mdc`**, **`.cursor/skills/exxat-kpi-max-four/SKILL.md`**
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# KPI trend arrows and deltas (`KeyMetrics`)
|
|
2
|
-
|
|
3
|
-
> **Handbook:** [`AGENTS.md`](../AGENTS.md) (mock KPI helpers, `KeyMetrics`). **Component:** [`components/key-metrics.tsx`](../components/key-metrics.tsx). **Cursor:** [`.cursor/rules/exxat-kpi-trends.mdc`](../../.cursor/rules/exxat-kpi-trends.mdc). **Skill:** [`.cursor/skills/exxat-kpi-trends/SKILL.md`](../../.cursor/skills/exxat-kpi-trends/SKILL.md).
|
|
4
|
-
|
|
5
|
-
## Goals
|
|
6
|
-
|
|
7
|
-
1. **Contextual** — The **label**, **value format** (count, %, currency, days), and **comparison period** (e.g. “vs last week”) must read as one story. Do not paste a generic “+12%” without tying it to what moved.
|
|
8
|
-
2. **Honest direction** — **`trend`** (`up` | `down` | `neutral`) always matches the **signed change** in the underlying metric so the **arrow** reflects reality.
|
|
9
|
-
3. **Correct sentiment** — **`trendPolarity`** decides whether “up” is **good news** (tint + assistive copy), **bad news**, or **informational** (muted — direction only).
|
|
10
|
-
|
|
11
|
-
## `MetricItem` fields
|
|
12
|
-
|
|
13
|
-
| Field | Role |
|
|
14
|
-
| --- | --- |
|
|
15
|
-
| `value` | Current bucket total or rate (formatted string or number). |
|
|
16
|
-
| `delta` | **Count change for the trend chip**, e.g. `+5`, `-3`, `+12%`. Pass `""` (or `0`) when there is no comparison this period — the chip is then **hidden**, not rendered as `—`. **Never** put captions or labels like `"left + right"` here. |
|
|
17
|
-
| `description` | Optional **caption** rendered **below** the value + trend row (muted, small). Use for *what* moved or *how* the value breaks down — `"left + right"`, `"vs last week"`, `"across 4 sites"`, `"scheduled for removal"`. |
|
|
18
|
-
| `trend` | **Visual direction** of the delta: more → `up`, less → `down`, flat / N/A → `neutral`. Combined with an empty `delta`, `neutral` collapses the chip. |
|
|
19
|
-
| `trendPolarity` | Optional. **`higher_is_better`** (default) \| **`lower_is_better`** \| **`informational`**. |
|
|
20
|
-
|
|
21
|
-
### Layout
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
┌─────────────────────────────────────────────┐
|
|
25
|
-
│ Pinned columns │ ← label
|
|
26
|
-
│ 2 ↑ +1 │ ← value + (delta in chip)
|
|
27
|
-
│ left + right │ ← description (caption)
|
|
28
|
-
└─────────────────────────────────────────────┘
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
The **only** thing next to the arrow is the **count**. Prose lives **below** the value as a description. When there is no direction *and* no count, the chip is **suppressed** entirely (no `—` placeholder).
|
|
32
|
-
|
|
33
|
-
## Polarity cheat sheet
|
|
34
|
-
|
|
35
|
-
| `trendPolarity` | Use when | Up arrow tint | Down arrow tint |
|
|
36
|
-
| --- | --- | --- | --- |
|
|
37
|
-
| **`higher_is_better`** (default) | Revenue, pass rate, completions, enrolled count, positive CSAT | Favorable (brand / chart positive token) | Unfavorable (destructive) |
|
|
38
|
-
| **`lower_is_better`** | Error rate, overdue tasks, **low PBI / item quality flags**, time-on-task when minimizing, spend when cutting cost | Unfavorable | Favorable |
|
|
39
|
-
| **`informational`** | Library size, mix %, neutral volume | Muted | Muted |
|
|
40
|
-
|
|
41
|
-
**Psychometrics example:** Point-biserial (PBI) **dropping** usually helps discrimination — often good (`lower_is_better` on a *“low quality” count* is clearer: **count of items below a review threshold** rising → `trend: "up"` + `trendPolarity: "lower_is_better"` → arrow up with **unfavorable** tint).
|
|
42
|
-
|
|
43
|
-
## Accessibility
|
|
44
|
-
|
|
45
|
-
- **Never colour alone:** `KeyMetrics` keeps **icon + numeric delta**; `aria-label` on the chip uses **`metricTrendAriaQualifier`** (e.g. “increased, unfavorable +1”).
|
|
46
|
-
- **Decorative icons** stay `aria-hidden`; meaning lives in the chip’s **`aria-label`** and visible delta text.
|
|
47
|
-
|
|
48
|
-
## Anti-patterns
|
|
49
|
-
|
|
50
|
-
- Forcing **`trend: "up"`** green because “up feels good” when the metric is **defects** or **flags** — set **`lower_is_better`** instead.
|
|
51
|
-
- Hiding a worsening metric by flipping the arrow without changing **`trend`** — arrows must match the data.
|
|
52
|
-
- Using **`informational`** for KPIs that **do** have an agreed quality bar — pick a polarity instead.
|
|
53
|
-
- Rendering an **empty `—`** chip just for layout symmetry. The component already hides the chip when there is no direction *and* no count; leave `delta: ""` + `trend: "neutral"` and use `description` for the supporting caption.
|
|
54
|
-
- Putting captions like **`"left + right"`**, **`"hidden"`**, **`"shown"`**, **`"vs last week"`** in **`delta`**. Those are not deltas. Use **`description`**.
|
|
55
|
-
|
|
56
|
-
## Related surfaces
|
|
57
|
-
|
|
58
|
-
- **`ChartCard`** `miniMetrics` / `kpi-chart` variant — optional **`trendPolarity`** on each mini metric; uses the same **`metricTrendTone`** helper from `key-metrics.tsx`.
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Large dataset strategy
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Large dataset strategy
|
|
6
|
-
|
|
7
|
-
How the Exxat DS table stack scales as record counts grow — what works today, when to
|
|
8
|
-
turn pagination on, and what to add when the dataset outgrows the browser. Cross-linked
|
|
9
|
-
from [`apps/web/AGENTS.md`](../AGENTS.md) and [`data-views-pattern.md`](./data-views-pattern.md).
|
|
10
|
-
|
|
11
|
-
## TL;DR
|
|
12
|
-
|
|
13
|
-
| Row count | What to do |
|
|
14
|
-
|---|---|
|
|
15
|
-
| **≤ 200** | Default `HubTable` setup. No pagination. Filter / sort all in memory. |
|
|
16
|
-
| **200 – 5K** | Turn pagination on (`pagination={true}` on `HubTable`). Default page size 10–25. List + board views auto-virtualize at 100 rows via `DataRowList`. |
|
|
17
|
-
| **5K – 50K** | Stay client-side with pagination, but consider adding `@tanstack/react-virtual` to the `DataTable` `<tbody>` (follow-up; see below). Filtering all rows in memory still works — `useTableState` is sub-100ms at 50K on a typical laptop. |
|
|
18
|
-
| **> 50K** | Switch to **server mode**: lift filters / sort / page out of `useTableState` via `paginationOverride` and fetch one page at a time. The hub composition (`ListPageTemplate` + `HubTable` + Properties drawer) does not change. |
|
|
19
|
-
|
|
20
|
-
## Today (client mode, in-memory)
|
|
21
|
-
|
|
22
|
-
The default `HubTable` rendering path is fully client-side:
|
|
23
|
-
|
|
24
|
-
```mermaid
|
|
25
|
-
flowchart LR
|
|
26
|
-
Mock["lib/mock/* (or API on first paint)"] --> Rows["rows: TRow[] (full dataset)"]
|
|
27
|
-
Rows --> UTS["useTableState(rows, columns, sort, paginationOverride?)"]
|
|
28
|
-
UTS --> FRows["state.rows (filtered + sorted)"]
|
|
29
|
-
UTS --> PRows["state.pagedRows (sliced when pagination is on)"]
|
|
30
|
-
FRows --> DT["DataTable body (renders every row in pagedRows)"]
|
|
31
|
-
FRows --> DRL["DataRowList (auto-virtualizes at 100 rows)"]
|
|
32
|
-
FRows --> Board["ListPageBoardTemplate (virtualizes per-column)"]
|
|
33
|
-
FRows --> Dash["Dashboard charts"]
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Key properties:
|
|
37
|
-
|
|
38
|
-
1. **One row bag, every view.** Table / list / board / dashboard / folder / panel all read
|
|
39
|
-
`tableState.rows` (already filtered + sorted). No parallel arrays — see
|
|
40
|
-
[`.cursor/rules/exxat-centralized-list-dataset.mdc`](../../.cursor/rules/exxat-centralized-list-dataset.mdc).
|
|
41
|
-
2. **`useTableState` is the only filter pass.** Every keystroke in the toolbar search or any
|
|
42
|
-
filter-chip change re-runs the predicate set across the full input array. This is
|
|
43
|
-
in-memory `Array.filter` / `Array.sort` — fast enough to be invisible up to ~50K rows on
|
|
44
|
-
typical hardware.
|
|
45
|
-
3. **Table grid does NOT virtualize today.** When pagination is off, `DataTable` renders
|
|
46
|
-
every row in `state.rows` as a `<tr>`. With pagination on, it only renders the current
|
|
47
|
-
page — typically 10–25 rows — so DOM cost is constant regardless of the dataset size.
|
|
48
|
-
4. **List + board views DO virtualize.** `DataRowList` uses `@tanstack/react-virtual` with
|
|
49
|
-
a `virtualizeThreshold` (default 100) so 5K rows in the **list** tab paint instantly
|
|
50
|
-
even without pagination. `ListPageBoardTemplate` slices per column with the same
|
|
51
|
-
threshold.
|
|
52
|
-
|
|
53
|
-
## When to turn on pagination
|
|
54
|
-
|
|
55
|
-
`HubTable` accepts a `pagination={true}` prop that wires:
|
|
56
|
-
|
|
57
|
-
- The Properties drawer's **Show pagination** toggle.
|
|
58
|
-
- `CountSyncer` so filter changes reset to page 1.
|
|
59
|
-
- `PaginationBar` glued to the bottom of the table card (sticky at viewport bottom on
|
|
60
|
-
overflow).
|
|
61
|
-
|
|
62
|
-
```tsx
|
|
63
|
-
<HubTable
|
|
64
|
-
rows={items}
|
|
65
|
-
columns={columns}
|
|
66
|
-
pagination
|
|
67
|
-
paginationInitialPageSize={25}
|
|
68
|
-
paginationPageSizeOptions={[10, 25, 50, 100]}
|
|
69
|
-
// …
|
|
70
|
-
/>
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Rule of thumb: **once the table no longer fits on one screen, enable pagination.** That's
|
|
74
|
-
typically around 100 rows for product grids with avatars / two-line cells. Below that,
|
|
75
|
-
scroll-everything feels lighter than chrome.
|
|
76
|
-
|
|
77
|
-
## Beyond 50K — server mode
|
|
78
|
-
|
|
79
|
-
`useTableState` accepts a `paginationOverride: { page, pageSize }` so the hub owner can
|
|
80
|
-
lift page state out of the table. Pair that with a `fetcher(page, pageSize, filters,
|
|
81
|
-
sort) => Promise<{ rows, total }>` and you have classic server-side pagination without
|
|
82
|
-
touching any DS primitive.
|
|
83
|
-
|
|
84
|
-
Skeleton (planned, not implemented today):
|
|
85
|
-
|
|
86
|
-
```tsx
|
|
87
|
-
function MyHubClient() {
|
|
88
|
-
const [page, setPage] = useState(1)
|
|
89
|
-
const [pageSize, setPageSize] = useState(25)
|
|
90
|
-
const [filters, setFilters] = useState<FilterStateShape>(EMPTY_FILTERS)
|
|
91
|
-
const [sort, setSort] = useState<SortState>(DEFAULT_SORT)
|
|
92
|
-
|
|
93
|
-
// Server fetches one page at a time, returns 25 rows + total count
|
|
94
|
-
const { data, isLoading } = useQuery({
|
|
95
|
-
queryKey: ["my-hub", page, pageSize, filters, sort],
|
|
96
|
-
queryFn: () => fetchMyHubPage({ page, pageSize, filters, sort }),
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<HubTable
|
|
101
|
-
rows={data?.rows ?? []}
|
|
102
|
-
paginationOverride={{ page, pageSize }}
|
|
103
|
-
// …filters / sort wired the same way
|
|
104
|
-
/>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
What stays the same:
|
|
110
|
-
|
|
111
|
-
- `HubTable` composition (toolbar + filter chips + Properties + view tabs).
|
|
112
|
-
- `ColumnDef` shape, including `filter:` blocks.
|
|
113
|
-
- The Properties drawer Display / Filter / Sort / Columns / Conditional rules panels.
|
|
114
|
-
|
|
115
|
-
What changes:
|
|
116
|
-
|
|
117
|
-
- The filter / sort / page state is **lifted** out of `useTableState`'s internal reducer
|
|
118
|
-
and into the hub client (so it can be threaded into the fetcher).
|
|
119
|
-
- A loading row state in `DataTable` (already supported via `emptyState`; we'd add a
|
|
120
|
-
skeleton variant when the data array is empty but a fetch is in-flight).
|
|
121
|
-
|
|
122
|
-
## Follow-up: row virtualization for `DataTable`
|
|
123
|
-
|
|
124
|
-
For "infinite scroll" dense grids (Notion / Airtable feel) without pagination, add
|
|
125
|
-
`@tanstack/react-virtual` to `DataTable`'s `<tbody>`:
|
|
126
|
-
|
|
127
|
-
- The package is already a dependency (used by `DataRowList`).
|
|
128
|
-
- The mechanics are very similar to `DataRowList` — measure visible viewport height,
|
|
129
|
-
render only the `<tr>` rows in the visible window, padding the head/tail with empty
|
|
130
|
-
rows of the right pixel height.
|
|
131
|
-
- Caveats: row pinning (left / right sticky columns) needs care; row-detail "expanded"
|
|
132
|
-
rows complicate height estimation; `groupable` rows would need flat indexing.
|
|
133
|
-
|
|
134
|
-
This is documented as a follow-up. Not on the critical path because pagination handles
|
|
135
|
-
the same scaling concern with simpler ergonomics.
|
|
136
|
-
|
|
137
|
-
## What we do NOT do
|
|
138
|
-
|
|
139
|
-
- **No infinite-scroll-by-default on the table grid.** Pagination is explicit, keyboard
|
|
140
|
-
reachable, and links well. Infinite scroll on a primary product grid hurts deep-link
|
|
141
|
-
reachability and keyboard navigation.
|
|
142
|
-
- **No per-cell async hydration.** Every cell render reads from the row prop synchronously.
|
|
143
|
-
Async cell content (e.g. user avatar from a separate endpoint) is a column-level
|
|
144
|
-
concern — the column's `cell:` renderer can `useQuery` for that data, but it should not
|
|
145
|
-
block the row from painting.
|
|
146
|
-
- **No client-side fetch waterfalls.** When the dataset moves to server mode, fetch the
|
|
147
|
-
page once with the filter / sort / page key. Don't fan out per-row fetches.
|
|
148
|
-
|
|
149
|
-
## See also
|
|
150
|
-
|
|
151
|
-
- [`exxat-data-tables.mdc`](../../.cursor/rules/exxat-data-tables.mdc) — `HubTable` is the
|
|
152
|
-
only product-data-list stack.
|
|
153
|
-
- [`exxat-centralized-list-dataset.mdc`](../../.cursor/rules/exxat-centralized-list-dataset.mdc)
|
|
154
|
-
— one `useTableState` row bag, every view.
|
|
155
|
-
- [`data-views-pattern.md`](./data-views-pattern.md) — connected-view architecture.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Library hub header — folder scope + Customize folder
|
|
2
|
-
|
|
3
|
-
**Audience:** Engineers extending the library library hub (`LibraryClient`, `LibraryPageHeader`, URL scope).
|
|
4
|
-
|
|
5
|
-
## Problem
|
|
6
|
-
|
|
7
|
-
The library uses **`ListPageTemplate`** with multiple **view tabs** (table, panel, tree, …). **`LibraryNewFolderSheet`** (customize mode) is also used inside **`LibraryTable`** for some views (e.g. panel columns). If **Customize folder** exists only there, users on **table** or other tabs **cannot** open the sheet from a consistent chrome entry point when the URL is scoped to a folder (`?scope=folder&folderId=…`).
|
|
8
|
-
|
|
9
|
-
## Pattern
|
|
10
|
-
|
|
11
|
-
1. **`LibraryPageHeader`** exposes optional **`onCustomizeFolder?: () => void`**. When **`navState.scope === "folder"`** and **`navState.folderId`** is set, the hub client passes a callback that opens customize mode for the matching **`LibraryFolder`**.
|
|
12
|
-
2. **`LibraryClient`** (or equivalent hub client) mounts **`LibraryNewFolderSheet`** **once** beside **`SecondaryPanelHubTemplate` / `ListPageTemplate`**, with local state for **`open`** and **`customizingFolder`**. Saving updates **`folders`** the same way as table-embedded customize flows.
|
|
13
|
-
3. The header **⋯ More** menu order stays aligned with **§4.7**: **Invite people** (when collaboration variant) → **Customize folder** (when folder-scoped) → **Export** → **Show / hide metric section** (when applicable).
|
|
14
|
-
|
|
15
|
-
## References
|
|
16
|
-
|
|
17
|
-
| Piece | Location |
|
|
18
|
-
|-------|-----------|
|
|
19
|
-
| Header prop + menu item | `components/library-page-header.tsx` |
|
|
20
|
-
| Client wiring + sheet | `components/library-client.tsx` |
|
|
21
|
-
| URL scope | `lib/library-nav.ts` (`parseLibraryNav`, `LibraryNavState`) |
|
|
22
|
-
| Sheet UI | `components/library-new-folder-sheet.tsx` |
|
|
23
|
-
|
|
24
|
-
**Cursor rule:** `.cursor/rules/exxat-library-hub-header.mdc`
|
|
25
|
-
**Handbook:** `AGENTS.md` §4.6 (folder-scoped hub chrome).
|