Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

46 KiB
Raw Blame History

Scratchpad — Shared Keyboard Shortcuts System

  • Plan slug: 2026-05-18-keyboard-shortcuts-shared-system
  • Created: 2026-05-18
  • Source design doc: .ai/keyboard-shortcuts-shared-system-plan.md

What This Is

Working memory for the shared keyboard shortcuts effort. Append discoveries and decisions; update earlier notes when a decision changes.

Decisions

  • (2026-05-18) Scope = all 6 phases incl. chord/sequence engine and visible kbd hints + aria-keyshortcuts. (User answer.)
  • (2026-05-18) i18n = English + all 8 production languages (en, fr, es, de, nl, it, pl, pt) translated in this plan, + pseudo xx/yy regenerated, + validate-translations.cjs must pass. (User answer.)
  • (2026-05-18) mod+k = stays global search; asset command palette rescoped to page-scoped assets.commandPalette with a non-mod+k default. (User answer; settles the source-doc open question, hard prereq for P2.)
  • (2026-05-18) Matcher = dual matching: code-kind tokens → event.code + exact modifier set; char-kind tokens (e.g. ?) → event.key produced char. Rationale: fixes macOS Option-as-dead-key (event.key becomes Dead/˜) and international layouts (Cyrillic/German mod+s).
  • (2026-05-18) Persist deltas only in platform-neutral syntax; resolve mod/platform per device at runtime; drop a user value that equals the current platform default. Never persist resolved (cmd+/ctrl+) bindings.
  • (2026-05-18) Respect event.defaultPrevented (reversed from original source draft) so the global system does not double-fire against ~40 component-local arrow/Enter handlers.
  • (2026-05-18) Engine lives in packages/ui/src/keyboard-shortcuts/*, kept dependency-light; preference wiring lives in the MSP wrapper via useUserPreference (@alga-psa/user-composition/hooks).
  • (2026-05-19) Dependency-boundary guard (group architecture-guard, F005-F007/T012-T014, placed right after scaffold so it lands before persistence). Engine persists ONLY via a ShortcutStorage interface (F005) + default in-memory adapter (F006); provider takes an injected storage (F043); the useUserPreference adapter lives in server and is injected (F140 reworded). Rationale: importing useUserPreference into packages/ui creates ui → user-composition → ui, a NEW cycle that fails .github/workflows/circular-deps.yml (not in known-cycles.json). Guard (F007) + T012 (no forbidden imports) + T013 (nx graph no new cycle) enforce it. Repo also has eslint-plugin-custom-rules/ no-feature-to-feature-imports (CI error).
  • (2026-05-18) i18n namespace = new msp/keyboard-shortcuts (action labels, groups, help, settings-panel chrome). Settings tab label goes in existing msp/settings. Action labelKey/groupKey are i18n keys from Phase 1 (no raw strings, no Phase-6 retrofit).
  • (2026-05-18) Settings UI = new tab in SettingsPage.tsx baseTabContent, component KeyboardShortcutsSettings.tsx, preference-backed (immediate debounced save, no explicit Save button), shared components only.
  • (2026-05-18) Commit policy = one commit per commitGroup (not per item). Every feature/test carries a commitGroup; 15 groups: scaffold, parser, matcher, registry, sequence, radix-escape, action-catalog, global-migration, panels-drawers, editors, persistence, settings-ui, help-a11y, i18n, regression → ≈15 commits. Commit a group only when all its items are implemented:true. Message form: <type>(<group>): summary [ids].
  • (2026-05-19) Added 3 net-new groups from manual-test feedback: page-actions (page-scoped page.create/page.save registered per page; default create = c, NOT mod+n — Ctrl/⌘+N is a browser new-window accelerator not reliably interceptable, esp. Chrome/Windows; mod+n is an opt-in alternate with a caveat), dialog-a11y (every page's create dialog keyboard-only: focus-on-open, trap, mod+Enter submit, Escape, SR roles), command-palette (Spotlight on mod+k — resolves the old open decision; record search now lives inside the palette; asset palette stays mod+shift+k). Palette grammar = TeamCity-derived (see PRD Appendix: field:value + aliases, quoted phrases, -/NOT, */?, prefix+OR default, AND in-field, $magic, sigils > # @ /). Parser is a pure module under the same FR30 boundary guard. F300-F353 / T300-T349.
  • (2026-05-19) Code review caught a severe wiring gap: customization is cosmetic. Provider accepts storage?/disabledActionIds? but MspLayoutClient passes neither (falls back to in-memory). Dispatch (collectSingleChord/Sequence) resolves from normalizeDefaultBindings, never resolveActionBindings; display.tsx uses getDefaultBindingsForPlatform only. So a rebind persists + shows in the settings Effective column but does nothing; old default still fires; disabled list never reaches dispatch; hints/aria stay on defaults. Root cause: no integration test. Every piece had a green unit/contract test in isolation; the architecture-guard checked the import boundary but nothing checked the functional wiring → F043/F140/F142/F146/F185/F186 were flipped true on unit-green. Corrective: reopened those 6 (commitGroup→customization-wiring), added F360-F366 (inject storage at mount; provider = single source; dispatch via resolveActionBindings; merge disabled; context resolver; settings consumes context) + T360-T364 including T360, the end-to-end test that would have caught it. FR34 added: unit-green is NOT sufficient to mark FR19/20/23/26 done.
  • Process lesson: any "engine + adapter + UI" feature needs one integration test across the seam before its features can be marked done; isolated unit tests passing is a false signal.

