@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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS — STOP before code on any surface design (new, rebuild, redesign, replace); output a design brief and WAIT for user go-ahead
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
appliesTo: [universal]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — UX discovery protocol
|
|
8
|
+
|
|
9
|
+
## STOP — read before you write any file
|
|
10
|
+
|
|
11
|
+
If the user's prompt asks you to **design, create, build, rebuild, redesign,
|
|
12
|
+
replace, redo, refresh, modernize, re-imagine, or "make a new version of"**
|
|
13
|
+
any **page, route, screen, hub, detail view, wizard, settings section,
|
|
14
|
+
dashboard, dialog, sheet, drawer, panel, layout, or significant component**:
|
|
15
|
+
|
|
16
|
+
1. **Do NOT write code, do NOT edit files, do NOT call edit tools yet.**
|
|
17
|
+
2. **Output the design brief** (template below) in chat.
|
|
18
|
+
3. **Wait for the user to reply** with `yes` / `proceed` / `ship it` / edits.
|
|
19
|
+
4. **Only then** move to implementation.
|
|
20
|
+
|
|
21
|
+
If your next tool call would be `write_file`, `str_replace`, `create_file`,
|
|
22
|
+
or any code-mutating action **and** you have not posted a brief and received
|
|
23
|
+
go-ahead, you are violating this rule. **Stop and post the brief.**
|
|
24
|
+
|
|
25
|
+
## When this gate fires (and when it doesn't)
|
|
26
|
+
|
|
27
|
+
This gate fires on **any task that decides what a surface should look like
|
|
28
|
+
or how it works** — whether the surface exists today or not. Treat
|
|
29
|
+
"replace what we have" and "create from scratch" identically — both need a
|
|
30
|
+
brief, because both make a design decision.
|
|
31
|
+
|
|
32
|
+
**Fires (brief required):**
|
|
33
|
+
|
|
34
|
+
- "Create a new student detail page."
|
|
35
|
+
- "Rebuild the dashboard."
|
|
36
|
+
- "Redesign the settings screen instead of what we have."
|
|
37
|
+
- "Make a new version of the placements table."
|
|
38
|
+
- "Replace the current onboarding flow."
|
|
39
|
+
- "Build a wizard for adding a site."
|
|
40
|
+
- "Design a sheet for inviting collaborators."
|
|
41
|
+
- User attaches a screenshot / mockup / Figma link and asks to "build this".
|
|
42
|
+
|
|
43
|
+
**Does NOT fire (brief not required, edit freely):**
|
|
44
|
+
|
|
45
|
+
- Single-class restyle of an existing surface that already follows DS rules.
|
|
46
|
+
- Copy / label edits.
|
|
47
|
+
- Bug fixes (a11y violation, broken state, wrong data).
|
|
48
|
+
- Dependency bumps, ESLint passes, type fixes, test-only changes.
|
|
49
|
+
- Adding a new column / filter to an *existing* `HubTable` that doesn't
|
|
50
|
+
change the page's IA.
|
|
51
|
+
|
|
52
|
+
When in doubt, ask: **"Am I deciding what this surface should be?"** Yes →
|
|
53
|
+
brief. No → edit.
|
|
54
|
+
|
|
55
|
+
## MUST
|
|
56
|
+
|
|
57
|
+
0. **Output the brief, then WAIT.** The brief is a checkpoint, not a
|
|
58
|
+
preamble. Do not bundle the brief and the first file edit in the same
|
|
59
|
+
turn. After posting the brief, end your turn with an explicit
|
|
60
|
+
"Ready to build — confirm or edit." prompt. Resume only on the user's
|
|
61
|
+
next message.
|
|
62
|
+
1. **No code without a confirmed brief.** "Confirmed" means the user wrote
|
|
63
|
+
`yes`, `proceed`, `ship it`, `LGTM`, `build it`, accepted edits, or asked
|
|
64
|
+
a follow-up that implies acceptance. Silence is not consent.
|
|
65
|
+
2. **Cite a reference.** Every brief names **one repo reference** + **two
|
|
66
|
+
modern SaaS analogues** (Linear / Notion / Stripe / Figma / Vercel /
|
|
67
|
+
Linear / Airtable / Coda / Height / etc.) that solve the same
|
|
68
|
+
**job-to-be-done**. Cite the SaaS analogues by product name + pattern
|
|
69
|
+
codes from `apps/web/docs/modern-saas-patterns.md` (e.g.
|
|
70
|
+
`Linear issue detail (M1, M4, M7)`).
|
|
71
|
+
3. **Name principles + breaks.** Brief lists the principles applied
|
|
72
|
+
(`exxat-ux-principles.mdc`) and any deviations with one-sentence reasons.
|
|
73
|
+
**Never-break** principles (P1–P8) cannot be deviated from.
|
|
74
|
+
4. **One assumption per gap.** If the user can't or won't answer, state the
|
|
75
|
+
assumption inline; never invent silently.
|
|
76
|
+
5. **Sync the brief to the implementation.** PR / commit message references
|
|
77
|
+
the job doc (`apps/web/docs/jobs/`) and any principle break.
|
|
78
|
+
|
|
79
|
+
## MUST NOT
|
|
80
|
+
|
|
81
|
+
- **Skip the brief because the prompt sounds like a refactor.** "Rebuild",
|
|
82
|
+
"redesign", "replace", "instead of what we have", and "from scratch" are
|
|
83
|
+
design decisions, not refactors. Brief required.
|
|
84
|
+
- **Post a brief and then write 8 files in the same turn.** That's not a
|
|
85
|
+
brief — that's a press release. End the turn after the brief.
|
|
86
|
+
- Generate files before the brief.
|
|
87
|
+
- Ask more than **3** questions in one batch.
|
|
88
|
+
- Ask questions whose answer is already in the prompt, the file tree, or
|
|
89
|
+
`apps/web/docs/jobs/`.
|
|
90
|
+
- Use uploaded screenshots as the implementation spec
|
|
91
|
+
(`exxat-no-image-pixel-copy.mdc`).
|
|
92
|
+
- Treat a feature list as a job. Ask: *what decision does this enable?*
|
|
93
|
+
|
|
94
|
+
## Question bank by surface type
|
|
95
|
+
|
|
96
|
+
Pick **at most 3** questions whose answers change the design. Skip the rest.
|
|
97
|
+
|
|
98
|
+
### Record / entity detail
|
|
99
|
+
1. Who reads this most — power admin (dense, keyboard-first) or occasional
|
|
100
|
+
user (cozy, guided)?
|
|
101
|
+
2. How is it reached — list, deep link, notification, search? (Drives
|
|
102
|
+
breadcrumb + back-affordance model.)
|
|
103
|
+
3. Section tabs needed, or single-scroll page? (Field count > ~20 or rich
|
|
104
|
+
children → tabs.)
|
|
105
|
+
|
|
106
|
+
### Hub / list page
|
|
107
|
+
1. Dense comparison (table) or visual browse (board / gallery)?
|
|
108
|
+
2. Per-record actions, bulk actions, or both?
|
|
109
|
+
3. Will users filter / sort / customize columns, or is the view fixed?
|
|
110
|
+
|
|
111
|
+
### Wizard / compose flow
|
|
112
|
+
1. One screen with sections, or multi-step (>3 decisions)?
|
|
113
|
+
2. Are intermediate steps savable as drafts?
|
|
114
|
+
3. Submit is destructive, or always reversible?
|
|
115
|
+
|
|
116
|
+
### Settings / preferences
|
|
117
|
+
1. User scope (personal), workspace (shared), or both?
|
|
118
|
+
2. Search needed (>20 settings) or flat list?
|
|
119
|
+
3. Apply-on-change or explicit Save?
|
|
120
|
+
|
|
121
|
+
### Dashboard / analytics
|
|
122
|
+
1. One critical KPI or a comparison set (≤4)?
|
|
123
|
+
2. Read at a glance, or drill into details?
|
|
124
|
+
3. Time range fixed, or user-controlled?
|
|
125
|
+
|
|
126
|
+
### Overlay (drawer / dialog / sheet)
|
|
127
|
+
1. Does the user need the hub visible behind them? (Yes → sheet; no → route.)
|
|
128
|
+
2. Blocking confirmation, or reversible? (Blocking → dialog; reversible → sheet.)
|
|
129
|
+
3. Will it grow to ≥3 steps? (Yes → consider route instead.)
|
|
130
|
+
|
|
131
|
+
## Brief template (copy verbatim)
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Problem: <one sentence — the user's pain, not the feature>
|
|
135
|
+
User & frequency: <persona, frequency, expertise>
|
|
136
|
+
Job-to-be-done: <the decision or action this enables>
|
|
137
|
+
Pattern: <route | sheet | dialog | inline> + IA shape
|
|
138
|
+
Reference (repo): <file path>
|
|
139
|
+
Reference (modern): <product 1 + Mx>, <product 2 + Mx>
|
|
140
|
+
Principles applied: <P-codes from exxat-ux-principles>
|
|
141
|
+
Deviations: <principle + one-sentence reason, or "none">
|
|
142
|
+
Out of scope: <what this does not do, deliberately>
|
|
143
|
+
Open questions: <max 2; ideally 0>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Examples
|
|
147
|
+
|
|
148
|
+
**Good:**
|
|
149
|
+
> Problem: Coordinators can't tell at a glance which students are
|
|
150
|
+
> non-compliant before placement.
|
|
151
|
+
> User & frequency: Program coordinator, daily, keyboard-first.
|
|
152
|
+
> Job-to-be-done: Identify and act on at-risk students in < 30s per record.
|
|
153
|
+
> Pattern: route `/students/[id]` + identity row + status row + 2×2 card grid.
|
|
154
|
+
> Reference (repo): `components/library-table.tsx` for IA cadence.
|
|
155
|
+
> Reference (modern): Linear issue detail (M1, M4, M7); Stripe customer
|
|
156
|
+
> record (M4, M11).
|
|
157
|
+
> Principles applied: P1, P2, P3, P5, P6, P13, P19.
|
|
158
|
+
> Deviations: none.
|
|
159
|
+
> Out of scope: inline editing (v2), bulk message (lives on list).
|
|
160
|
+
> Open questions: none.
|
|
161
|
+
|
|
162
|
+
**Bad (silent generate):**
|
|
163
|
+
> *Agent writes 4 files without explanation.*
|
|
164
|
+
|
|
165
|
+
**Bad (vague prompt accepted):**
|
|
166
|
+
> User: "make a settings page". *Agent writes generic form.* Should have
|
|
167
|
+
> asked: scope (personal/workspace), search needed, apply-on-change vs Save.
|
|
168
|
+
|
|
169
|
+
## Closing artifact: the design → engineering handoff
|
|
170
|
+
|
|
171
|
+
When the build is complete (files written, surface working in dev), produce
|
|
172
|
+
**one filled-in handoff document** before declaring the task done.
|
|
173
|
+
|
|
174
|
+
**Template** lives at:
|
|
175
|
+
|
|
176
|
+
- Workspace: `apps/web/docs/templates/handoff.md` (or workspace root if not yet copied — copy from `packages/ui/consumer-extras/templates/handoff.md`)
|
|
177
|
+
- Consumer apps (after `exxat-ui sync-extras`): `docs/exxat-ds/templates/handoff.md`
|
|
178
|
+
|
|
179
|
+
**Fill it in** with the actual primitives, tokens, icons, and shortcuts the
|
|
180
|
+
build used. Save the filled copy at:
|
|
181
|
+
|
|
182
|
+
- `apps/web/docs/handoff/<surface-slug>.md` (workspace)
|
|
183
|
+
- `docs/exxat-ds/handoff/<surface-slug>.md` (consumer)
|
|
184
|
+
|
|
185
|
+
Attach the filled handoff to the PR description (or paste inline). The
|
|
186
|
+
engineer reads it once, runs `exxat-ui audit <generated-file>` to verify
|
|
187
|
+
the floor, and is done.
|
|
188
|
+
|
|
189
|
+
**MUST sections:** 1 (job-to-be-done), 3 (pattern + IA), 5 (DS components
|
|
190
|
+
used), 6 (tokens), 8 (a11y checklist — every box ticked), 11 (keyboard map),
|
|
191
|
+
12 (any deviations from P9–P20).
|
|
192
|
+
|
|
193
|
+
If the handoff is incomplete or hand-wavy, the design task is not finished.
|
|
194
|
+
|
|
195
|
+
## See also
|
|
196
|
+
|
|
197
|
+
- `.cursor/skills/exxat-senior-ux/SKILL.md` — the persona + full protocol
|
|
198
|
+
- `.cursor/rules/exxat-ux-principles.mdc` — principles + when to break
|
|
199
|
+
- `apps/web/docs/jobs/` — canonical references per job type
|
|
200
|
+
- `apps/web/docs/modern-saas-patterns.md` — pattern codes (M1–M12)
|
|
201
|
+
- `.cursor/rules/exxat-no-image-pixel-copy.mdc` — IA from screenshots only
|
|
202
|
+
- **Closing artifact:** `packages/ui/consumer-extras/templates/handoff.md`
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Exxat DS UX principles — what to follow, when to break, why
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
appliesTo: [universal]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — UX principles (and when to break them)
|
|
8
|
+
|
|
9
|
+
Principles are the senior designer's spine. They make most decisions cheap.
|
|
10
|
+
**Break deliberately, not accidentally** — and say so in the design brief
|
|
11
|
+
(`exxat-ux-discovery-protocol.mdc`). If you can't write the deviation reason
|
|
12
|
+
in one sentence, you shouldn't be breaking the principle.
|
|
13
|
+
|
|
14
|
+
Each principle below has: **what it says**, **why**, **when to break**, and
|
|
15
|
+
the **anti-pattern**.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Always-follow (P1–P8) — no exceptions
|
|
20
|
+
|
|
21
|
+
### P1. Way back is obvious, and exactly once
|
|
22
|
+
Breadcrumb in `SiteHeader` OR explicit back affordance — **never both**.
|
|
23
|
+
- **Why:** Two paths back create cognitive overhead and a redundant UI.
|
|
24
|
+
- **When to break:** Never.
|
|
25
|
+
- **Anti-pattern:** Breadcrumb "Dashboard › Students › Jordan Lee" + a
|
|
26
|
+
separate "Back to roster" button in the body.
|
|
27
|
+
- **Reference:** `exxat-breadcrumbs-no-back.mdc`.
|
|
28
|
+
|
|
29
|
+
### P2. One H1 per page; no duplicated identity
|
|
30
|
+
The record name appears in exactly one carrier — typically `PageHeader.title`
|
|
31
|
+
+ ancestors-only breadcrumb. Not also as an `<h1>` in the body.
|
|
32
|
+
- **Why:** Screen readers and scan-reading rely on a single page title.
|
|
33
|
+
- **When to break:** Never.
|
|
34
|
+
- **Anti-pattern:** Name in breadcrumb leaf + `PageHeader title` + body `<h1>`.
|
|
35
|
+
|
|
36
|
+
### P3. One primary action per surface
|
|
37
|
+
Exactly one filled `Button`; everything else `outline` / `ghost` / `link`.
|
|
38
|
+
- **Why:** Two primaries = no primary.
|
|
39
|
+
- **When to break:** Never. Symmetric pairs ("Approve" + "Reject") are both
|
|
40
|
+
*outline* — neither is the styled "primary".
|
|
41
|
+
- **Anti-pattern:** Two filled buttons in the header.
|
|
42
|
+
|
|
43
|
+
### P4. Don't pixel-copy
|
|
44
|
+
Extract IA from screenshots and competitor references; never copy chrome,
|
|
45
|
+
colors, or bespoke widgets.
|
|
46
|
+
- **Why:** Pixels carry decisions made in another product's context.
|
|
47
|
+
- **When to break:** Never.
|
|
48
|
+
- **Reference:** `exxat-no-image-pixel-copy.mdc`.
|
|
49
|
+
|
|
50
|
+
### P5. Every list / detail / action ships empty + error + loading
|
|
51
|
+
- **Why:** Real users hit all three; afterthoughts ship as bugs.
|
|
52
|
+
- **When to break:** Never.
|
|
53
|
+
- **Anti-pattern:** "Loading…" text and no skeleton; missing 404 / empty.
|
|
54
|
+
|
|
55
|
+
### P6. Keyboard parity
|
|
56
|
+
Every mouse action has a keyboard equivalent; focus is always visible.
|
|
57
|
+
- **Why:** Power users, motor-disabled users, screen-reader users all rely
|
|
58
|
+
on this.
|
|
59
|
+
- **When to break:** Never.
|
|
60
|
+
- **Reference:** `exxat-kbd-shortcuts.mdc`, `exxat-accessibility.mdc`.
|
|
61
|
+
|
|
62
|
+
### P7. WCAG 2.1 AA minimum
|
|
63
|
+
Contrast ≥ 4.5:1 (text) / ≥ 3:1 (UI), targets ≥ 24×24, labels on icon-only.
|
|
64
|
+
- **Why:** Floor for shipping. Not negotiable.
|
|
65
|
+
- **When to break:** Never.
|
|
66
|
+
- **Reference:** `exxat-accessibility.mdc`.
|
|
67
|
+
|
|
68
|
+
### P8. Reuse before invent
|
|
69
|
+
Compose from DS primitives first. New shared primitives require user approval
|
|
70
|
+
after a real, repeated need (≥ 2 use cases).
|
|
71
|
+
- **Why:** Forked stacks divide the team; the DS is the vocabulary.
|
|
72
|
+
- **When to break:** Never silently. Ask the user.
|
|
73
|
+
- **Reference:** `exxat-reuse-before-custom.mdc`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Default-follow (P9–P20) — break only with stated reason
|
|
78
|
+
|
|
79
|
+
### P9. Clarity over cleverness
|
|
80
|
+
Direct labels, recognizable patterns. "Save" beats "Persist". "Send" beats
|
|
81
|
+
"Beam".
|
|
82
|
+
- **Why:** Decisions cost less when the label states the action.
|
|
83
|
+
- **Break when:** Intentional brand voice in low-stakes surfaces (onboarding
|
|
84
|
+
celebration, marketing splash). Never in core flows.
|
|
85
|
+
- **Anti-pattern:** "Materialize this configuration" instead of "Save".
|
|
86
|
+
|
|
87
|
+
### P10. Recognition over recall
|
|
88
|
+
Surface what the user needs; don't make them remember.
|
|
89
|
+
- **Why:** Working memory is the most expensive resource on a screen.
|
|
90
|
+
- **Break when:** Daily power users where keyboard shortcuts replace visible
|
|
91
|
+
chrome (Cron, Linear, Raycast). Provide a discoverable hint.
|
|
92
|
+
- **Anti-pattern:** Hiding a frequent action behind a chord with no UI.
|
|
93
|
+
|
|
94
|
+
### P11. One job per surface
|
|
95
|
+
A screen optimizes for one primary user decision.
|
|
96
|
+
- **Why:** Splits attention, splits metrics, splits design quality.
|
|
97
|
+
- **Break when:** Workspace surfaces (Notion-style pages, Linear-style issue
|
|
98
|
+
views) deliberately compose multiple jobs. Even then, one job is **primary**.
|
|
99
|
+
- **Anti-pattern:** Settings page that also includes a dashboard.
|
|
100
|
+
|
|
101
|
+
### P12. Progressive disclosure
|
|
102
|
+
Show core first; depth on demand (popovers, sheets, expand).
|
|
103
|
+
- **Why:** Most users do the common thing most of the time.
|
|
104
|
+
- **Break when:** Compliance / legal / audit surfaces where everything must
|
|
105
|
+
be visible.
|
|
106
|
+
- **Anti-pattern:** 30-field form on first load with no grouping.
|
|
107
|
+
|
|
108
|
+
### P13. Status visible without scroll
|
|
109
|
+
Status signals that drive decisions (compliance, errors, urgency) appear
|
|
110
|
+
above the fold.
|
|
111
|
+
- **Why:** Status hidden = ignored.
|
|
112
|
+
- **Break when:** Privacy / discretion contexts (e.g. HR records where peer
|
|
113
|
+
visibility is undesirable).
|
|
114
|
+
- **Anti-pattern:** Compliance status on tab #3 of a record detail.
|
|
115
|
+
|
|
116
|
+
### P14. Density follows frequency
|
|
117
|
+
Daily power users get compact / dense; occasional users get cozy / spacious.
|
|
118
|
+
- **Why:** Different audiences scan differently.
|
|
119
|
+
- **Break when:** Accessibility (low-vision, motor) mandates generous
|
|
120
|
+
spacing. Offer a density toggle if both audiences share a surface.
|
|
121
|
+
- **Anti-pattern:** Sparse table for a coordinator viewing 200 students/day.
|
|
122
|
+
|
|
123
|
+
### P15. Inline editing where data is read
|
|
124
|
+
Modern SaaS (Notion, Linear, Airtable) edits in place. Don't bounce users
|
|
125
|
+
to a form for a single-field change.
|
|
126
|
+
- **Why:** Mode-switch friction kills throughput.
|
|
127
|
+
- **Break when:** Bulk operations, multi-field validation, audit-logged
|
|
128
|
+
fields, destructive edits — use a focused form / drawer.
|
|
129
|
+
- **Anti-pattern:** "Edit" page just to change a phone number.
|
|
130
|
+
|
|
131
|
+
### P16. Optimistic UI for low-risk actions
|
|
132
|
+
Favorite, archive, status flip, reorder — feel instant; reconcile on error.
|
|
133
|
+
- **Why:** Speed of feedback is a quality signal.
|
|
134
|
+
- **Break when:** Financial / irreversible / regulated actions — show progress
|
|
135
|
+
and confirmation.
|
|
136
|
+
- **Anti-pattern:** Spinner overlay for a star-favorite toggle.
|
|
137
|
+
|
|
138
|
+
### P17. Search is global and fast
|
|
139
|
+
`⌘K` opens command + search; everything addressable is searchable.
|
|
140
|
+
- **Why:** Modern users navigate by typing.
|
|
141
|
+
- **Break when:** Never for modern SaaS at this scale.
|
|
142
|
+
- **Reference:** `exxat-command-menu.mdc`.
|
|
143
|
+
|
|
144
|
+
### P18. Content first, chrome last
|
|
145
|
+
Data is the star. Sidebars collapse to icons. Headers stay ~48–56px.
|
|
146
|
+
Surfaces are generous.
|
|
147
|
+
- **Why:** Chrome competes with the work.
|
|
148
|
+
- **Break when:** Heavily configurable admin consoles where chrome **is** the
|
|
149
|
+
content. Prefer collapsing over crowding.
|
|
150
|
+
- **Anti-pattern:** Purple gradient hero bar above a data table.
|
|
151
|
+
|
|
152
|
+
### P19. Type carries hierarchy
|
|
153
|
+
Weight, size, color do the heavy lifting before borders, boxes, or color
|
|
154
|
+
tints.
|
|
155
|
+
- **Why:** Ornament dates fast; type ages well.
|
|
156
|
+
- **Break when:** Status surfaces where color is encoding meaning (chips,
|
|
157
|
+
alerts).
|
|
158
|
+
- **Anti-pattern:** Every value wrapped in a tinted card.
|
|
159
|
+
|
|
160
|
+
### P20. AI is a sidecar, not the primary path
|
|
161
|
+
Ask Leo / inline AI — opt-in, contextual, cancellable. Never auto-runs on a
|
|
162
|
+
record. Never the only way to do something.
|
|
163
|
+
- **Why:** AI is a feature; the core product must work without it.
|
|
164
|
+
- **Break when:** Surfaces explicitly built around AI (e.g. an AI-only
|
|
165
|
+
composer). Even then, expose the deterministic path.
|
|
166
|
+
- **Reference:** `exxat-command-menu.mdc` (⌘K vs ⌘⌥K split).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## How to declare a deviation
|
|
171
|
+
|
|
172
|
+
In the design brief, list the broken principle + the reason on one line:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Deviations: P14 (density) — broke "follows frequency" because the primary
|
|
176
|
+
user here is occasional but the same surface also serves daily
|
|
177
|
+
admins; resolved with a density toggle, default cozy.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
If the reason doesn't fit on one line, the deviation isn't justified yet.
|
|
181
|
+
|
|
182
|
+
## See also
|
|
183
|
+
|
|
184
|
+
- `.cursor/skills/exxat-senior-ux/SKILL.md` — the persona
|
|
185
|
+
- `.cursor/rules/exxat-ux-discovery-protocol.mdc` — gating + brief
|
|
186
|
+
- `apps/web/docs/modern-saas-patterns.md` — the modern canon (M1–M12)
|
|
187
|
+
- All `exxat-*.mdc` rules — concrete enforcement per pattern
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exxat-accessibility
|
|
3
|
+
description: WCAG 2.x / ARIA checklist for Exxat DS — tablists, touch targets, contrast, and audit follow-ups. Use when fixing axe/Deque issues, building nav or tab UIs, or reviewing accessibility.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Exxat DS — accessibility checklist
|
|
8
|
+
|
|
9
|
+
Standard target: **WCAG 2.1 Level AA** (and 2.2 where noted).
|
|
10
|
+
|
|
11
|
+
**Canonical for agents (MUST/MUST NOT, checklist):** `apps/web/AGENTS.md` **§8** in the repo (same content summarized there; this skill stays the detailed checklist + product tokens).
|
|
12
|
+
|
|
13
|
+
## ARIA roles & structure (SC 1.3.1)
|
|
14
|
+
|
|
15
|
+
- **`role="tablist"`** may only contain **`role="tab"`** (or elements that resolve to tabs). Do **not** place `role="button"`, menus (`aria-haspopup`), or ad-hoc controls **inside** the same `tablist` container.
|
|
16
|
+
- **Composite view switchers** (tabs + per-tab settings menu + remove control): use **`role="toolbar"`** with **`aria-label`**, and **`aria-pressed`** on view toggle buttons instead of misusing `tab`/`tablist`.
|
|
17
|
+
- Prefer **`<button type="button">`** over **`span role="button"`** for clickable icons (keyboard + AT).
|
|
18
|
+
|
|
19
|
+
## Touch targets (WCAG 2.2 AA — 2.5.8 Target Size Minimum)
|
|
20
|
+
|
|
21
|
+
- Interactive controls (including icon-only chevrons and close icons) should be at least **24×24 CSS pixels**, or have **24px** spacing so their **24px** hit circles do not overlap adjacent targets.
|
|
22
|
+
- Use **`min-h-6 min-w-6`** (or `size-6`) with centered icons; avoid `size-4` (16px) for sole click targets.
|
|
23
|
+
|
|
24
|
+
## Icons that communicate information — always have a text alternative (SC 1.1.1, 3.3.2, 2.4.6)
|
|
25
|
+
|
|
26
|
+
This rule covers **every icon that carries meaning**, not only icon-only buttons. FA glyphs, inline SVGs, avatar placeholders, trend arrows, status dots, chart-legend squares, calendar/clock/pin icons in cells — if the icon **tells the user something**, that something MUST be reachable by screen readers AND discoverable to sighted users who don't recognise the glyph.
|
|
27
|
+
|
|
28
|
+
There are three cases. Pick the one that matches the icon's context:
|
|
29
|
+
|
|
30
|
+
### Case A — Decorative icon next to text that already names it
|
|
31
|
+
|
|
32
|
+
When the icon sits adjacent to a visible text label that already carries the meaning, the icon is **decorative**. It MUST be `aria-hidden` and MUST NOT carry `aria-label` (screen readers would announce the meaning twice).
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<span className="flex items-center gap-1.5">
|
|
36
|
+
<i className="fa-light fa-calendar-days" aria-hidden />
|
|
37
|
+
<span>12/14/2025 – 12/20/2025</span>
|
|
38
|
+
</span>
|
|
39
|
+
|
|
40
|
+
<Button>
|
|
41
|
+
<i className="fa-light fa-plus" aria-hidden />
|
|
42
|
+
<span>New placement</span>
|
|
43
|
+
</Button>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
No tooltip needed — the text is the alt. This is the default in table cells, buttons with text labels, breadcrumbs, badges, menu items.
|
|
47
|
+
|
|
48
|
+
### Case B — Informational icon standing alone (no adjacent label)
|
|
49
|
+
|
|
50
|
+
When the icon is the **only visible carrier** of the information — e.g.:
|
|
51
|
+
|
|
52
|
+
- `fa-calendar-days` in a compact table column header meaning "date range"
|
|
53
|
+
- `fa-clock` meaning "updated at" or "time remaining"
|
|
54
|
+
- `fa-location-dot` meaning "site" / "location"
|
|
55
|
+
- `fa-graduation-cap` meaning "student"
|
|
56
|
+
- trending arrow in a KPI card (↑ / ↓)
|
|
57
|
+
- status dot (a coloured circle meaning "on track / at risk / blocked")
|
|
58
|
+
- icon-only legend key on a chart
|
|
59
|
+
- `fa-paperclip` meaning "has attachments"
|
|
60
|
+
- `fa-lock` meaning "restricted"
|
|
61
|
+
|
|
62
|
+
…the icon MUST:
|
|
63
|
+
|
|
64
|
+
1. Announce itself to AT via **`role="img"` + `aria-label`** on a wrapping element (span/div), OR inherit the name from a labelled ancestor via `aria-labelledby`.
|
|
65
|
+
2. Expose a visible **`Tooltip`** so sighted mouse/keyboard users who don't recognise the glyph learn the meaning.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<Tooltip>
|
|
69
|
+
<TooltipTrigger asChild>
|
|
70
|
+
{/* Non-interactive: wrap in a span with role="img".
|
|
71
|
+
tabIndex={0} so the tooltip opens on keyboard focus, not just hover. */}
|
|
72
|
+
<span
|
|
73
|
+
role="img"
|
|
74
|
+
aria-label="Placement date range"
|
|
75
|
+
tabIndex={0}
|
|
76
|
+
className="inline-flex size-6 items-center justify-center rounded-md text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
77
|
+
>
|
|
78
|
+
<i className="fa-light fa-calendar-days" aria-hidden />
|
|
79
|
+
</span>
|
|
80
|
+
</TooltipTrigger>
|
|
81
|
+
<TooltipContent side="top">Placement date range</TooltipContent>
|
|
82
|
+
</Tooltip>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Rules:
|
|
86
|
+
1. `TooltipContent` text MUST match the `aria-label`.
|
|
87
|
+
2. The inner `<i>` / `<svg>` is always `aria-hidden`; the accessible name lives on the wrapper.
|
|
88
|
+
3. If a visible text label could fit, **prefer Case A** (add the label) over tooltip-only.
|
|
89
|
+
4. Target/focus size still **≥ 24×24 CSS px** so keyboard users can focus the icon reliably.
|
|
90
|
+
5. Status dots MUST additionally carry a **text or shape cue** (not colour alone — SC 1.4.1). E.g. pair a coloured dot with a status label nearby, or differentiate by icon shape (`circle-check` vs `triangle-exclamation` vs `circle-xmark`).
|
|
91
|
+
|
|
92
|
+
### Case C — Interactive icon-only button / link
|
|
93
|
+
|
|
94
|
+
Any button or link whose visible content is a **single icon** — close (`×`), chevron, overflow (`⋯`), sort direction, filter chip dismiss, copy-to-clipboard, Ask Leo toggle, expand/collapse, row actions — MUST carry BOTH:
|
|
95
|
+
|
|
96
|
+
1. **`aria-label`** on the `<button>` / `<a>` (programmatic name for AT and axe).
|
|
97
|
+
2. A wrapping **`Tooltip`** whose content repeats the same name (plus a `Kbd` when a shortcut exists).
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"
|
|
101
|
+
import { Kbd } from "@/components/ui/kbd"
|
|
102
|
+
|
|
103
|
+
<Tooltip>
|
|
104
|
+
<TooltipTrigger asChild>
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
aria-label="Close insight"
|
|
108
|
+
onClick={onClose}
|
|
109
|
+
className="inline-flex size-7 min-h-7 min-w-7 items-center justify-center rounded-md …"
|
|
110
|
+
>
|
|
111
|
+
<i className="fa-solid fa-xmark" aria-hidden />
|
|
112
|
+
</button>
|
|
113
|
+
</TooltipTrigger>
|
|
114
|
+
<TooltipContent side="top" className="flex items-center gap-1.5">
|
|
115
|
+
<span>Close</span>
|
|
116
|
+
<Kbd>Esc</Kbd>
|
|
117
|
+
</TooltipContent>
|
|
118
|
+
</Tooltip>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Rules:
|
|
122
|
+
1. `TooltipContent` MUST match or extend the `aria-label`.
|
|
123
|
+
2. Inside the tooltip use the **default tile** `<Kbd>` (NOT `variant="bare"`).
|
|
124
|
+
3. The inner `<i>` / `<svg>` MUST have `aria-hidden`.
|
|
125
|
+
4. Click target **≥ 24×24 CSS px** (see Touch targets section).
|
|
126
|
+
|
|
127
|
+
### Narrow exceptions (all cases)
|
|
128
|
+
|
|
129
|
+
- A chevron inside a labelled composite (`Select`, `Combobox`, `Breadcrumb` separator) where the parent already names the whole control or the glyph is purely structural — mark it `aria-hidden`.
|
|
130
|
+
- Drag handles inside a `dnd-kit` listener that reference a labelled ancestor via `aria-describedby`.
|
|
131
|
+
- Decorative icons that purely decorate (e.g. the Leo star next to an already-named "Ask Leo" button) — `aria-hidden`, no tooltip.
|
|
132
|
+
|
|
133
|
+
### Decision tree
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Icon appears somewhere →
|
|
137
|
+
Is there adjacent visible text that already names the meaning?
|
|
138
|
+
YES → Case A: aria-hidden, no tooltip. Done.
|
|
139
|
+
NO → Is the icon the visible content of a button/link?
|
|
140
|
+
YES → Case C: aria-label on the control + Tooltip.
|
|
141
|
+
NO → Case B: span[role="img", aria-label, tabIndex=0] + Tooltip.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**When in doubt: add the accessible name + tooltip.** Silence is never correct for an icon that means something.
|
|
145
|
+
|
|
146
|
+
## Keyboard shortcuts inside buttons — `<Kbd variant="bare">` only
|
|
147
|
+
|
|
148
|
+
The `Kbd` primitive (`@/components/ui/kbd`) has two variants:
|
|
149
|
+
|
|
150
|
+
| Variant | Where it goes | Chrome |
|
|
151
|
+
|---------|---------------|--------|
|
|
152
|
+
| `tile` (default) | Inside `TooltipContent`, menu `shortcut=` slots, standalone surfaces | Background + border, fixed contrast |
|
|
153
|
+
| `bare` | Inline **inside** a `Button` (primary, secondary, wizard Next/Back/Submit) | No background, no border, inherits `currentColor` at 70 % opacity |
|
|
154
|
+
|
|
155
|
+
Inside a filled/solid button the `tile` variant looks like a pasted-on patch (wrong background color against the button fill). Always glue multi-key chords into **one** bare kbd:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
// ✅ correct — bare inside a button
|
|
159
|
+
<Button>
|
|
160
|
+
<span>Ask Leo</span>
|
|
161
|
+
<KbdGroup className="ml-1.5">
|
|
162
|
+
<Kbd variant="bare">⌘⌥K</Kbd>
|
|
163
|
+
</KbdGroup>
|
|
164
|
+
</Button>
|
|
165
|
+
|
|
166
|
+
// ❌ wrong — tile variant on a primary button
|
|
167
|
+
<Button>
|
|
168
|
+
<span>Ask Leo</span>
|
|
169
|
+
<KbdGroup className="ml-1.5">
|
|
170
|
+
<Kbd>⌘</Kbd><Kbd>⌥</Kbd><Kbd>K</Kbd>
|
|
171
|
+
</KbdGroup>
|
|
172
|
+
</Button>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Reference pattern: `components/new-placement-form.tsx` (Next = `<Kbd variant="bare">{mod}⏎</Kbd>`, Back = `<Kbd variant="bare">{mod}{alt}←</Kbd>`). See `.cursor/rules/exxat-kbd-shortcuts.mdc` for the full shortcut table.
|
|
176
|
+
|
|
177
|
+
## Color (SC 1.4.3 / 1.4.11)
|
|
178
|
+
|
|
179
|
+
- **Normal text** (including small badge labels): **≥ 4.5:1** against its background.
|
|
180
|
+
- **UI components** (borders, focus rings): **≥ 3:1** where required by 1.4.11.
|
|
181
|
+
- **Muted text on tinted surfaces** (e.g. sidebar): use tokens mixed against **`--sidebar`**, not only against `--background` (see `--sidebar-section-label-foreground`).
|
|
182
|
+
|
|
183
|
+
## Sidebar badges (design tokens)
|
|
184
|
+
|
|
185
|
+
- **Count / numeric (sidebar):** **red** (`bg-red-600` + white). Placement toolbar counts use **status colors** (see below).
|
|
186
|
+
- **“New”:** use **brand** (`bg-brand` + `text-brand-foreground`).
|
|
187
|
+
- **“Beta”:** bright yellow fill + **dark** text (`bg-yellow-400` + `text-yellow-950`) for contrast.
|
|
188
|
+
|
|
189
|
+
## Placements toolbar count pills (`filterId`)
|
|
190
|
+
|
|
191
|
+
- **`all`:** slate
|
|
192
|
+
- **`upcoming`:** amber
|
|
193
|
+
- **`ongoing`:** blue
|
|
194
|
+
- **`completed`:** emerald
|
|
195
|
+
- Unknown `filterId` falls back to **all** (slate).
|
|
196
|
+
|
|
197
|
+
## Form fields — format hints MUST be persistent (SC 3.3.2, 1.3.1)
|
|
198
|
+
|
|
199
|
+
Placeholder text disappears on focus, is low-contrast by default, and is not reliably announced by assistive tech. For any field with a **required format**, render the format as **persistent helper text** tied to the input via `aria-describedby` — never rely on the placeholder alone.
|
|
200
|
+
|
|
201
|
+
**When this applies (non-exhaustive):**
|
|
202
|
+
|
|
203
|
+
- Dates, times, date ranges (even when a picker is present — the typed-format fallback must show the mask).
|
|
204
|
+
- Phone / fax, country-specific formats.
|
|
205
|
+
- Currency, GPA, percentages, hours, durations, unit-bearing numbers ("hrs/wk", "USD").
|
|
206
|
+
- IDs with a pattern (Student ID `STU-YYYY-####`, NPI, license #).
|
|
207
|
+
- Email / URL where a domain or protocol is required.
|
|
208
|
+
- Credit hours, scores, weights — anything whose scale is not obvious from the label.
|
|
209
|
+
|
|
210
|
+
**Pattern — use `FormDescription` from shadcn `Form`:**
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<FormField name="dob" render={({ field }) => (
|
|
214
|
+
<FormItem>
|
|
215
|
+
<FormLabel>Date of birth<span aria-hidden="true"> *</span></FormLabel>
|
|
216
|
+
<FormControl>
|
|
217
|
+
<Input {...field} inputMode="numeric" placeholder="MM/DD/YYYY" />
|
|
218
|
+
</FormControl>
|
|
219
|
+
{/* Persistent — announced via aria-describedby, survives focus */}
|
|
220
|
+
<FormDescription>MM/DD/YYYY</FormDescription>
|
|
221
|
+
<FormMessage />
|
|
222
|
+
</FormItem>
|
|
223
|
+
)} />
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Rules:**
|
|
227
|
+
|
|
228
|
+
1. The format MUST appear as visible text **outside** the placeholder — `FormDescription`, a helper `<p>` with `id` referenced by `aria-describedby`, or an adjacent `<small>`.
|
|
229
|
+
2. Placeholder MAY mirror the format as extra affordance, but is never the sole source.
|
|
230
|
+
3. Prefer a **picker primitive** over free-text where one exists: `DatePickerField` for dates, `Select` for enumerated values, masked input for phone/IDs.
|
|
231
|
+
4. Required marker (`*`) is decorative — always pair with `aria-required="true"` on the input.
|
|
232
|
+
5. Error messages (`FormMessage`) replace description announcement while active; keep the description concise so the combined `aria-describedby` string stays readable.
|
|
233
|
+
6. Units belong in the description, not the label suffix, when they vary by context (e.g. "Out of 4.0" under GPA rather than in the label).
|
|
234
|
+
|
|
235
|
+
## High-Contrast modes
|
|
236
|
+
|
|
237
|
+
There are **two** HC paths in this app. Fix both or the bar will still look broken in one of them:
|
|
238
|
+
|
|
239
|
+
1. **App HC theme — `html[data-contrast="high"]`** (user toggle in Settings). Use the **`hc:`** Tailwind variant defined in `globals.css` (line 22: `@custom-variant hc (&:is([data-contrast="high"] *));`). This is what the user sees when they switch to High Contrast in-app.
|
|
240
|
+
2. **OS / browser forced-colors** — Windows High Contrast, some Linux DEs, `prefers-contrast: more`. Use `forced-colors:` variants mapping to system colors (`Canvas`, `CanvasText`, `Highlight`, `HighlightText`, `GrayText`).
|
|
241
|
+
|
|
242
|
+
### Why tokens collapse in HC
|
|
243
|
+
|
|
244
|
+
In HC themes many surfaces resolve to near-identical values (e.g. `bg-muted` ≈ `bg-background` in HC dark, `bg-brand` gets desaturated). Any UI encoding state **only via fill/track color** (progress bars, quota gauges, chart ranges, meter pills, dashed reference lines, status pills) flattens into a single rectangle. The fix is to force distinct contrast in HC:
|
|
245
|
+
|
|
246
|
+
- **Track / container:** `hc:border hc:border-border hc:bg-background` + `forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]`
|
|
247
|
+
- **Active fill / progress:** `hc:bg-foreground` + `forced-colors:bg-[Highlight]`
|
|
248
|
+
- **Dashed marker / divider:** `hc:border-foreground` + `forced-colors:border-[CanvasText]`
|
|
249
|
+
- **Pill on colored bg:** invert to `hc:bg-background hc:text-foreground hc:border hc:border-foreground` + `forced-colors:bg-[Canvas] forced-colors:text-[CanvasText] forced-colors:border-[CanvasText]`
|
|
250
|
+
- **Disabled state:** `forced-colors:text-[GrayText]`
|
|
251
|
+
|
|
252
|
+
Never rely on `box-shadow` alone for separation in HC — shadows are suppressed; pair with a border.
|
|
253
|
+
|
|
254
|
+
Windows HC mode (and the CSS `forced-colors: active` media query) strips every custom `background-color`, `border-color`, and `box-shadow` and remaps text/icons to a small palette of system colors. Progress bars, pills, chart legends, avg-markers, and any "color-only" indicator collapse into a single flat rectangle if you rely purely on tokens like `bg-brand` or `bg-muted`.
|
|
255
|
+
|
|
256
|
+
**Rule:** for every UI that encodes state **via fill / track color** (progress bars, quota gauges, chart ranges, meter pills, status pills, dashed reference lines):
|
|
257
|
+
|
|
258
|
+
1. Keep the default `bg-*` tokens for light/dark themes.
|
|
259
|
+
2. Add `forced-colors:` variants mapping to system colors:
|
|
260
|
+
- **Track / container:** `forced-colors:bg-[Canvas] forced-colors:border forced-colors:border-[CanvasText]`
|
|
261
|
+
- **Active fill / progress:** `forced-colors:bg-[Highlight]` (text on it → `forced-colors:text-[HighlightText]`)
|
|
262
|
+
- **Dashed marker / divider:** `forced-colors:border-[CanvasText]`
|
|
263
|
+
- **Tooltip / pill on colored bg:** `forced-colors:bg-[Canvas] forced-colors:text-[CanvasText] forced-colors:border forced-colors:border-[CanvasText]`
|
|
264
|
+
- **Disabled state:** `forced-colors:text-[GrayText]`
|
|
265
|
+
3. Never rely on `box-shadow` alone for separation in HC — shadows are suppressed; pair with a border.
|
|
266
|
+
|
|
267
|
+
Reference fix: `components/dashboard-quota-progress-card.tsx` `StudentScoreProgressRow` — track → `Canvas`, fill → `Highlight`, avg-marker pill → `Canvas` + `CanvasText` border.
|
|
268
|
+
|
|
269
|
+
## Audit rules to track (examples)
|
|
270
|
+
|
|
271
|
+
| Severity | Rule | Criterion |
|
|
272
|
+
|----------|------|-----------|
|
|
273
|
+
| Critical | Certain ARIA roles must contain particular children | 1.3.1 |
|
|
274
|
+
| Serious | Touch targets must be ≥24px or spaced accordingly | 2.5.8 |
|
|
275
|
+
|
|
276
|
+
When fixing, re-run **axe** or your preferred checker on the **Placements** page after changing the Views toolbar.
|
|
277
|
+
|
|
278
|
+
## Charts (keyboard exploration)
|
|
279
|
+
|
|
280
|
+
- **Chart region:** Product charts that support arrow-key exploration use **`ChartFigure`** (`role="application"`, focus ring on the chart container, click-or-Tab to focus per `charts-overview`).
|
|
281
|
+
- **Selected datum:** Prefer **visible** feedback that matches the **`/dashboard` gallery** — Recharts **`activeBar`** + **`activeIndex`** for bars, **`activeShape`** + **`activeIndex`** for pies — via **`@/lib/chart-keyboard-selection`**. Avoid relying on **opacity-only** dimming as the only “selected” indicator; pair with ring/stroke so focus is perceivable (WCAG **2.4.7 Focus Visible** where applicable).
|
|
282
|
+
- **Tables under charts:** **`ChartDataTable`** (`sr-only`) provides an equivalent programmatic structure for screen-reader users.
|