@exxatdesignux/ui 0.5.10 → 0.5.12
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 +37 -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 +13 -2
- 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 +92 -12
- 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/cursor-skills/exxat-senior-ux/SKILL.md +70 -17
- 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/dist/hooks/use-app-theme.d.ts +1 -1
- package/package.json +2 -2
- 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/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/app → template-vite/src/styles}/globals.css +6 -2
- 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/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/exxat-product-logo.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
|
@@ -19,9 +19,11 @@ description: >
|
|
|
19
19
|
## 1. Project Overview
|
|
20
20
|
|
|
21
21
|
- **Stack:** Next.js 16 (App Router), React, TypeScript, Tailwind CSS, shadcn/ui primitives, Font Awesome icons
|
|
22
|
-
- **App root:** `
|
|
23
|
-
- **Single source of truth:** `
|
|
22
|
+
- **App root:** `apps/web/app/(app)/` — route group that wraps all authenticated pages
|
|
23
|
+
- **Single source of truth:** `apps/web/AGENTS.md` for full prose explanations; this skill is the actionable summary
|
|
24
24
|
- **Companion skills (narrow topics):** `exxat-fontawesome-icons`, `exxat-mono-ids`, `exxat-primary-nav-secondary-panel`, `exxat-centralized-list-dataset`, `exxat-list-page-view-shells`, `exxat-dedicated-search-surfaces`, `exxat-accessibility`, `exxat-board-cards`, `exxat-collaboration-access` — live under `.cursor/skills/`; vetted copies ship with **`@exxatdesignux/ui`** in `consumer-extras/cursor-skills/` after **`pnpm --filter @exxatdesignux/ui vendor:consumer-extras`**.
|
|
25
|
+
- **Library folder-scoped header (rule + doc):** **`.cursor/rules/exxat-library-hub-header.mdc`** and **`docs/library-hub-header-pattern.md`** — pair with **`exxat-primary-nav-secondary-panel`** when URL **`scope=folder`** drives the hub title.
|
|
26
|
+
- **Consumer repos (npm install of `@exxatdesignux/ui`):** After a version bump, read **`node_modules/@exxatdesignux/ui/CHANGELOG.md`**, run **`npx --package=@exxatdesignux/ui@latest exxat-ui sync-extras`** so **`docs/exxat-ds/consumer-upgrade-checklist.md`** and Cursor skills match the tarball, and diff the host app against **`node_modules/@exxatdesignux/ui/template/`** for anything new to port (routes, re-exports, AGENTS). Use **`exxat-ui changelog`**, **`exxat-ui update`**, and **`exxat-ui doctor`** for CLI guidance.
|
|
25
27
|
|
|
26
28
|
---
|
|
27
29
|
|
|
@@ -87,16 +89,161 @@ To add a primary nav item, append to `NAV_PRIMARY`:
|
|
|
87
89
|
|
|
88
90
|
| Concern | Pattern |
|
|
89
91
|
|--------|---------|
|
|
90
|
-
| **Product (One / Prism)** | **`ExxatProductLogo`** (`components/exxat-product-logo.tsx`) for the header control and **`ProductSwitcher`** — **not** logo.dev rasters unless product explicitly changes that. |
|
|
92
|
+
| **Product (One / Prism)** | **`ExxatProductLogo`** (`components/exxat-product-logo.tsx`) for the header control and **`ProductSwitcher`** — **not** logo.dev rasters unless product explicitly changes that. The logo is now **generated from a config**, not hand-built SVG paths — see **§3.4** to add a new product. |
|
|
91
93
|
| **School/program menu width** | **`DropdownMenuContent`** defaults to **intrinsic width** (**`min-w-52 w-max max-w-[min(24rem,calc(100vw-2rem))]`** via **`DROPDOWN_MENU_CONTENT_SURFACE_CLASS`** in **`@exxatdesignux/ui/lib/dropdown-menu-surface`**) — pure CSS, no **`ResizeObserver`**. The **school / program** switcher still uses an explicit wider surface (**`!w-max min-w-72 max-w-[min(100vw-2rem,28rem)]`**) so dense rows stay readable. |
|
|
92
94
|
| **School/program copy** | **Do not truncate** school or program names in the switcher; wrap (**`break-words`**, **`whitespace-normal`**, **`items-start`** on multi-line rows). The selected-school summary shows **school name + current program**. |
|
|
93
95
|
| **Team switcher trigger** | **`SidebarMenuButton` `size="lg"`** uses **`h-12`** + **`overflow-hidden`**, which **clips** a second line (program). When the sidebar is **expanded** or **mobile**, add **`h-auto min-h-12`** and **`overflow-x-clip overflow-y-visible`**. On **icon rail**, hide label rows with **`group-data-[collapsible=icon]:hidden`** (tooltip still exposes the full string). Icon mode defaults **`size-8` + `p-2`** (~16px inner) **clips** school logos — override **`!size-9`**, **`!p-0`**, **`overflow-visible`**. Omit header **chevrons** next to logos if they look like stray chrome. |
|
|
94
96
|
| **Motion / Animate UI** | [Animate UI](https://animate-ui.com/docs) — open **copy-first** animated components (Motion + Tailwind). This repo uses **`motion/react`** + **`lib/motion-ui.ts`** presets; pull more animations from their registry into `components/` when needed. |
|
|
95
|
-
| **Nav items with children** |
|
|
97
|
+
| **Nav items with children** | See **§3.2** below for the full parent ↔ children pattern (collapsible vs popover, active-state rules, chevron + slide animation, reduced motion). |
|
|
96
98
|
| **Profile (mock)** | **`stockPortraitUrl()`** from **`lib/stock-portrait.ts`**; **`AvatarImage`** **`referrerPolicy="no-referrer"`** for external URLs. |
|
|
97
99
|
|
|
98
100
|
**Reference:** `components/app-sidebar.tsx`, `components/nav-user.tsx`, `components/product-switcher.tsx`.
|
|
99
101
|
|
|
102
|
+
### 3.2 Parent ↔ children nav (collapsible vs popover vs secondary panel)
|
|
103
|
+
|
|
104
|
+
A primary nav row that owns sub-routes has **three possible shapes** — pick exactly one per item:
|
|
105
|
+
|
|
106
|
+
| Shape | When to use | Where it lives |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| **A. Collapsible children** (e.g. Library → All / My / Favorites / Folders) | Small finite child list that benefits from inline browsing (≤ 40 items, no extra page chrome). | `NavLinkItem.children` rendered by **`CollapsibleNavItem`** in `app-sidebar.tsx`. |
|
|
109
|
+
| **B. Secondary panel** (separate nested rail) | Same nav row needs **scoped search / tree / metrics** alongside the hub content. | `NavLinkItem.secondaryPanel = "<id>"` + `PANELS[id]` — see companion skill `exxat-primary-nav-secondary-panel`. |
|
|
110
|
+
| **C. Both A + B on one row** (Library does this) | Most cases when a hub has a sub-list AND a rich rail. The sidebar still shows the collapsible children; clicking the parent route also opens the secondary panel via `useAutoPanel`. | Combine A + B; the active-state and animation rules in §3.2 still apply to the sidebar children. |
|
|
111
|
+
|
|
112
|
+
#### Active-state rules — **the single biggest mistake to avoid**
|
|
113
|
+
|
|
114
|
+
1. **Expanded sidebar (full rail):** parent stays **visually neutral** when a child is active — **never double-highlight**. `isCollapsibleParentMenuButtonActive` returns `false` if `anyChildActive` so the active child row carries `data-active` alone.
|
|
115
|
+
2. **Collapsed sidebar (icon rail):** the parent icon is the **only** affordance, so it lights up when **any child** route is active. Implementation: `iconRailActive = isAnyChildActive` is fed into `SidebarMenuButton.isActive` and selects `item.iconActive` (`fa-solid`) over `item.icon` (`fa-light`).
|
|
116
|
+
3. **Tooltip / aria copy** in icon mode names the parent (e.g. "Library — open subpages"); the **popover** content lists children with their own active state so users see which sub-route is selected.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// Inside CollapsibleNavItem
|
|
120
|
+
const isAnyChildActive = item.children?.some(c => isCollapsibleChildActive(...))
|
|
121
|
+
const parentMenuButtonActive = isCollapsibleParentMenuButtonActive(pathname, item) // false when anyChildActive
|
|
122
|
+
const iconRailActive = isAnyChildActive
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Chevron + content animation (shadcn / Radix collapsible)
|
|
126
|
+
|
|
127
|
+
The collapsible rotates a chevron and slides the children in/out, mirroring shadcn's `Accordion`/`Collapsible` motion. **Three pieces wired together:**
|
|
128
|
+
|
|
129
|
+
1. **`group/collapsible`** on the **`SidebarMenuItem`** that wraps the trigger — opts the chevron into `group-data-[state=open]/collapsible:rotate-90` (or whatever direction the icon needs). Without `group/collapsible` the chevron never rotates.
|
|
130
|
+
2. **`CollapsibleContent`** uses Radix's `--radix-collapsible-content-height` CSS var via the shared keyframes:
|
|
131
|
+
```tsx
|
|
132
|
+
<CollapsibleContent className="overflow-hidden
|
|
133
|
+
data-[state=open]:[animation:collapsible-down_200ms_ease-out]
|
|
134
|
+
data-[state=closed]:[animation:collapsible-up_200ms_ease-out]
|
|
135
|
+
motion-reduce:animate-none
|
|
136
|
+
group-data-[collapsible=icon]:hidden">
|
|
137
|
+
<SidebarMenuSub>...</SidebarMenuSub>
|
|
138
|
+
</CollapsibleContent>
|
|
139
|
+
```
|
|
140
|
+
`overflow-hidden` is **required** so the height clip is visible during the animation. `motion-reduce:animate-none` honours `prefers-reduced-motion` (WCAG 2.3.3).
|
|
141
|
+
3. **Keyframes in `app/globals.css`** (shared, reused by `Accordion` too):
|
|
142
|
+
```css
|
|
143
|
+
@keyframes collapsible-down { from { height: 0 } to { height: var(--radix-collapsible-content-height) } }
|
|
144
|
+
@keyframes collapsible-up { from { height: var(--radix-collapsible-content-height) } to { height: 0 } }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Icon-rail popover (collapsed sidebar)
|
|
148
|
+
|
|
149
|
+
When `state === "collapsed"` (or `isMobile === false` on a tablet icon rail), `CollapsibleNavItem` renders a **`Popover`** anchored to the parent icon, listing children as full rows. **Do not** pass `tooltip={…}` to a `SidebarMenuButton` that is the **direct** child of `CollapsibleTrigger asChild` — the tooltip wrapper inserts an extra `Tooltip` root and breaks Radix `Slot` (`React.Children.only`). Compose `Tooltip > TooltipTrigger > CollapsibleTrigger > SidebarMenuButton` without the `tooltip` prop, or use the popover branch only.
|
|
150
|
+
|
|
151
|
+
#### Hydration
|
|
152
|
+
|
|
153
|
+
`CollapsibleNavItem` is an isolated component with its own controlled `open` state initialised in `useEffect`. **Do not** pass `defaultOpen` based on pathname at render time — server and client resolve it differently and Radix throws a hydration mismatch.
|
|
154
|
+
|
|
155
|
+
#### Cap
|
|
156
|
+
|
|
157
|
+
The data shape supports any number of children, but the collapsible variant is rendered only when `childCount <= 40`. Beyond that, model the children as a **secondary panel** (shape B) so the user gets search + scroll.
|
|
158
|
+
|
|
159
|
+
**Reference:** `components/app-sidebar.tsx` (`CollapsibleNavItem`, `isCollapsibleParentMenuButtonActive`, `isCollapsibleChildActive`), `app/globals.css` (`@keyframes collapsible-down/up`), `lib/mock/navigation.tsx` (`NavLinkItem.children`).
|
|
160
|
+
|
|
161
|
+
### 3.3 Secondary panel auto-collapse on high zoom
|
|
162
|
+
|
|
163
|
+
`SecondaryPanelProvider` (`components/secondary-panel.tsx`) reads **`useSidebarReflowZoom()`** (browser zoom ≥ 200% **or** very short viewport — same WCAG 1.4.10 signal the primary sidebar uses) and **auto-collapses the nested rail to its icon variant on entering high zoom**. The user can re-expand once collapsed; the next zoom-out → zoom-in cycle re-collapses. `openPanel` also opens directly in compact mode when high zoom is active so freshly-navigated panels don't briefly flash expanded.
|
|
164
|
+
|
|
165
|
+
Any future secondary-panel-like rail should reuse `useSidebarReflowZoom` rather than inventing a parallel zoom hook.
|
|
166
|
+
|
|
167
|
+
**Reference:** `components/secondary-panel.tsx` (`SecondaryPanelProvider`), `hooks/use-sidebar-reflow-zoom.ts`.
|
|
168
|
+
|
|
169
|
+
### 3.4 Product wordmark + brand registry
|
|
170
|
+
|
|
171
|
+
The product logo ("**Exxat** *One*" / "**Exxat** *Prism*") is **generated from a `ProductBrandConfig`** in `lib/product-brand.ts`, not from hand-built SVG paths. The suffix word renders as real **Ivy Presto** italic text (`var(--font-heading)`, Adobe Fonts kit `wuk5wqn` preloaded in `app/layout.tsx`); the circular mark is the same Exxat "E" geometry, recolored from the brand's gradient.
|
|
172
|
+
|
|
173
|
+
**To add a new product:**
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// lib/my-product-brand.ts
|
|
177
|
+
import { defineProductBrand, registerProductBrand } from "@/lib/product-brand"
|
|
178
|
+
|
|
179
|
+
export const EXXAT_PULSE = defineProductBrand({
|
|
180
|
+
id: "exxat-pulse",
|
|
181
|
+
prefix: "Exxat", // optional, defaults to "Exxat"
|
|
182
|
+
suffix: "Pulse", // rendered in Ivy Presto italic
|
|
183
|
+
brandColor: "#00A8E8", // any CSS color — drives suffix text + mark fill
|
|
184
|
+
markGradient: ["#0083C7", "#3FC6FF"], // optional 2-stop linear gradient on the mark
|
|
185
|
+
markShadow: "#006FAA", // optional inner shadow behind the "E" cut-out
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
registerProductBrand(EXXAT_PULSE)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Then render anywhere via either the **generic** primitives or the **Exxat** convenience wrappers:
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { ProductWordmark, ProductMark, ProductLogo } from "@/components/product-wordmark"
|
|
195
|
+
import { ExxatProductLogo, ExxatProductMark } from "@/components/exxat-product-logo"
|
|
196
|
+
|
|
197
|
+
// Generic — works for any registered brand
|
|
198
|
+
<ProductLogo config={EXXAT_PULSE} className="h-7" variant="mutedSuffix" />
|
|
199
|
+
<ProductMark config={EXXAT_PULSE} className="size-7" />
|
|
200
|
+
<ProductWordmark config={EXXAT_PULSE} className="h-7" />
|
|
201
|
+
|
|
202
|
+
// Existing Exxat call-sites unchanged
|
|
203
|
+
<ExxatProductLogo product="exxat-one" variant="mutedSuffix" className="h-7" />
|
|
204
|
+
<ExxatProductMark product="exxat-prism" className="size-7" />
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Visual contract (so new brands look like real logos, not styled text):**
|
|
208
|
+
|
|
209
|
+
1. **Prefix** uses Inter `font-extrabold` (800) `tracking-tight` in deep slate (`#273441`) / soft grey on dark (`#A8B2BA`). Always.
|
|
210
|
+
2. **Suffix** — **official Exxat brand spec from Figma** — `var(--font-heading)` (IvyPresto Text), **upright `font-semibold` (600)** with `tracking-[-0.03em]` (Figma "letter spacing -3 %"), tinted with `brandColor`. **Not italic**, **not Bold/ExtraBold** — IvyPresto's Bodoni-lineage SemiBold already has the thick verticals that read as a logo, and pushing to 700/800 makes the letterforms visually heavier than the brand asset (`ExxatOne_WordmarkLogo_WithMinClearSpace.png`). Avoid `medium` (500) / `regular` (400) — those read as inline text, not as a wordmark.
|
|
211
|
+
3. **Size relationship** — the `ExxatProductLogo` outer span pins `text-base leading-none` (16 px inherited) so the wordmark's `text-[1.78em]` resolves to ~28 px font / ~20 px cap-height regardless of host surface (sidebar `text-sm`, dropdown `text-base`, etc.). The mark renders at **`h-full`** of the outer height — the original hand-built `viewBox="0 0 766 164"` already bakes breathing room around the circle artwork, so giving the mark the full outer height reproduces the brand asset's 1.56:1 mark-to-cap ratio (caps ≈ 20 px against a 32 px mark at `h-8`). Shrinking the mark (e.g. `h-[88%]`) makes it visually smaller than the wordmark span and inverts the intended hierarchy. Use **`h-8`** (32 px) as the default sidebar / dropdown / switcher height; `h-7` is too tight for the new wordmark scale and produces a sub-30 px mark.
|
|
212
|
+
4. **Cap-to-mark centring** — the wordmark adds `translate-y-[0.09em]` to compensate for the cap-midpoint vs em-box-midpoint offset (Inter / Ivy Presto put cap glyphs in the upper portion of the line box). Without this, `items-center` aligns the *spans* but not the *visible cap* and mark — the wordmark visually rides ~3 px above the mark. Keep the translate when forking.
|
|
213
|
+
5. **`variant="mutedSuffix"`** (sidebar / switcher) keeps the brand color in **light** mode and only tints to `--muted-foreground` in **dark** mode. Don't mute the suffix in light mode — it loses brand recognition.
|
|
214
|
+
6. **Mark** stays the canonical Exxat "E" geometry so existing pixel-aligned layouts (sidebar header avatar slot, switcher dropdown rows) keep working when you swap brands; only colors change.
|
|
215
|
+
7. **Host span** uses `overflow-visible` because the wordmark's `1.78em` line-box can overshoot the parent `h-X` by ~1 px — let it render, don't clip.
|
|
216
|
+
8. **Don't pass `object-*` classes** to the logo `className` — `ExxatProductLogo` is a `<span>`, not a replaced element, so `object-contain` / `object-left` are silently dropped. Use width / max-width constraints instead.
|
|
217
|
+
9. **`ProductMark` has no size default** — callers MUST set explicit dimensions (`size-7`, `h-full w-auto`, etc.). A `size-*` default would silently lose against a downstream `h-full / w-auto` whenever `tailwind-merge` failed to recognise the shorthand-to-pair equivalence, and the mark would render at the default size (28 px) instead of the parent height (32 px in h-8). `ExxatProductLogo` passes `h-full w-auto`; the icon-rail / collapsed-sidebar usages pass `size-7`. Keep the explicit size.
|
|
218
|
+
|
|
219
|
+
**Where to extend the registry:** brand configs live alongside `lib/product-brand.ts` for the two built-ins. Co-locate new product configs near their feature (e.g. `app/(app)/<product>/_lib/brand.ts`) and call `registerProductBrand` at module import time so `ProductSwitcher` / `getProductBrand(id)` resolve it without ordering issues.
|
|
220
|
+
|
|
221
|
+
**MUST NOT:** add new hand-traced SVG paths for an additional product. Author a `ProductBrandConfig` instead.
|
|
222
|
+
|
|
223
|
+
**Reference:** `lib/product-brand.ts`, `components/product-wordmark.tsx`, `components/exxat-product-logo.tsx`, `components/product-switcher.tsx`.
|
|
224
|
+
|
|
225
|
+
### 3.5 Appearance preview tiles (Theme / Contrast)
|
|
226
|
+
|
|
227
|
+
`components/settings-appearance-card.tsx` renders the Theme (Light / Dark / System) and Contrast (Normal / High / Windows / System) pickers using a shared **`ChromeIllustration`** SVG helper — a polished mini browser window (Mac-style traffic lights, sidebar with brand-tinted mark + 5 nav rows, header bar with search pill + avatar, KPI card + mini bar-chart card + list rows).
|
|
228
|
+
|
|
229
|
+
Two knobs to make new variants without forking the geometry:
|
|
230
|
+
|
|
231
|
+
- **`tokens: ChromeTokens`** — palette per labeled mode. Override individual fills via `{...CHROME_LIGHT, shellStroke: "..."}`. Don't reach for `var(--background)` — preview tiles must show their target mode regardless of the active theme so users can compare before committing.
|
|
232
|
+
- **`strokeBoost: number`** — multiplier applied to every border weight. `1.8` is the high-contrast value used by both **High** and **Windows** tiles.
|
|
233
|
+
|
|
234
|
+
For "System" variants, use the **`SplitSystemSvg`** helper. It renders `ChromeIllustration` twice in the **same** 96 × 56 viewBox, each clipped to a triangular half (top-left triangle = "light", bottom-right triangle = "dark") via SVG `<clipPath>` polygons. The result is **one window with a diagonal theme split** — the macOS / iOS "Auto" pattern — not two adjacent windows.
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
<SplitSystemSvg
|
|
238
|
+
light={{ tokens: CHROME_LIGHT, sidebar: split.light, sidebarMark: split.markLight }}
|
|
239
|
+
dark={{ tokens: CHROME_DARK, sidebar: split.dark, sidebarMark: split.markDark }}
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**MUST NOT** invent ad-hoc rect-stacks for new appearance options, or render two side-by-side mini-windows for a single "System" tile — extend `ChromeIllustration` or compose `SplitSystemSvg`.
|
|
244
|
+
|
|
245
|
+
**Reference:** `components/settings-appearance-card.tsx` (`ChromeIllustration`, `SplitSystemSvg`, `CHROME_LIGHT`, `CHROME_DARK`, `SPLIT_SIDEBAR`).
|
|
246
|
+
|
|
100
247
|
---
|
|
101
248
|
|
|
102
249
|
## 4. Primary Hub Pages — Mandatory Pattern
|
|
@@ -104,37 +251,41 @@ To add a primary nav item, append to `NAV_PRIMARY`:
|
|
|
104
251
|
Any **primary nav destination** that shows a list of records **must** use this composition (same as Placements / Team):
|
|
105
252
|
|
|
106
253
|
```
|
|
107
|
-
ListPageTemplate
|
|
254
|
+
ListPageTemplate (supportedViewTypes = FULL_HUB_SUPPORTED_VIEWS — seven views)
|
|
108
255
|
├── PageHeader (title, subtitle with count, primary CTA, ⋯ more menu)
|
|
109
256
|
├── KeyMetrics (flat variant, single row)
|
|
110
257
|
└── renderContent()
|
|
111
|
-
└──
|
|
258
|
+
└── HubTable + useTableState + TablePropertiesDrawer + renderers per view
|
|
112
259
|
```
|
|
113
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
|
+
|
|
114
263
|
**Reference implementations:**
|
|
115
|
-
- `components/library-
|
|
116
|
-
- `components/columns-showcase.tsx` —
|
|
117
|
-
- `components/tokens-themes-client.tsx` + `components/tokens-
|
|
118
|
-
- `components/
|
|
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
|
|
268
|
+
- `components/placements-client.tsx` + `components/placements-table.tsx` — Placements (most complete)
|
|
119
269
|
|
|
120
270
|
**Files to create for a new hub page `Foo`:**
|
|
121
271
|
| File | Purpose |
|
|
122
272
|
|------|---------|
|
|
123
273
|
| `lib/mock/foo.ts` | Mock data + TypeScript interface (12+ rows) |
|
|
124
|
-
| `lib/mock/foo-kpi.ts` | `fooKpiMetrics()`
|
|
274
|
+
| `lib/mock/foo-kpi.ts` | `fooKpiMetrics()` + `fooKpiInsight()` |
|
|
125
275
|
| `components/foo-page-header.tsx` | `PageHeader` + primary CTA + ⋯ menu |
|
|
126
|
-
| `components/foo-
|
|
127
|
-
| `components/foo-client.tsx` | `ListPageTemplate` orchestrator
|
|
276
|
+
| `components/foo-table.tsx` | `DataTable` + `useTableState` + `TablePropertiesDrawer` |
|
|
277
|
+
| `components/foo-client.tsx` | `ListPageTemplate` orchestrator |
|
|
128
278
|
| `app/(app)/foo/page.tsx` | Thin server component |
|
|
129
279
|
|
|
130
|
-
**Do not** ship a **nav-linked hub** as an **empty page** or a single “replace this later” paragraph. If the route appears in **`lib/mock/navigation.tsx`**, implement the full hub (mock rows, **`ListPageTemplate`**, connected views per **`
|
|
280
|
+
**Do not** ship a **nav-linked hub** as an **empty page** or a single “replace this later” paragraph. If the route appears in **`lib/mock/navigation.tsx`**, implement the full hub (mock rows, **`ListPageTemplate`**, connected views per **`apps/web/AGENTS.md` §4.1**) unless the product explicitly defines a non-data shell.
|
|
131
281
|
|
|
132
282
|
### Page vs drawer (actions)
|
|
133
283
|
|
|
134
284
|
- **Drawer / sheet** — Use when the user needs **the current page behind them** *and* a **quick view**, **quick actions**, or a **short step** (e.g. properties, export, glance at a row).
|
|
285
|
+
- **Dialog** — **Blocking** confirm/alert/short choice — **`docs/drawer-vs-dialog-pattern.md`**, **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**.
|
|
135
286
|
- **New page** — Use **otherwise**: **primary**, **long-form**, **multi-step**, or flows that need their **own URL** without the hub visible.
|
|
136
287
|
|
|
137
|
-
Align with **`
|
|
288
|
+
Align with **`apps/web/AGENTS.md` §6.4**, **`docs/data-views-pattern.md`**, **`docs/drawer-vs-dialog-pattern.md`**, **`.cursor/rules/exxat-page-vs-drawer.mdc`**, **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**.
|
|
138
289
|
|
|
139
290
|
---
|
|
140
291
|
|
|
@@ -150,7 +301,7 @@ Align with **`exxat-ds/AGENTS.md` §6.4**, **`docs/data-views-pattern.md`**, **`
|
|
|
150
301
|
| `ColumnDef` from `@/components/data-table/types` | Column type |
|
|
151
302
|
| `FilterFieldDef`, `FilterOperator`, `ConditionalRule` from `@/components/table-properties/types` | Filter types |
|
|
152
303
|
|
|
153
|
-
**Board (kanban) cards:** Use **`ListPageBoardCard`** and related parts from **`components/data-views/list-page-board-card.tsx`**; **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** from **`board-card-primitives.tsx`**. **List hub** status (Team, Compliance, Library, …): maps in **`lib/list-status-badges.ts`**; render with **`ListHubStatusBadge`** (**`surface="table"`** in table/list, **`surface="board"`** on cards); semantic tints **`LIST_HUB_STATUS_TINT_*`** for new domains; no **`uppercase`**. **Placements** uses **`StatusBadge`** in **`
|
|
304
|
+
**Board (kanban) cards:** Use **`ListPageBoardCard`** and related parts from **`components/data-views/list-page-board-card.tsx`**; **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** from **`board-card-primitives.tsx`**. **List hub** status (Team, Compliance, Library, …): maps in **`lib/list-status-badges.ts`**; render with **`ListHubStatusBadge`** (**`surface="table"`** in table/list, **`surface="board"`** on cards); semantic tints **`LIST_HUB_STATUS_TINT_*`** for new domains; no **`uppercase`**. **Placements** uses **`StatusBadge`** in **`placements-table-cells.tsx`** (wrapper over **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`**). **Full rules:** **`apps/web/AGENTS.md` §4.4**, **`.cursor/rules/exxat-board-cards.mdc`**, **`.cursor/skills/exxat-board-cards/SKILL.md`**.
|
|
154
305
|
|
|
155
306
|
**Minimum required features on any data list page:**
|
|
156
307
|
- Search (wire `searchable={displayOptions.showToolbarSearch}`)
|
|
@@ -192,6 +343,14 @@ Align with **`exxat-ds/AGENTS.md` §6.4**, **`docs/data-views-pattern.md`**, **`
|
|
|
192
343
|
|
|
193
344
|
**`MetricItem.trend`** must match the **signed change** (arrow direction = truth). **`trendPolarity`** (`higher_is_better` default, **`lower_is_better`**, **`informational`**) controls **tints** and **`aria-label`** — e.g. **low PBI / review flags** rising → `trend: "up"` + **`lower_is_better`** → unfavourable (red), not green. **Doc:** **`docs/kpi-trend-pattern.md`** · **Rule:** **`.cursor/rules/exxat-kpi-trends.mdc`** · **Skill:** **`.cursor/skills/exxat-kpi-trends/SKILL.md`**.
|
|
194
345
|
|
|
346
|
+
### 5.3 KPI count (max four)
|
|
347
|
+
|
|
348
|
+
**`ListPageTemplate`** metrics and **Data tab** key-metrics cards: **≤ 4** `MetricItem` — **`docs/kpi-strip-max-four-pattern.md`**, **`lib/dashboard-layout-merge.ts`**, **`.cursor/rules/exxat-kpi-max-four.mdc`**, **`.cursor/skills/exxat-kpi-max-four/SKILL.md`**.
|
|
349
|
+
|
|
350
|
+
### 5.4 Cards vs table rows
|
|
351
|
+
|
|
352
|
+
Dense comparable hub → **`DataTable`**. Boards / folders / visual browse → **`ListPageBoardCard`** + **`ListPageViewFrame`**. **Doc:** **`docs/card-vs-rows-pattern.md`** · **Rule:** **`.cursor/rules/exxat-card-vs-list-rows.mdc`**.
|
|
353
|
+
|
|
195
354
|
**DataTable must wrap in `<div className="pb-6">`.**
|
|
196
355
|
|
|
197
356
|
---
|
|
@@ -247,6 +406,8 @@ Use `PageHeader` from `@/components/page-header` for the content-area header (be
|
|
|
247
406
|
|
|
248
407
|
When a hub is **shared**, use **`PageHeader` `variant="collaboration"`**: **empty roster** → outline **Add collaborator**; **non-empty** → face rail (faces / **`+N`** open the invite sheet). **Invite people** also lives under the entity header **⋯ More** and opens **`InviteCollaboratorsDrawer`** via **`CollaborationAccessFlow`** when possible. Library access (Owner / Editor / Commenter / Viewer) comes from **`lib/collaborator-access.ts`**; directory tags (Faculty, Program coordinator, Director) use **`PageHeaderCollaborator.roles`**.
|
|
249
408
|
|
|
409
|
+
**Library library — folder URL scope:** When **`?scope=folder&folderId=`** applies, **⋯ More** must also offer **Customize folder** (**`LibraryPageHeader`** **`onCustomizeFolder`**) and the **`LibraryNewFolderSheet`** must be mounted on **`LibraryClient`** so it works on every **`ListPageTemplate`** view tab. **`.cursor/rules/exxat-library-hub-header.mdc`** · **`docs/library-hub-header-pattern.md`** (app: **`apps/web/docs/...`**).
|
|
410
|
+
|
|
250
411
|
**Handbook:** `apps/web/AGENTS.md` §4.7 · **Doc:** `docs/collaboration-access-pattern.md` · **Skill:** `.cursor/skills/exxat-collaboration-access/SKILL.md` · **Reference:** Library header + client.
|
|
251
412
|
|
|
252
413
|
---
|
|
@@ -300,7 +461,7 @@ Never install new packages or create parallel components. Always use what exists
|
|
|
300
461
|
| Color | CSS design tokens only — no hardcoded hex/rgb |
|
|
301
462
|
| Minimum font size | **`text-xs`** (11px at 16px root via `--text-xs`) or larger — never arbitrary classes below 11px (`AGENTS.md` §8.3) |
|
|
302
463
|
|
|
303
|
-
Before adding any component: search `components/ui/` first. Add a prop/variant to an existing component rather than creating a parallel one.
|
|
464
|
+
Before adding any component: search `components/ui/` first. Add a prop/variant to an existing component rather than creating a parallel one. **If nothing fits** and you need a **new shared primitive or large bespoke widget**, **ask the user** before new files — **`.cursor/rules/exxat-reuse-before-custom.mdc`** (unless the task already approved greenfield).
|
|
304
465
|
|
|
305
466
|
---
|
|
306
467
|
|
|
@@ -395,7 +556,7 @@ import { DropdownMenuItem, Shortcut } from "@/components/ui/dropdown-menu"
|
|
|
395
556
|
| Duplicate | ⌘/Ctrl + D |
|
|
396
557
|
| Review / Info | ⌘/Ctrl + I |
|
|
397
558
|
| Remove / Delete item | ⌘/Ctrl + ⌫ |
|
|
398
|
-
| Add view (1..n) |
|
|
559
|
+
| Add view (1..n) | **1..9** (plain digit; `dataListViewAddShortcut`) |
|
|
399
560
|
| **Submit a workflow** (Create, Save, Export, Apply) | **Enter** ⏎ — scoped to the open form/drawer/dialog |
|
|
400
561
|
| **Cancel / dismiss** a workflow | **Esc** (Radix Dialog/Sheet/AlertDialog already bind this) |
|
|
401
562
|
| **Advance a multi-step wizard** | ⌘/Ctrl + Enter (plain Enter must not submit mid-flow) |
|
|
@@ -450,7 +611,7 @@ Reference implementations: `new-placement-form.tsx` (Create placement = Enter on
|
|
|
450
611
|
- **Natural language / AI:** Product **SHOULD** show **quick results in the palette** when the response fits; use **Ask Leo** (**⌘⌥K**) for **longer or complex** answers.
|
|
451
612
|
- **Do not** treat the palette as a static link list only—leave room for inline AI results as they ship.
|
|
452
613
|
|
|
453
|
-
**Details:** `
|
|
614
|
+
**Details:** `apps/web/docs/command-menu-pattern.md`, **`apps/web/AGENTS.md` §7.1** (or `./` when the app folder is the workspace root).
|
|
454
615
|
|
|
455
616
|
---
|
|
456
617
|
|
|
@@ -461,7 +622,7 @@ Full checklist in `references/accessibility.md`. Summary of the most-violated ru
|
|
|
461
622
|
### Structure
|
|
462
623
|
- One `<main id="main-content" tabIndex={-1}>` per page
|
|
463
624
|
- One `<h1>` per page (via `PageHeader`) — `SiteHeader` title is NOT an h1
|
|
464
|
-
- `DialogTitle` / `SheetTitle`
|
|
625
|
+
- `DialogTitle` / `SheetTitle` always present (use `className="sr-only"` if visually hidden)
|
|
465
626
|
|
|
466
627
|
### ARIA roles
|
|
467
628
|
- `role="tablist"` → only `role="tab"` children. **Never** put buttons, menus, or other controls inside `tablist`
|
|
@@ -705,8 +866,8 @@ Copy and complete for every list/table/hub page:
|
|
|
705
866
|
- [ ] Sidebar item added to `lib/mock/navigation.tsx` with light/solid icon pair
|
|
706
867
|
- [ ] **Shell sidebar:** Product header uses **`ExxatProductLogo`**; school **`logoDevUrl`** + **`lib/logo-dev`**; team switcher menu **`!w-max`** (not trigger-width-only); expanded switcher **`h-auto min-h-12`** so school + program lines are not clipped; no **`CollapsibleTrigger` → `SidebarMenuButton` with `tooltip` prop**; child nav uses **popover** on icon rail per **§3.1**
|
|
707
868
|
- [ ] Hub pages: `ListPageTemplate` + `DataTable` + `useTableState` + `TablePropertiesDrawer`
|
|
708
|
-
- [ ] Board view: `ListPageBoardCard` shell + `ListHubStatusBadge` + `list-status-badges` when applicable (`
|
|
709
|
-
- [ ] New primary hubs: not placeholder-only — full template + data + views (`
|
|
869
|
+
- [ ] Board view: `ListPageBoardCard` shell + `ListHubStatusBadge` + `list-status-badges` when applicable (`apps/web/AGENTS.md` §4.4)
|
|
870
|
+
- [ ] New primary hubs: not placeholder-only — full template + data + views (`apps/web/AGENTS.md` §4.1)
|
|
710
871
|
- [ ] **§6.4:** Parent **context** + quick view/actions → drawer/sheet; primary or long flows → **new page** (`AGENTS.md`, `docs/data-views-pattern.md`)
|
|
711
872
|
- [ ] No raw `<table>` or `ui/table` for product data lists
|
|
712
873
|
- [ ] No double horizontal padding around `DataTable`
|
|
@@ -718,7 +879,7 @@ Copy and complete for every list/table/hub page:
|
|
|
718
879
|
- [ ] All icons: `aria-hidden="true"`; Ask Leo: `fa-duotone fa-solid fa-star-christmas text-brand`
|
|
719
880
|
- [ ] **Every icon that communicates info has a text alternative** — Case A adjacent label (preferred), Case B `role="img"` + `aria-label` + `Tooltip` (calendar-for-date, status dot, trend arrow, icon-only legend), Case C `aria-label` + `Tooltip` on icon-only buttons; target ≥ 24×24 px. See §12 *Icons that communicate information*.
|
|
720
881
|
- [ ] **`Kbd` inside a `Button` uses `variant="bare"`** (glue chords into one bare kbd); **`Kbd` inside `TooltipContent` uses the default tile** — see §11 Keyboard shortcuts
|
|
721
|
-
- [ ] `DialogTitle`/`SheetTitle
|
|
882
|
+
- [ ] `DialogTitle`/`SheetTitle` present on every overlay
|
|
722
883
|
- [ ] `role="tablist"` contains only tab-role children
|
|
723
884
|
- [ ] No new shadcn components, no hardcoded colors, no duplicate component abstractions
|
|
724
885
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — WCAG 2.1 AA, ARIA tablists, 24px targets, contrast; see AGENTS.md §8 and exxat-accessibility skill
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
appliesTo: [universal]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — accessibility (binding summary)
|
|
8
|
+
|
|
9
|
+
**Full checklist (ARIA, touch targets, color, sidebar badges, audit follow-ups):** repo **`.cursor/skills/exxat-accessibility/SKILL.md`**.
|
|
10
|
+
|
|
11
|
+
**Product rules in prose + checklist:** **`apps/web/AGENTS.md` §8**.
|
|
12
|
+
|
|
13
|
+
## Non‑negotiables
|
|
14
|
+
|
|
15
|
+
1. **Target:** **WCAG 2.1 Level AA** (2.2 where noted — e.g. target size).
|
|
16
|
+
2. **`role="tablist"`** — only **`role="tab"`** (or equivalent) as direct tab semantics. **MUST NOT** put `role="button"`, menus (`aria-haspopup`), or other controls **inside** the same `tablist` container.
|
|
17
|
+
3. **Composite view switchers** (tabs + per-tab menu + remove): use **`role="toolbar"`** + **`aria-label`**; **`aria-pressed`** on toggles — **MUST NOT** misuse `tab`/`tablist` for those controls.
|
|
18
|
+
4. **Touch targets (2.5.8):** interactive controls **≥ 24×24 CSS px** or **24px** spacing so hit areas do not overlap — use **`min-h-6 min-w-6` / `size-6`** for icon-only targets; avoid **`size-4`** as the sole target.
|
|
19
|
+
5. **Contrast:** normal text **≥ 4.5:1**; UI components / focus where required **≥ 3:1**; muted text on tinted surfaces use tokens against the correct surface (e.g. sidebar), not only `--background`.
|
|
20
|
+
6. **Minimum text size:** visible product copy **≥ 11px** — use **`text-xs`** or larger; **MUST NOT** use arbitrary Tailwind font sizes below that (see **`apps/web/AGENTS.md` §8.3**, **`apps/web/app/globals.css`** `--text-xs`).
|
|
21
|
+
7. **Dialogs / sheets:** must expose a **Title** (`DialogTitle` / `SheetTitle`); use **`sr-only`** if visually hidden (shadcn pattern).
|
|
22
|
+
8. **Format hints are persistent, not placeholders (SC 3.3.2 Labels or Instructions, 1.3.1).** Any field that expects a specific format — **date, time, phone, currency, ID pattern, GPA scale, URL, hours, unit-bearing numbers** — MUST show the format as **persistent helper text** via **`FormDescription`** (or the field's description slot). Placeholders disappear on focus and are unreliable for AT, so **MUST NOT** be the sole carrier of the format. Example: GPA → "Out of 4.0"; Date → "MM/DD/YYYY"; Phone → "+1 (555) 555-0100"; Student ID → "STU-YYYY-####". Pair with `inputMode`, `pattern`, or a picker primitive (e.g. `DatePickerField`) where applicable; never rely on a free-text date input.
|
|
23
|
+
9. **Every icon that communicates information MUST have a text alternative** — not just icon-only buttons. Three cases (SC 1.1.1 Non-text Content, 3.3.2 Labels or Instructions, 2.4.6 Headings and Labels):
|
|
24
|
+
|
|
25
|
+
- **A. Decorative icon next to text that already names it** (e.g. `<i class="fa-light fa-calendar-days" aria-hidden /> 12/14/2025 – 12/20/2025`) → icon MUST be **`aria-hidden`**; MUST NOT add `aria-label` (screen readers would announce the meaning twice). No tooltip needed.
|
|
26
|
+
- **B. Informational icon standing alone** — a calendar glyph used to mean "date range", clock for "updated at", pin for "site", graduation cap for "student", trending arrow for direction, status dot, icon-only chart legend — MUST pair **`role="img"` + `aria-label`** (or `aria-labelledby` on a wrapping element) with a visible **`Tooltip`** so sighted users who don't recognise the glyph learn the meaning. The icon wrapper MUST be keyboard-focusable (`tabIndex={0}` on the `span[role="img"]`) so the tooltip opens on focus.
|
|
27
|
+
- **C. Interactive icon-only button/link** (close `×`, chevron, overflow `⋯`, sort direction, filter chip dismiss, copy, Ask Leo toggle, row actions) → MUST pair **`aria-label`** on the `<button>` with a wrapping **`Tooltip`**. `aria-label` alone is NOT enough — sighted mouse users and keyboard users rely on the tooltip to discover what a bare icon does.
|
|
28
|
+
|
|
29
|
+
In all three cases, the inner `<i>` / `<svg>` MUST be `aria-hidden`; the accessible name lives on the wrapping element. Tooltip text MUST match the accessible name. Narrow exception: a chevron inside a labelled composite (`Select`, `Combobox`) where the parent control already names the whole thing. See **§8.6 (Case A/B/C)** in `AGENTS.md` and the accessibility skill.
|
|
30
|
+
10. **Keyboard shortcut hints inside buttons** MUST use **`<Kbd variant="bare">`** (no background, no border, inherits `currentColor` at 70%). The default `tile` variant is reserved for **tooltips** and **menu `shortcut=` slots**. Glue multi-key chords into one bare kbd (e.g. `<Kbd variant="bare">⌘⌥K</Kbd>`), not one tile per key. Reference: `Next` / `Back` buttons in `new-library-item-form.tsx`; see `.cursor/rules/exxat-kbd-shortcuts.mdc`.
|
|
31
|
+
|
|
32
|
+
After changing **views toolbar** or **tab** UIs, re-run **axe** (or equivalent) on **Placements** (or the affected page).
|
|
33
|
+
|
|
34
|
+
**Charts:** Keyboard exploration uses **`ChartFigure`**; selected data points should have **visible** focus feedback — see skill **§ Charts (keyboard exploration)** and **`AGENTS.md` §4.3** (`chart-keyboard-selection`).
|
|
35
|
+
|
|
36
|
+
## See also
|
|
37
|
+
|
|
38
|
+
- **`apps/web/AGENTS.md`** §8 — accessibility in project context.
|
|
39
|
+
- **`.cursor/rules/exxat-kbd-shortcuts.mdc`** — shortcuts paired with `Kbd` hints.
|
|
40
|
+
- **`apps/web/.cursor/rules/exxat-dashboard-view-charts.mdc`** — Data view chart keyboard parity.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — board (kanban) cards via ListPageBoardCard, badges, primitives. Auto-attaches when editing React board/list files; ask explicitly when designing kanban surfaces.
|
|
3
|
+
globs: apps/web/components/**/*.{tsx,ts}
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
appliesTo: [react]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Exxat DS — board cards
|
|
9
|
+
|
|
10
|
+
**Authoritative detail:** **`apps/web/AGENTS.md` §4.4** and **`.cursor/skills/exxat-board-cards/SKILL.md`**.
|
|
11
|
+
|
|
12
|
+
## MUST
|
|
13
|
+
|
|
14
|
+
1. **Shell** — Use **`ListPageBoardCard`** from **`components/data-views/list-page-board-card.tsx`** for product board cards (same **`Card` `size="sm"`** treatment as Placements).
|
|
15
|
+
2. **Hierarchy** — **Title** (`ListPageBoardCardTitleRow`) → optional **avatar** (`ListPageBoardCardAvatar` on `trailing`) → **status row** (`ListPageBoardCardBadgeRow` + **`ListHubStatusBadge`** **`surface="board"`**) when the entity has status → **body** (`ListPageBoardCardBody`) with **`BoardCardTwoLineBlock`** / **`BoardCardIconRow`** (**`components/data-views/board-card-primitives.tsx`**).
|
|
16
|
+
3. **Status** — Use **`ListHubStatusBadge`** with **`surface="table"`** in grid / list rows and **`surface="board"`** on kanban cards. Generic semantic tints live in **`lib/list-status-badges.ts`** (**`LIST_HUB_STATUS_TINT_SUCCESS / WARNING / INFO / NEUTRAL / DANGER`**); compose per-domain label / icon maps next to the entity's mock data. Reference: **`library-board-view.tsx`** + **`lib/mock/library.ts`**. **MUST NOT** use **`uppercase`** on those chips.
|
|
17
|
+
4. **Simple column boards** — **`ListPageBoardTemplate`** + **`renderCard`**; compose **`ListPageBoardCard`** inside **`renderCard`**.
|
|
18
|
+
|
|
19
|
+
## MUST NOT
|
|
20
|
+
|
|
21
|
+
- Duplicate **`ListPageBoardCard`** with ad-hoc **`<button>`** + border/padding classes for the same hub pattern.
|
|
22
|
+
- Show **status** only as plain text in the **body** when the hub uses **status** elsewhere — use the **badge row** + shared maps.
|
|
23
|
+
|
|
24
|
+
## See also
|
|
25
|
+
|
|
26
|
+
- **`apps/web/AGENTS.md` §4.4**, **§13** checklist
|
|
27
|
+
- **`apps/web/docs/data-views-pattern.md`** — Board UI reuse
|
|
28
|
+
- **`lib/initials-from-name.ts`** — owner initials when mock has no `initials` field
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Do not duplicate parent navigation with a Back control when breadcrumbs exist
|
|
3
|
+
globs: apps/web/**/*.tsx
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
appliesTo: [react]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Exxat DS — breadcrumbs and back navigation
|
|
9
|
+
|
|
10
|
+
## MUST NOT
|
|
11
|
+
|
|
12
|
+
When a page uses **`SiteHeader`** with **`breadcrumbs`** (a visible trail such as Patterns → Library → current title), **do not** add a **“Back to …”** link or button in the page body that goes to the same parent as the breadcrumb segment. Breadcrumbs already provide hierarchy and one-click navigation up the tree.
|
|
13
|
+
|
|
14
|
+
## MAY
|
|
15
|
+
|
|
16
|
+
- Rely on **`SiteHeader`** breadcrumbs only for returning to parent routes.
|
|
17
|
+
- Use a **single** explicit back affordance on flows that **omit** breadcrumbs by design (e.g. full-screen step, modal route) where product copy requires it.
|
|
18
|
+
|
|
19
|
+
## See also
|
|
20
|
+
|
|
21
|
+
- `components/site-header.tsx`
|
|
22
|
+
- `components/templates/primary-page-template.tsx`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — when to use cards vs DataTable rows vs simple list rows.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
appliesTo: [universal]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — cards vs rows vs lists
|
|
8
|
+
|
|
9
|
+
## MUST
|
|
10
|
+
|
|
11
|
+
1. **Dense, comparable records (10+)** — **`DataTable`** + **`ListPageTemplate`** + **`useTableState`** for primary hubs (**`exxat-data-tables.mdc`**).
|
|
12
|
+
2. **Board / tiles / visual browse** — **`ListPageBoardCard`** (and related shells), **`ListPageViewFrame`** for non-table bodies (**`exxat-board-cards.mdc`**, **`exxat-list-page-view-shells.mdc`**).
|
|
13
|
+
3. **One dataset** — Cards and tables read the **same** **`tableState.rows`**; no forked mock arrays (**`exxat-centralized-list-dataset.mdc`**).
|
|
14
|
+
|
|
15
|
+
## MUST NOT
|
|
16
|
+
|
|
17
|
+
- Replace a **primary data hub grid** with a **card wall** when users need column sort, filter chips, and export parity.
|
|
18
|
+
- Introduce a **second table stack** for the same entity.
|
|
19
|
+
|
|
20
|
+
## See also
|
|
21
|
+
|
|
22
|
+
- **`docs/card-vs-rows-pattern.md`** · **`.cursor/skills/exxat-card-vs-list-rows/SKILL.md`**
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Single source of truth for hub rows, KPIs, table properties, detail views, and shared view chrome — dataset + presentation consistency across DataListViewType surfaces. Auto-attaches when editing list-hub React files.
|
|
3
|
+
globs: apps/web/{components,lib}/**/*.{tsx,ts}
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
appliesTo: [react]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Exxat DS — centralized list hub dataset + presentation
|
|
9
|
+
|
|
10
|
+
**Authoritative detail:** **`apps/web/AGENTS.md` §4.1** and **§4.5** (connected views + view shells), **`docs/data-views-pattern.md`**, **`.cursor/rules/exxat-list-page-connected-views.mdc`**, and **`.cursor/rules/exxat-list-page-view-shells.mdc`**. This rule tightens **where data lives** and **where reusable view chrome lives** so **table, list, board, dashboard, folder, panel, tree, and inspectors** never drift.
|
|
11
|
+
|
|
12
|
+
## Single dataset
|
|
13
|
+
|
|
14
|
+
1. **Rows** — One typed collection per entity (**`lib/mock/<entity>.ts`** until API wiring). **MUST NOT** maintain a second parallel array (e.g. “tree only” or “panel only”) with overlapping IDs unless it is a **derived view** (computed from the same source via selectors).
|
|
15
|
+
|
|
16
|
+
2. **`useTableState`** — One instance per tab table; **`tableState.rows`** is the **only** filtered/sorted row bag that **list / board / dashboard / folder / panel / tree** surfaces read. **MUST NOT** bypass filters by importing raw mock arrays into alternate views.
|
|
17
|
+
|
|
18
|
+
3. **Detail / inspector / finder / split-panel bodies** — Resolve the active record by **`id`** (or stable key) against **`tableState.rows`** (or the same upstream state that feeds **`useTableState`**). **MUST NOT** hydrate inspector fields from ad-hoc literals when the row already exists on the dataset.
|
|
19
|
+
|
|
20
|
+
4. **Columns & table “properties”** — Column defs **`accessorKey` / cell renderers** map to the **same** row interface as KPI helpers and board cards. **MUST NOT** introduce incompatible duplicate TypeScript shapes per view.
|
|
21
|
+
|
|
22
|
+
5. **`TablePropertiesDrawer`** — Stays wired to the **`DataTable`** that owns **`useTableState`** (**§4.2**): **`currentView`**, **`onViewChange`**, column visibility/density — all reflect manipulation of the **same** underlying rows.
|
|
23
|
+
|
|
24
|
+
6. **Labels / status / chips** — Prefer shared maps (**`lib/list-status-badges.ts`**, **`lib/data-list-view.ts`** tiles, entity-specific maps **next to** **`lib/mock/<entity>.ts`**). **MUST NOT** fork label strings or badge colors per view file.
|
|
25
|
+
|
|
26
|
+
7. **KPIs & charts** — **`MetricItem`** / **`MetricInsight`** builders take **`tableState.rows`** (or equivalent filtered list). Same inputs as the grid after search/filters.
|
|
27
|
+
|
|
28
|
+
## Centralized presentation (layout + components)
|
|
29
|
+
|
|
30
|
+
8. **`ListPageViewFrame`** — Non-**`DataTable`** view bodies (**folder**, **panel**, icon grids, comparable dashboard sections) **MUST** use **`ListPageViewFrame`** (and exported max-width constants) instead of copy-pasted **`mx-*` / `max-w-*`** per hub (**`exxat-list-page-view-shells.mdc`**).
|
|
31
|
+
|
|
32
|
+
9. **`components/data-views/`** — New **record-bearing** view layouts (**grids**, **OS folder**, **finder split**) **MUST** land as **generic** building blocks under **`data-views/`** with **`rows`**, **`getRowId`**, render props — hub **`TeamTable`** / **`LibraryTable`** **only** wires props and branch logic (**`AGENTS.md` §4.5**).
|
|
33
|
+
|
|
34
|
+
10. **Hub client composition** — One **`*-client.tsx`** owns **`useTableState`**, passes **`tableState.rows`** into every **`viewType`** branch, and mounts **template** slots (**metrics**, **export**, **`beforeSiteHeader`**) — **MUST NOT** split the same hub across multiple clients with different row sources.
|
|
35
|
+
|
|
36
|
+
## MUST NOT
|
|
37
|
+
|
|
38
|
+
- Ship alternate mock datasets per **`DataListViewType`** for the same hub without documenting them as **computed derivatives** of one canonical list.
|
|
39
|
+
- Duplicate entity fields in inspector-only types that contradict **`LibraryItem`** / **`Placement`** / etc.; extend the shared interface **once**.
|
|
40
|
+
|
|
41
|
+
## See also
|
|
42
|
+
|
|
43
|
+
- **`.cursor/skills/exxat-centralized-list-dataset/SKILL.md`** — workflow + checklist.
|
|
44
|
+
- **`.cursor/rules/exxat-table-properties-drawer.mdc`** — **`currentView`** / **`onViewChange`**.
|
|
45
|
+
- **`exxat-list-page-connected-views.mdc`** — toolbar + **`tableState.rows`**.
|
|
46
|
+
- **`exxat-list-page-view-shells.mdc`** — **`ListPageViewFrame`** + **`data-views/`** extraction.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Shared hubs — collaboration header, invite sheet, library access vs directory roles
|
|
3
|
+
globs: apps/web/components/**/*.{tsx,ts},apps/web/lib/**/*.{tsx,ts}
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
appliesTo: [react]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Exxat DS — collaboration & access
|
|
9
|
+
|
|
10
|
+
**Authoritative detail:** **`apps/web/AGENTS.md` §4.7** and **`apps/web/docs/collaboration-access-pattern.md`**.
|
|
11
|
+
|
|
12
|
+
## MUST
|
|
13
|
+
|
|
14
|
+
1. **Shared hubs** — Use **`PageHeader`** **`variant="collaboration"`** with **`collaborators`** (`PageHeaderCollaborator[]`) and optional **`accessInfo`**.
|
|
15
|
+
2. **Header chrome** — **Empty roster:** outline **Add collaborator** (`COLLABORATION_HEADER_ADD_LABEL` / **`addCollaboratorLabel`**) opens the invite sheet. **Non-empty roster:** face rail only; faces and **`+N`** open the same sheet via **`onCollaboratorsOpen`**.
|
|
16
|
+
3. **Invite entry** — **⋯ More** → **Invite people** on the entity page header; opens **`InviteCollaboratorsDrawer`** (floating sheet, same family as **`ExportDrawer`**).
|
|
17
|
+
4. **State** — Prefer **`CollaborationAccessFlow`** on hub **`*-client.tsx`**; otherwise own **`collaborators`** + **`inviteOpen`**. Successful invite updates **`collaborators`** so the header and sheet roster match.
|
|
18
|
+
5. **Library access** — Labels and invite options from **`lib/collaborator-access.ts`**; trailing roster badge = access (Owner / Editor / Commenter / Viewer).
|
|
19
|
+
6. **Directory roles** — Optional **`roles`** on **`PageHeaderCollaborator`** as **`Badge variant="outline"`** chips (Faculty, Program coordinator, Director) — **not** library access.
|
|
20
|
+
7. **Roster layout** — One bordered list with row dividers; per row: **name**, **email**, role tags, access badge — **not** one card per person.
|
|
21
|
+
8. **Invite field** — **`FieldGroup`** + **`Field`**; combined email + access row uses **`InputGroup`** + **`InputGroupInput`** + **`InputGroupAddon`** with shadcn **`Select`** (**`SelectGroup`** / **`SelectItem`**, **`position="popper"`**); persistent **`FieldDescription`** for email format; **no** **`toast()`** (**`exxat-no-toast.mdc`**).
|
|
22
|
+
|
|
23
|
+
## MUST NOT
|
|
24
|
+
|
|
25
|
+
- A second invite control **beside** a populated face rail.
|
|
26
|
+
- Fork access enums/labels per hub — extend **`collaborator-access.ts`** once.
|
|
27
|
+
- Use **`Select`** inside **`InputGroupAddon`** without **`InputGroupInput`** / **`SelectGroup`**; put email above name in the roster (order is **name → email → role tags**).
|
|
28
|
+
|
|
29
|
+
## See also
|
|
30
|
+
|
|
31
|
+
- **`exxat-page-vs-drawer.mdc`** — invite is a **sheet**, not a new route.
|
|
32
|
+
- **`exxat-kbd-shortcuts.mdc`** — workflow **Cancel** / **Send invite** shortcuts on the sheet.
|
|
33
|
+
- **`exxat-library-hub-header.mdc`** — Library library: when URL is **folder-scoped**, **⋯ More** also includes **Customize folder** (hub client hosts **`LibraryNewFolderSheet`**).
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Exxat DS — global command palette (⌘K) as search + quick AI vs Ask Leo for long answers.
|
|
3
3
|
alwaysApply: true
|
|
4
|
+
appliesTo: [universal]
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Exxat DS — global command palette (`CommandMenu`)
|
|
7
8
|
|
|
8
9
|
## Intent
|
|
9
10
|
|
|
10
|
-
- **`CommandMenu`** (**⌘K** / **Ctrl+K**) is **global search** (routes, library, patterns, AI starters, optional row data such as student names / question stems) — see **`AGENTS.md` §7.1** and **`docs/command-menu-pattern.md`**.
|
|
11
|
+
- **`CommandMenu`** (**⌘K** / **Ctrl+K**) is **global search** (routes, library, patterns, AI starters, optional row data such as student names / question stems) — see **`apps/web/AGENTS.md` §7.1** and **`apps/web/docs/command-menu-pattern.md`**.
|
|
11
12
|
- **Quick / lookup / short AI:** Prefer **results inside the palette** when the product can return compact answers or lightweight “research” without leaving the flow.
|
|
12
13
|
- **Long or complex answers:** **Ask Leo** side panel (**⌘⌥K** / **Ctrl+Alt+K**)—not forced into the palette.
|
|
13
14
|
|
|
14
15
|
## Implementation pointers
|
|
15
16
|
|
|
16
|
-
- Shell: `components/command-menu.tsx`; config **`buildCommandMenuConfig()`** in **`lib/command-menu-config.ts`**; optional **`dataGroups`** from **`lib/command-menu-search-data.ts`** (e.g. **`getCommandMenuSearchDataGroups()`**), wired in **`app/(app)/layout.tsx`**. Keep domain mapping out of the shell.
|
|
17
|
-
- Large indexes: set **`searchOnly: true`** on **`CommandMenuGroup`** so **`command-menu.tsx`** skips the group until the user types (avoids listing every row on open).
|
|
18
|
-
- Sidebar **“Search or ask Leo”** opens the same palette.
|
|
17
|
+
- Shell: `apps/web/components/command-menu.tsx`; config **`buildCommandMenuConfig()`** in **`apps/web/lib/command-menu-config.ts`**; optional **`dataGroups`** from **`apps/web/lib/command-menu-search-data.ts`** (e.g. **`getCommandMenuSearchDataGroups()`**), wired in **`apps/web/app/(app)/layout.tsx`**. Keep domain mapping out of the shell.
|
|
18
|
+
- Large indexes: set **`searchOnly: true`** on **`CommandMenuGroup`** so **`command-menu.tsx`** skips the group until the user types (avoids listing every row on open; cmdk shows all items when the search string is empty).
|
|
19
19
|
|
|
20
20
|
## See also
|
|
21
21
|
|
|
22
22
|
- **`apps/web/AGENTS.md` §7.1**
|
|
23
|
-
-
|
|
23
|
+
- **`exxat-kbd-shortcuts.mdc`** (⌘K vs ⌘⌥K)
|