Discoveries / Constraints

  • (2026-05-19) GAP found in manual test: catalog.ts defined navigation.goTickets/goAssets/goClients (sequence g t/a/c) but no component registered handlersg t was a silent no-op. Source plan had nav as "illustrative"; sequence group built the engine only. Fixed: registered the 3 nav actions in DefaultLayout.tsx with router.push (navigation commitGroup, F095 impl, T095 e2e pending).
  • (2026-05-18) ~50 files use keydown/onKeyDown; only window/document-level handlers are in scope. Component-local widget handlers (DatePicker, SearchableSelect, TagInput, comboboxes, Radix internals) stay as-is.
  • (2026-05-18) Two DrawerContexts: server/src/context/DrawerContext.tsx AND packages/ui/src/context/DrawerContext.tsx — migrate together or they fight.
  • (2026-05-18) Alt+ArrowLeft/Right (TicketNavigation, DrawerContext) = browser Back/Forward on Windows/Linux → change record nav default to [/].
  • (2026-05-18) DefaultLayout.tsx:215/229 mod+l/mod+ArrowUp gated on aiAssistantAvailable (early-return before preventDefault) — preserve via the registry enabled flag.
  • (2026-05-18) AssetDashboardClient.tsx:285 window mod+k listener = the conflict source; remove on rescope.
  • (2026-05-18) Radix Dialog/Drawer Escape is managed via onEscapeKeyDown + stopPropagation and ModalityContext/InsideDialogContext nesting fix (see global memory). Escape actions must integrate, not add a competing window listener.
  • (2026-05-18) Settings shell: server/src/components/settings/SettingsPage.tsxCustomTabs + UnsavedChangesProvider; tabs are a hard-coded baseTabContent array ({id,label,icon,content}); add a tab there.
  • (2026-05-18) Settings panel analogs: server/src/components/settings/general/ExperimentalFeaturesSettings.tsx (toggle list), GeneralSettings.tsx (Table of items with controls).
  • (2026-05-18) Shared component import paths: @alga-psa/ui/components/{Card,Table,Button,Switch,Dialog,ConfirmationDialog,LoadingIndicator,Alert,Input,Label,CustomSelect}; feedback via react-hot-toast + @alga-psa/ui/lib/errorHandling#handleError. Button variants: default|destructive|outline|ghost|soft; every interactive element needs an id.
  • (2026-05-18) i18n: i18next + react-i18next. Client hook useTranslation('namespace') from @alga-psa/ui/lib/i18n/client; server getServerTranslation/getServerLocale from @alga-psa/ui/lib/i18n/serverOnly. Locale files server/public/locales/{lang}/{namespace}.json (nested JSON, dot keys). Languages: en, fr, es, de, nl, it, pl, pt (+ xx, yy pseudo).
  • (2026-05-18) i18n workflow: add EN keys → mirror to 7 langs → regenerate pseudo (node scripts/generate-pseudo-locales.cjs) → validate (node scripts/validate-translations.cjs) → register namespace in ROUTE_NAMESPACES (packages/core/src/lib/i18n/config.ts).
  • (2026-05-18) useUserPreference already does default→localStorage→server, 500ms debounce, hydration-mismatch avoidance, skipServerFetch/ isUserLoggedIn unauth path — it is THE persistence impl, not re-invented.
  • (2026-05-18) Plan-folder convention: docs/plans/YYYY-MM-DD-<slug>/ with PRD.md/features.json/tests.json/SCRATCHPAD.md (matches docs/plans/2026-05-13-threaded-comments/).

Commands / Runbooks

  • Pseudo-locales: node scripts/generate-pseudo-locales.cjs
  • Validate translations: node scripts/validate-translations.cjs
  • Pseudo QA: switch locale to xx (expect 11111…) or yy (55555…) to confirm all strings are extracted.
  • Source: .ai/keyboard-shortcuts-shared-system-plan.md
  • Engine target: packages/ui/src/keyboard-shortcuts/*
  • Settings shell: server/src/components/settings/SettingsPage.tsx
  • Persistence: packages/user-composition/src/hooks/useUserPreference.ts
  • i18n config: packages/core/src/lib/i18n/config.ts
  • Locale root: server/public/locales/
  • Global memory: Radix Dialog nesting / ModalityContext escape fix.

Work Log

  • (2026-05-19) Completed scaffold group F001-F004/T026. Added packages/ui/src/keyboard-shortcuts/ with a barrel, core public types (ShortcutAction, scopes, binding descriptors, parse result, persisted shortcut blob), and client-only platform detection helpers. Added package exports and a tsup entry for @alga-psa/ui/keyboard-shortcuts. Detection now returns null when window is absent so SSR/node paths do not inspect navigator; useClientPlatform resolves after mount.
  • (2026-05-19) Verification for scaffold: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/platform.test.ts from packages/ui passed (3 tests).
  • (2026-05-19) Completed parser group F010-F019/T001-T011. Added parser.ts with typed-result parseBinding and parseSequence. Supported physical code tokens for letters, digits, F1-F12, named navigation keys, and brackets; glyphs like ? parse as character tokens. Modifier normalization is deterministic (mod, ctrl, meta, alt, shift), and literal ctrl/meta remain distinct from neutral mod.
  • (2026-05-19) Verification for parser: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/parser.test.ts from packages/ui passed (27 tests).
  • (2026-05-19) Completed matcher group F020-F025/T020-T025. Added matcher.ts with platform-time mod resolution, exact modifier matching for code tokens, produced-character matching for char tokens with Shift ignored only as the glyph-producing modifier, and AltGraph protection. Tests cover mac/other mod, macOS Option dead-key behavior, international-layout physical-code matching, and AltGr false-positive prevention.
  • (2026-05-19) Verification for matcher: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/matcher.test.ts from packages/ui passed (6 tests).
  • (2026-05-19) Completed registry group F030-F042/T030-T044/T234. Added ShortcutRegistry, normalizeDefaultBindings, and a client KeyboardShortcutsProvider with one capture-phase document listener. Hooks now register actions, scopes, and active regions without adding listeners. Dispatch skips defaultPrevented, honors enabled/disabled gates, suppresses editable targets unless opted in, filters by active scopes, uses priority then most-local active scope, and reports residual ties through onConflict.
  • (2026-05-19) Route-change clearing is represented in the UI package by a provider routeKey prop so MSP can pass pathname later without coupling the package to Next routing. Active-region gating covers selection.* and unmodified single-letter page actions.
  • (2026-05-19) Verification for registry: npx vitest run --config vitest.config.ts src/keyboard-shortcuts from packages/ui passed (57 tests). npx tsc --noEmit -p packages/ui/tsconfig.json passed.
  • (2026-05-19) PRD/checklist was updated outside my prior commits with new FR30 dependency-boundary requirements and a late F043 storage-adapter item. Moved that late item from the already-committed registry group to a new architecture-boundary group to preserve the one-commit-per-group rule, and added F044/T045-T047 to cover the boundary guard and CI path.
  • (2026-05-19) Completed architecture-boundary group F043-F044/T045-T047. Added ShortcutStorage, default in-memory storage, provider injection via storage prop/context, and useShortcutStorage. Added scripts/guard-keyboard-shortcuts-boundary.mjs plus a package script and circular-deps workflow step. The guard blocks imports from @alga-psa/user-composition and feature packages in packages/ui/src/keyboard-shortcuts, and delegates graph checks to scripts/check-circular-deps.mjs --baseline .github/known-cycles.json when --graph is supplied.
  • (2026-05-19) Verification for architecture-boundary: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/storage.test.tsx from packages/ui passed (2 tests); node --test scripts/tests/guard-keyboard-shortcuts-boundary.test.mjs passed (2 tests); npm run guard:keyboard-shortcuts-boundary passed; npx tsc --noEmit -p packages/ui/tsconfig.json passed.
  • (2026-05-19) Completed sequence group F050-F054/T050-T054. The provider now evaluates sequence actions through the same delegated listener, with a configurable sequenceTimeoutMs (default 1000ms). Sequence buffers reset on timeout, non-match, scope push/pop, route change, and editable targets. Prefixes do not block single-chord actions; a full sequence prevents default only when its handler reports handled.
  • (2026-05-19) Verification for sequence: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/sequence.test.tsx src/keyboard-shortcuts/provider.test.tsx from packages/ui passed (24 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed.
  • (2026-05-19) Completed radix-escape group F060-F062/T060-T062. Added a ref-counted Radix Escape owner bridge (useRadixEscapeOwner) and wired shared Dialog/Drawer to mark ownership while open. The provider skips Escape dispatch while any Radix owner exists, so document-capture shortcuts cannot race Radix onEscapeKeyDown or nested stopPropagation. When no Radix owner exists, panel.close/Escape actions still dispatch normally.
  • (2026-05-19) Verification for radix-escape: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/escape.test.tsx src/keyboard-shortcuts/provider.test.tsx from packages/ui passed (22 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed.
  • (2026-05-19) Completed action-catalog group F070-F071/T070-T071. Added catalog.ts with stable metadata entries for global, AI, page, selection, navigation sequence, assets, dialog/panel/drawer, record, and editor actions. Each entry carries labelKey/groupKey, scope, priority, defaults, and flags like sequence/allowInEditable; editor.redo resolves to mod+shift+z on mac and ctrl+y/ctrl+shift+z elsewhere.
  • (2026-05-19) Verification for action-catalog: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/catalog.test.ts from packages/ui passed (2 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed.
  • (2026-05-19) Completed global-migration group F080-F090/T080-T089/T230. Mounted KeyboardShortcutsProvider in MspLayoutClient around the MSP product shells (not auth/client portal). Migrated global.search out of SearchPalette's window listener; migrated global.toggleChat and ai.quickAsk out of DefaultLayout's window listener while preserving aiAssistantAvailable gates; registered global.openShortcuts and global.quickCreate; and rescoped the assets palette to page-scoped assets.commandPalette on mod+shift+k.
  • (2026-05-19) Temporary note: global.openShortcuts currently opens a minimal placeholder dialog in DefaultLayout; the full shared help dialog remains owned by the later help-a11y group. global.quickCreate opens the ticket quick-create dialog by default.
  • (2026-05-19) Verification for global-migration: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/global-migration.contract.test.ts from packages/ui passed (5 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed; npx tsc --noEmit -p packages/assets/tsconfig.json passed; npm run typecheck --workspace server passed. Attempting direct server layout Vitest files hit an existing Vitest 4/coverage-v8 3.2.4 harness error before tests executed (Cannot read properties of undefined (reading 'fetchCache')).
  • (2026-05-19) Completed panels-drawers group F100-F108/T100-T107. Both server/src/context/DrawerContext.tsx and packages/ui/src/context/DrawerContext.tsx now register panel.close, drawer.historyBack, and drawer.historyForward; both push panel scope only while open. TicketNavigation now registers record.previous/next on [/] and removed its Alt+Arrow window listener. The catalog keeps Alt+ArrowLeft/Right only in OPTIONAL_ALTERNATE_BINDINGS.
  • (2026-05-19) Verification for panels-drawers: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/panels-drawers.contract.test.ts src/keyboard-shortcuts/provider.test.tsx src/keyboard-shortcuts/catalog.test.ts from packages/ui passed (25 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed; npx tsc --noEmit -p packages/tickets/tsconfig.json passed; npm run typecheck --workspace server passed.
  • (2026-05-19) Completed editors group F120-F124/T120-T124. Invoice designer useDesignerShortcuts.ts now pushes editor scope and registers undo, redo, delete selection, cancel, and arrow-move actions instead of a window keydown listener. TextEditor pushes editor scope and marks the BlockNote root with data-keyboard-shortcuts-editor-root="true" but does not register undo/redo, so BlockNote internal editing shortcuts remain local. Search did not find a separate workflow-designer window-level shortcut hook to migrate in this group.
  • (2026-05-19) Verification for editors: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/editors.contract.test.ts src/keyboard-shortcuts/catalog.test.ts from packages/ui passed (5 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed; npx tsc --noEmit -p packages/billing/tsconfig.json passed.
  • (2026-05-19) Completed persistence group F140-F147/T140-T150. Added dependency-free preference utilities under packages/ui for keyboard_shortcuts_v1, version migration, neutral delta storage, override/default resolution, drop-equals-default, disabled action ids, and hostile/reserved combo advisories. Added the MSP bridge hook server/src/hooks/useKeyboardShortcutPreferenceStorage.ts, which uses useUserPreference with localStorage and skipServerFetch support.
  • (2026-05-19) Verification for persistence: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/preferences.test.ts src/keyboard-shortcuts/persistence-bridge.contract.test.ts from packages/ui passed (6 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed; npm run typecheck --workspace server passed.
  • (2026-05-19) Completed settings-ui group F160-F172/T160-T174. Added KeyboardShortcutsSettings as a settings tab with shared Card/Table/Switch/ Button/LoadingIndicator/ConfirmationDialog components. The panel lists catalog actions by group, captures bindings inline, writes preference deltas immediately through useKeyboardShortcutPreferenceStorage, supports disable, reset one, reset all, and conflict confirmation, and uses toast/handleError.
  • (2026-05-19) Verification for settings-ui: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/settings-ui.contract.test.ts from packages/ui passed (3 tests); npm run typecheck --workspace server passed.
  • (2026-05-19) Completed help-a11y group F180-F187/T180-T187. Added shared Kbd, ShortcutHint, shortcut formatting, aria-keyshortcuts conversion, and ShortcutHelpDialog. The search input now exposes aria-keyshortcuts and a visible hint, and global.openShortcuts renders the shared help dialog instead of the temporary placeholder.
  • (2026-05-19) Verification for help-a11y: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/display.test.tsx from packages/ui passed (3 tests); npx tsc --noEmit -p packages/ui/tsconfig.json passed; npm run typecheck --workspace server passed.

Open Questions

  • Final assets.commandPalette default binding after rescope (confirm in P2).
  • Whether any editor action needs allowInEditable exceptions beyond designer canvases (confirm in P4 against useDesignerShortcuts.ts).
  • Exact set of controls to receive visible kbd hints / aria-keyshortcuts (curate during P6 — avoid hint clutter).

2026-05-19 — i18n group implementation

  • F200: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F201: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F202: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F203: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F204: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F205: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F206: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F207: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F208: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F209: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F210: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F211: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F212: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F213: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F214: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F215: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • F216: Implemented in commitGroup i18n. Added the keyboard-shortcuts namespace, settings tab key, route preload, pseudo-locale generation support, and localized platform/advisory/help/settings/action strings as applicable.
  • T200: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T201: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T202: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T203: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T204: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T205: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T206: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T207: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T208: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • T209: Verified in commitGroup i18n via the keyboard shortcuts i18n contract test plus pseudo-locale generation and validate-translations.cjs.
  • Checks: node scripts/validate-translations.cjs passed with pre-existing Polish plural warnings only; npx vitest run --config vitest.config.ts src/keyboard-shortcuts/i18n.contract.test.ts src/keyboard-shortcuts/display.test.tsx passed; npx tsc --noEmit -p packages/ui/tsconfig.json passed.
  • Additional check: npm run typecheck --workspace server passed for i18n wiring.

2026-05-19 — architecture-guard checklist reconciliation

  • F005: Verified existing ShortcutStorage adapter/default memory storage/dependency guard implementation and marked complete under the current architecture-guard checklist ids.
  • F006: Verified existing ShortcutStorage adapter/default memory storage/dependency guard implementation and marked complete under the current architecture-guard checklist ids.
  • F007: Verified existing ShortcutStorage adapter/default memory storage/dependency guard implementation and marked complete under the current architecture-guard checklist ids.
  • T012: Verified with guard/storage tests and Nx graph baseline check; marked complete under the current architecture-guard checklist ids.
  • T013: Verified with guard/storage tests and Nx graph baseline check; marked complete under the current architecture-guard checklist ids.
  • T014: Verified with guard/storage tests and Nx graph baseline check; marked complete under the current architecture-guard checklist ids.
  • Checks: node --test scripts/tests/guard-keyboard-shortcuts-boundary.test.mjs, npx vitest run --config vitest.config.ts src/keyboard-shortcuts/storage.test.tsx, and npx nx graph --file=/tmp/project-graph.json && node scripts/guard-keyboard-shortcuts-boundary.mjs --graph /tmp/project-graph.json passed.

2026-05-19 — regression group implementation

  • F230: Added final regression contract coverage asserting migrated legacy shortcut listeners remain removed after replacement actions are present.
  • F231: Added SSR/client-only regression coverage for platform-sensitive shortcut components and navigator access ordering.
  • F232: Added regression coverage proving DatePicker, SearchableSelect, AsyncSearchableSelect, TagInput, and TagInputInline keep local onKeyDown handling and do not register shared shortcut actions.
  • T231: Covered by regression.contract.test.ts widget-local assertions.
  • T232: Covered by regression.contract.test.ts client-only/platform assertions plus existing platform.test.ts.
  • T233: Covered by regression.contract.test.ts migrated legacy-listener assertions plus phase contract tests.
  • Checks: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/regression.contract.test.ts src/keyboard-shortcuts/global-migration.contract.test.ts src/keyboard-shortcuts/panels-drawers.contract.test.ts src/keyboard-shortcuts/editors.contract.test.ts src/keyboard-shortcuts/platform.test.ts passed; npx tsc --noEmit -p packages/ui/tsconfig.json passed.

2026-05-19 — Gap analysis (post-implementation review) → commitGroup gap-hardening

A full end-to-end read of the branch surfaced structural gaps. Finding #1 was already captured by the prior customization-wiring group (F360-F366 / T360-T364, FR34) — verified, not re-added. The remaining three findings are added as F367-F373 / T365-T373 under commitGroup gap-hardening (FR35/FR36/FR37). The loop should not commit this group until all of its items are implemented: true (one-commit-per-group rule).

  • (FR35, F367-F369/T365-T366) Catalog is not the source of truth. catalog.ts carries scope/priority/bindings, but registration sites hand-author ShortcutAction literals. Concrete divergence: only useDesignerShortcuts sets priority: 60; DefaultLayout, both DrawerContext, SearchPalette, TicketNavigation, AssetDashboardClient omit priority so the provider uses ?? 0, contradicting catalog.ts DEFAULT_PRIORITY (panel 40 / page 20 / editor 60). The settings UI/help show catalog priorities; dispatch uses different ones. Fix = a catalog-derived factory (id + handler only) + a drift guard (unit + extend guard-keyboard-shortcuts-boundary.mjs). This is distinct from FR30 (import boundary), which the guard already enforces correctly.

  • (FR36, F370-F371/T367-T368) Active-region gating is a no-op. DefaultLayout calls useShortcutActiveRegion(true) unconditionally, so activeRegionsRef is always non-empty while the MSP shell is mounted → requiresActiveRegion() never gates anything. global.quickCreate (c), selection.next (j), selection.previous (k) fire on any non-editable focus app-wide, the opposite of FR10's intent. Fix = register an active region only from genuine roving-focus list/selection containers via a shared wrapper/hook.

  • (FR37, F372-F373/T369-T373) Contract tests are source greps, not behavioral. Every *.contract.test.ts (global-migration, panels-drawers, editors, persistence-bridge, settings-ui, regression, i18n) is readFileSync + .toContain('...'). They assert code exists, never that it works — which is why findings #1-#3 shipped "green". T360-T364 already add behavioral coverage for the customization path; gap-hardening converts the remaining grep suites to behavioral and adds a meta test-guard so a new grep-only contract test fails CI.

  • Uncommitted working tree at review time: DefaultLayout.tsx carries the F095 nav handlers (legit, not yet committed); features.json/tests.json have incidental \uXXXX escape churn from a JSON re-serializer (the new gap-hardening items are authored in plain ASCII to avoid adding to it). These are pre-existing and out of scope for gap-hardening — flag for the branch owner, do not bundle into this group's commit.

2026-05-19 — navigation group verification

  • T095: Added packages/ui/src/keyboard-shortcuts/navigation.test.tsx to exercise the catalogued navigation.goTickets/goAssets/goClients sequence actions through KeyboardShortcutsProvider. The test verifies g t, g a, and g c dispatch to their handlers and that the same sequence is suppressed while typing in an input.

2026-05-19 — customization-wiring group implementation

  • F043/F140/F142/F146/F185/F186/F360-F366: Connected the preference adapter end-to-end. MspLayoutClient now injects useKeyboardShortcutPreferenceStorage into KeyboardShortcutsProvider; the provider loads the persisted blob, keeps it as reactive context state, resolves dispatch through resolveActionBindings, merges preferences.disabled, and exposes resolved bindings plus set/disable/reset mutators. ShortcutHint, useAriaKeyShortcuts, ShortcutHelpDialog, and KeyboardShortcutsSettings now read the same provider-owned source instead of separate defaults/preference hooks.
  • T360-T363: Added packages/ui/src/keyboard-shortcuts/customization-wiring.test.tsx. The test covers rebind -> new combo dispatches and old default stops, Effective + ShortcutHint + aria-keyshortcuts update, disable stops dispatch and hides help, reset-one/reset-all live-update, and persisted storage loads before dispatch.
  • T364/checks: npx nx graph --file=/tmp/project-graph.json && node scripts/guard-keyboard-shortcuts-boundary.mjs --graph /tmp/project-graph.json passed. Focused tests passed: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/customization-wiring.test.tsx src/keyboard-shortcuts/provider.test.tsx src/keyboard-shortcuts/storage.test.tsx src/keyboard-shortcuts/display.test.tsx src/keyboard-shortcuts/settings-ui.contract.test.ts src/keyboard-shortcuts/persistence-bridge.contract.test.ts src/keyboard-shortcuts/global-migration.contract.test.ts. Type checks passed: npx tsc --noEmit -p packages/ui/tsconfig.json and npm run typecheck --workspace server.

2026-05-19 — page-actions group implementation

  • F300-F302/F310: Added page.create to the catalog with default c, kept mod+n only as an optional alternate with a browser-owned new-window caveat, and added shared usePageCreateShortcut/usePageSaveShortcut helpers in packages/ui/src/keyboard-shortcuts/page-actions.ts. Page-scoped actions are now suppressed when panel/dialog/editor scope is active, in addition to the existing editable-target suppression.
  • F303-F308: Wired page create shortcuts to existing create controls: TicketingDashboard (ticket), Clients (client), Contacts (contact), InteractionsFeed (interaction), Projects (project), and AssetDashboardClient (asset).
  • F309: Wired page save to primary editable Save controls found in this pass: ClientDetails (handleSave) and ticket TicketInfo (handleSaveChanges). I did not find a route-level project-detail Save equivalent; project detail appears to save scoped phase/task edits through local controls.
  • F311/T304: Added actions.page.create label/description to en/fr/es/de/nl/it/pl/pt and regenerated xx/yy pseudo-locales.
  • Tests/checks: Added page-actions.test.tsx for catalog/default/optional alternate behavior, create/save dispatch, editable suppression, and panel-scope suppression. Added page-actions.source.test.ts to smoke the page component wiring. Passed: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/page-actions.test.tsx src/keyboard-shortcuts/page-actions.source.test.ts src/keyboard-shortcuts/provider.test.tsx src/keyboard-shortcuts/catalog.test.ts src/keyboard-shortcuts/i18n.contract.test.ts; node scripts/validate-translations.cjs (pre-existing Polish plural warnings only); npx tsc --noEmit -p packages/ui/tsconfig.json; npx tsc --noEmit -p packages/tickets/tsconfig.json; npx tsc --noEmit -p packages/clients/tsconfig.json; npx tsc --noEmit -p packages/projects/tsconfig.json; npx tsc --noEmit -p packages/assets/tsconfig.json; and npm run typecheck --workspace server.

2026-05-19 — dialog-a11y group implementation

  • F320-F324/F330: Moved the shared create-dialog keyboard contract into packages/ui/src/components/Dialog.tsx: store/restore invoker focus, focus the first focusable field on open when no custom onOpenAutoFocus is supplied, keep Radix modal focus trapping enabled, and submit the first form on Ctrl/Cmd+Enter even from textarea/editor-like fields. Escape remains routed through the existing Radix owner bridge.
  • F325-F329: Removed disableFocusTrap from Create Ticket, Create Client, Create Contact, Create Project, and Create Asset dialogs. Create Interaction was already on the focus-trapped shared Dialog path.
  • T320-T326: Added dialog-a11y.test.tsx for first-field focus, invoker focus restore, Escape close, and mod+Enter form submit. Added dialog-a11y.source.test.ts to smoke that all create dialogs use shared Dialog, keep forms, and no longer opt out of focus trapping.
  • Checks passed: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/dialog-a11y.test.tsx src/keyboard-shortcuts/dialog-a11y.source.test.ts src/keyboard-shortcuts/escape.test.tsx; npx tsc --noEmit -p packages/ui/tsconfig.json; npx tsc --noEmit -p packages/tickets/tsconfig.json; npx tsc --noEmit -p packages/clients/tsconfig.json; npx tsc --noEmit -p packages/projects/tsconfig.json; npx tsc --noEmit -p packages/assets/tsconfig.json; and npm run typecheck --workspace server.

2026-05-19 — command-palette group implementation

  • F340-F353: Implemented the command palette overlay opened by global.search (mod+k) from SearchPalette. The overlay merges navigation menu entries, catalog shortcut actions, and existing record typeahead search; supports keyboard navigation/activation, Escape close, localStorage frequency boosts, accessible combobox/listbox roles, live result counts, visible mod+k hint, and in-palette syntax help.
  • F343-F347: Added pure UI-decoupled packages/ui/src/keyboard-shortcuts/command-palette-query.ts parser with field aliases, sigils, quoted phrases, exclusion, wildcards, fuzzy suffixes, OR/AND metadata, and $mine/$recent/$open aliases.
  • F350/F352: Added command palette chrome/syntax/type labels across en/fr/es/de/nl/it/pl/pt, regenerated pseudo locales, and added a shortcut help-dialog link to the palette syntax affordance.
  • T340-T349: Covered by parser unit tests, command palette source wiring smoke, translation validation, server/UI typecheck, and the existing keyboard shortcut dependency-boundary/graph guard.
  • Checks passed: npx vitest run --config vitest.config.ts src/keyboard-shortcuts/command-palette-query.test.ts src/keyboard-shortcuts/command-palette.source.test.ts; npx tsc --noEmit -p packages/ui/tsconfig.json; npm run typecheck --workspace server; node scripts/generate-pseudo-locales.cjs; node scripts/validate-translations.cjs (pre-existing Polish plural warnings only); and npx nx graph --file=/tmp/project-graph.json && node scripts/guard-keyboard-shortcuts-boundary.mjs --graph /tmp/project-graph.json.

2026-05-19 — gap-hardening group implementation

  • F367-F369: Added catalog-derived createShortcutAction and useCatalogShortcut, then migrated SearchPalette, DefaultLayout, both DrawerContexts, TicketNavigation, AssetDashboardClient, and invoice designer shortcuts away from hand-authored metadata. The boundary guard now also checks these registration sites for hand-authored shortcut metadata and unknown catalog IDs.
  • F370-F371: Removed the unconditional useShortcutActiveRegion(true) from DefaultLayout. Added shared ShortcutActiveRegion, which registers an active region only while focus is within the real list region, and applied it to ticket/client/contact/interaction/project/asset list surfaces.
  • F372-F373: Added behavioral gap-hardening coverage for global, panel, editor, active-region, and settings preference behavior; added a contract-test guard so grep-only contract tests must carry behavioral coverage. Existing source smoke tests were updated to assert the catalog-hook wiring rather than stale metadata literals.
  • Checks passed: npx vitest run --config vitest.config.ts src/keyboard-shortcuts; node --test scripts/tests/guard-keyboard-shortcuts-contract-tests.test.mjs scripts/tests/guard-keyboard-shortcuts-boundary.test.mjs; node scripts/guard-keyboard-shortcuts-contract-tests.mjs; npx tsc --noEmit -p packages/ui/tsconfig.json; npx tsc --noEmit -p packages/tickets/tsconfig.json; npx tsc --noEmit -p packages/clients/tsconfig.json; npx tsc --noEmit -p packages/assets/tsconfig.json; npx tsc --noEmit -p packages/projects/tsconfig.json; npx tsc --noEmit -p packages/billing/tsconfig.json; npm run typecheck --workspace server; and npx nx graph --file=/tmp/project-graph.json && node scripts/guard-keyboard-shortcuts-boundary.mjs --graph /tmp/project-graph.json && node scripts/guard-keyboard-shortcuts-contract-tests.mjs.

2026-05-20 — shortcuts-ui-redesign (Profile move + visual keyboard cheatsheet)

Built directly (not via loop) because the work is high-fidelity visual + judgment-heavy and the loop has no signal for either. Source: design handoff in ~/Downloads/design_handoff_keyboard_shortcuts/ (variation-c is canonical).

  • Root cause of "no nav tab" turned out NOT to be the AlgaDesk allowlist (user is on full PSA). The real cause: SettingsPage.tsx does not render the tab strip itself — the settings nav is driven by the Sidebar in settings mode (Sidebar.tsx:111, DefaultLayout.tsx:52), a separate curated menu that never got a keyboard-shortcuts entry. The content route worked, but there was nothing to click. settings-ui.contract.test.ts was a grep that only asserted the id/icon/<Component/> strings were present in SettingsPage source, never that the nav surfaced it — another FR37-class miss the gap-hardening group didn't cover (it converted engine contract tests, not the server-side settings one).
  • Decision: move to Profile sub-tab (matches per-user preference convention; uses real <CustomTabs tabs={tabContent}> so the tab strip appears immediately; sidesteps any allowlist). Added entry to UserProfile.tsx tabContent, deleted KeyboardShortcutsSettings.tsx and its SettingsPage registration + Keyboard lucide import. Added 'keyboard-shortcuts' to BASE_PROFILE_TABS (calendarAvailability.ts) so /msp/profile?tab=keyboard-shortcuts deep-links.
  • Engine: extended PersistedShortcuts to v2 with profile: string and a v1→v2 migration; SHORTCUT_PROFILES ships default / vim / emacs with parser-valid neutral single-chord deltas keyed by real catalog ids. Multi-chord emacs sequences (mod+x mod+s) were deliberately NOT assigned to non-sequence actions (page.save etc.) — they would parse-fail and silently never dispatch. Resolution = user override → profile delta → platform default, inside resolveActionBindings so dispatch + hints + ARIA + panel all read the same effective binding. setActionBindingsDelta now drops overrides equal to the profile baseline, so per-action reset returns to the active profile (not raw factory). Provider exposes profile + setProfile; useKeyboardShortcutPreferences includes them. Vim/Emacs deltas are best-guess pending team confirmation (open Q in handoff).
  • Panel: server/src/components/keyboard-shortcuts/KeyboardShortcutsPanel.tsx recreates variation-c using product CSS vars (--color-primary-*, --color-card, --radius-md, --font-mono — globals.css already defines these) + Radix-based ConfirmationDialog/Switch/LoadingIndicator. Real keydown capture via window-level capture-phase listener with stopImmediatePropagation so the provider dispatch doesn't double-fire during rebind. Conflict UX = prompt-to-reassign (the new binding takes over, the previous owner is left unbound) per user decision. Override scope = per-account only (useUserPreference); per-device deferred. Unmappable keys (?, Escape, Delete, sequences) route to the chord rail instead of disappearing — improvement over the prototype.
  • Tests: added profiles.test.ts (7 behavioral cases for the profile layer). Rewrote settings-ui.contract.test.ts to assert the new placement + panel wiring (with @behavioralCoverage linking the real behavioral suites). Updated i18n.contract.test.ts to assert profile.tabs.keyboardShortcuts in en/msp/profile.json and that SettingsPage no longer references the removed tab. Added the key to all production+pseudo locales for parity and ran generate-pseudo-locales.cjs + validate-translations.cjs (0 errors, 8 pre-existing Polish plural warnings).
  • Verification: npx vitest run --config vitest.config.ts src/keyboard-shortcuts → 29 files / 137 tests pass; npx tsc --noEmit -p packages/ui/tsconfig.json clean; npm run typecheck --workspace server clean; node scripts/guard-keyboard-shortcuts-boundary.mjs OK; meta contract-test guard OK; node scripts/validate-translations.cjs PASSED.
  • Follow-up (F386/T386, implemented:false): translate panel chrome strings (settings.*, profiles.*, legend.*, settings.chords.*, settings.conflict.*, settings.actions.*) into the 7 non-EN production locales; today the panel falls back via t(..., { defaultValue }) outside EN. Per-device override scope from the handoff is also deferred.
  • Visual QA: not driven here. Browser/conduit QA pending with the user; the prototype's prototype/index.html is the side-by-side reference.