Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
46 KiB
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
kbdhints +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.cjsmust pass. (User answer.) - (2026-05-18)
mod+k= stays global search; asset command palette rescoped to page-scopedassets.commandPalettewith a non-mod+kdefault. (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.keyproduced char. Rationale: fixes macOS Option-as-dead-key (event.keybecomesDead/˜) and international layouts (Cyrillic/Germanmod+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 viauseUserPreference(@alga-psa/user-composition/hooks). - (2026-05-19) Dependency-boundary guard (group
architecture-guard, F005-F007/T012-T014, placed right afterscaffoldso it lands beforepersistence). Engine persists ONLY via aShortcutStorageinterface (F005) + default in-memory adapter (F006); provider takes an injected storage (F043); theuseUserPreferenceadapter lives inserverand is injected (F140 reworded). Rationale: importinguseUserPreferenceintopackages/uicreatesui → user-composition → ui, a NEW cycle that fails.github/workflows/circular-deps.yml(not inknown-cycles.json). Guard (F007) + T012 (no forbidden imports) + T013 (nx graphno new cycle) enforce it. Repo also haseslint-plugin-custom-rules/no-feature-to-feature-imports(CIerror). - (2026-05-18) i18n namespace = new
msp/keyboard-shortcuts(action labels, groups, help, settings-panel chrome). Settings tab label goes in existingmsp/settings. ActionlabelKey/groupKeyare i18n keys from Phase 1 (no raw strings, no Phase-6 retrofit). - (2026-05-18) Settings UI = new tab in
SettingsPage.tsxbaseTabContent, componentKeyboardShortcutsSettings.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 acommitGroup; 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-scopedpage.create/page.saveregistered per page; default create =c, NOTmod+n— Ctrl/⌘+N is a browser new-window accelerator not reliably interceptable, esp. Chrome/Windows;mod+nis an opt-in alternate with a caveat),dialog-a11y(every page's create dialog keyboard-only: focus-on-open, trap,mod+Entersubmit, Escape, SR roles),command-palette(Spotlight onmod+k— resolves the old open decision; record search now lives inside the palette; asset palette staysmod+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?butMspLayoutClientpasses neither (falls back to in-memory). Dispatch (collectSingleChord/Sequence) resolves fromnormalizeDefaultBindings, neverresolveActionBindings;display.tsxusesgetDefaultBindingsForPlatformonly. 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.tsdefinednavigation.goTickets/goAssets/goClients(sequenceg t/a/c) but no component registered handlers →g twas a silent no-op. Source plan had nav as "illustrative";sequencegroup built the engine only. Fixed: registered the 3 nav actions inDefaultLayout.tsxwithrouter.push(navigationcommitGroup, 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.tsxANDpackages/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/229mod+l/mod+ArrowUpgated onaiAssistantAvailable(early-return beforepreventDefault) — preserve via the registryenabledflag. - (2026-05-18)
AssetDashboardClient.tsx:285windowmod+klistener = the conflict source; remove on rescope. - (2026-05-18) Radix Dialog/Drawer Escape is managed via
onEscapeKeyDown+stopPropagationandModalityContext/InsideDialogContextnesting fix (see global memory). Escape actions must integrate, not add a competing window listener. - (2026-05-18) Settings shell:
server/src/components/settings/SettingsPage.tsx→CustomTabs+UnsavedChangesProvider; tabs are a hard-codedbaseTabContentarray ({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 viareact-hot-toast+@alga-psa/ui/lib/errorHandling#handleError. Button variants:default|destructive|outline|ghost|soft; every interactive element needs anid. - (2026-05-18) i18n:
i18next+react-i18next. Client hookuseTranslation('namespace')from@alga-psa/ui/lib/i18n/client; servergetServerTranslation/getServerLocalefrom@alga-psa/ui/lib/i18n/serverOnly. Locale filesserver/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 inROUTE_NAMESPACES(packages/core/src/lib/i18n/config.ts). - (2026-05-18)
useUserPreferencealready does default→localStorage→server, 500ms debounce, hydration-mismatch avoidance,skipServerFetch/isUserLoggedInunauth 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 (matchesdocs/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(expect11111…) oryy(55555…) to confirm all strings are extracted.
Links / References
- 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 returnsnullwhenwindowis absent so SSR/node paths do not inspectnavigator;useClientPlatformresolves after mount. - (2026-05-19) Verification for scaffold:
npx vitest run --config vitest.config.ts src/keyboard-shortcuts/platform.test.tsfrompackages/uipassed (3 tests). - (2026-05-19) Completed parser group F010-F019/T001-T011. Added
parser.tswith typed-resultparseBindingandparseSequence. 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 literalctrl/metaremain distinct from neutralmod. - (2026-05-19) Verification for parser:
npx vitest run --config vitest.config.ts src/keyboard-shortcuts/parser.test.tsfrompackages/uipassed (27 tests). - (2026-05-19) Completed matcher group F020-F025/T020-T025. Added
matcher.tswith platform-timemodresolution, 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/othermod, 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.tsfrompackages/uipassed (6 tests). - (2026-05-19) Completed registry group F030-F042/T030-T044/T234.
Added
ShortcutRegistry,normalizeDefaultBindings, and a clientKeyboardShortcutsProviderwith one capture-phase document listener. Hooks now register actions, scopes, and active regions without adding listeners. Dispatch skipsdefaultPrevented, 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 throughonConflict. - (2026-05-19) Route-change clearing is represented in the UI package by a
provider
routeKeyprop so MSP can pass pathname later without coupling the package to Next routing. Active-region gating coversselection.*and unmodified single-letter page actions. - (2026-05-19) Verification for registry:
npx vitest run --config vitest.config.ts src/keyboard-shortcutsfrompackages/uipassed (57 tests).npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed. - (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
registrygroup to a newarchitecture-boundarygroup 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 viastorageprop/context, anduseShortcutStorage. Addedscripts/guard-keyboard-shortcuts-boundary.mjsplus a package script and circular-deps workflow step. The guard blocks imports from@alga-psa/user-compositionand feature packages inpackages/ui/src/keyboard-shortcuts, and delegates graph checks toscripts/check-circular-deps.mjs --baseline .github/known-cycles.jsonwhen--graphis supplied. - (2026-05-19) Verification for architecture-boundary:
npx vitest run --config vitest.config.ts src/keyboard-shortcuts/storage.test.tsxfrompackages/uipassed (2 tests);node --test scripts/tests/guard-keyboard-shortcuts-boundary.test.mjspassed (2 tests);npm run guard:keyboard-shortcuts-boundarypassed;npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed. - (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.tsxfrompackages/uipassed (24 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed. - (2026-05-19) Completed radix-escape group F060-F062/T060-T062. Added a
ref-counted Radix Escape owner bridge (
useRadixEscapeOwner) and wired sharedDialog/Drawerto mark ownership while open. The provider skips Escape dispatch while any Radix owner exists, so document-capture shortcuts cannot race RadixonEscapeKeyDownor nestedstopPropagation. 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.tsxfrompackages/uipassed (22 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed. - (2026-05-19) Completed action-catalog group F070-F071/T070-T071. Added
catalog.tswith stable metadata entries for global, AI, page, selection, navigation sequence, assets, dialog/panel/drawer, record, and editor actions. Each entry carrieslabelKey/groupKey, scope, priority, defaults, and flags likesequence/allowInEditable;editor.redoresolves tomod+shift+zon mac andctrl+y/ctrl+shift+zelsewhere. - (2026-05-19) Verification for action-catalog:
npx vitest run --config vitest.config.ts src/keyboard-shortcuts/catalog.test.tsfrompackages/uipassed (2 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed. - (2026-05-19) Completed global-migration group F080-F090/T080-T089/T230.
Mounted
KeyboardShortcutsProviderinMspLayoutClientaround the MSP product shells (not auth/client portal). Migratedglobal.searchout ofSearchPalette's window listener; migratedglobal.toggleChatandai.quickAskout ofDefaultLayout's window listener while preservingaiAssistantAvailablegates; registeredglobal.openShortcutsandglobal.quickCreate; and rescoped the assets palette to page-scopedassets.commandPaletteonmod+shift+k. - (2026-05-19) Temporary note:
global.openShortcutscurrently opens a minimal placeholder dialog inDefaultLayout; the full shared help dialog remains owned by the laterhelp-a11ygroup.global.quickCreateopens 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.tsfrompackages/uipassed (5 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed;npx tsc --noEmit -p packages/assets/tsconfig.jsonpassed;npm run typecheck --workspace serverpassed. 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.tsxandpackages/ui/src/context/DrawerContext.tsxnow registerpanel.close,drawer.historyBack, anddrawer.historyForward; both pushpanelscope only while open.TicketNavigationnow registersrecord.previous/nexton[/]and removed itsAlt+Arrowwindow listener. The catalog keepsAlt+ArrowLeft/Rightonly inOPTIONAL_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.tsfrompackages/uipassed (25 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed;npx tsc --noEmit -p packages/tickets/tsconfig.jsonpassed;npm run typecheck --workspace serverpassed. - (2026-05-19) Completed editors group F120-F124/T120-T124. Invoice designer
useDesignerShortcuts.tsnow pusheseditorscope and registers undo, redo, delete selection, cancel, and arrow-move actions instead of a window keydown listener.TextEditorpushes editor scope and marks the BlockNote root withdata-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.tsfrompackages/uipassed (5 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed;npx tsc --noEmit -p packages/billing/tsconfig.jsonpassed. - (2026-05-19) Completed persistence group F140-F147/T140-T150. Added
dependency-free preference utilities under
packages/uiforkeyboard_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 hookserver/src/hooks/useKeyboardShortcutPreferenceStorage.ts, which usesuseUserPreferencewith localStorage andskipServerFetchsupport. - (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.tsfrompackages/uipassed (6 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed;npm run typecheck --workspace serverpassed. - (2026-05-19) Completed settings-ui group F160-F172/T160-T174. Added
KeyboardShortcutsSettingsas 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 throughuseKeyboardShortcutPreferenceStorage, 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.tsfrompackages/uipassed (3 tests);npm run typecheck --workspace serverpassed. - (2026-05-19) Completed help-a11y group F180-F187/T180-T187. Added shared
Kbd,ShortcutHint, shortcut formatting,aria-keyshortcutsconversion, andShortcutHelpDialog. The search input now exposesaria-keyshortcutsand a visible hint, andglobal.openShortcutsrenders 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.tsxfrompackages/uipassed (3 tests);npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed;npm run typecheck --workspace serverpassed.
Open Questions
- Final
assets.commandPalettedefault binding after rescope (confirm in P2). - Whether any
editoraction needsallowInEditableexceptions beyond designer canvases (confirm in P4 againstuseDesignerShortcuts.ts). - Exact set of controls to receive visible
kbdhints /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.cjspassed 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.tsxpassed;npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed. - Additional check:
npm run typecheck --workspace serverpassed 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, andnpx nx graph --file=/tmp/project-graph.json && node scripts/guard-keyboard-shortcuts-boundary.mjs --graph /tmp/project-graph.jsonpassed.
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.tswidget-local assertions. - T232: Covered by
regression.contract.test.tsclient-only/platform assertions plus existingplatform.test.ts. - T233: Covered by
regression.contract.test.tsmigrated 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.tspassed;npx tsc --noEmit -p packages/ui/tsconfig.jsonpassed.
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.tscarries scope/priority/bindings, but registration sites hand-authorShortcutActionliterals. Concrete divergence: onlyuseDesignerShortcutssetspriority: 60;DefaultLayout, bothDrawerContext,SearchPalette,TicketNavigation,AssetDashboardClientomitpriorityso the provider uses?? 0, contradictingcatalog.tsDEFAULT_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 + extendguard-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.
DefaultLayoutcallsuseShortcutActiveRegion(true)unconditionally, soactiveRegionsRefis 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) isreadFileSync+.toContain('...'). They assert code exists, never that it works — which is why findings #1-#3 shipped "green".T360-T364already add behavioral coverage for the customization path;gap-hardeningconverts 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.tsxcarries the F095 nav handlers (legit, not yet committed);features.json/tests.jsonhave incidental\uXXXXescape churn from a JSON re-serializer (the newgap-hardeningitems are authored in plain ASCII to avoid adding to it). These are pre-existing and out of scope forgap-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.tsxto exercise the cataloguednavigation.goTickets/goAssets/goClientssequence actions throughKeyboardShortcutsProvider. The test verifiesg t,g a, andg cdispatch 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.
MspLayoutClientnow injectsuseKeyboardShortcutPreferenceStorageintoKeyboardShortcutsProvider; the provider loads the persisted blob, keeps it as reactive context state, resolves dispatch throughresolveActionBindings, mergespreferences.disabled, and exposes resolved bindings plus set/disable/reset mutators.ShortcutHint,useAriaKeyShortcuts,ShortcutHelpDialog, andKeyboardShortcutsSettingsnow 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-keyshortcutsupdate, 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.jsonpassed. 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.jsonandnpm run typecheck --workspace server.
2026-05-19 — page-actions group implementation
- F300-F302/F310: Added
page.createto the catalog with defaultc, keptmod+nonly as an optional alternate with a browser-owned new-window caveat, and added sharedusePageCreateShortcut/usePageSaveShortcuthelpers inpackages/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), andAssetDashboardClient(asset). - F309: Wired page save to primary editable Save controls found in this pass:
ClientDetails(handleSave) and ticketTicketInfo(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.createlabel/description to en/fr/es/de/nl/it/pl/pt and regenerated xx/yy pseudo-locales. - Tests/checks: Added
page-actions.test.tsxfor catalog/default/optional alternate behavior, create/save dispatch, editable suppression, and panel-scope suppression. Addedpage-actions.source.test.tsto 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; andnpm 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 customonOpenAutoFocusis 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
disableFocusTrapfrom 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.tsxfor first-field focus, invoker focus restore, Escape close, and mod+Enter form submit. Addeddialog-a11y.source.test.tsto 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; andnpm run typecheck --workspace server.
2026-05-19 — command-palette group implementation
- F340-F353: Implemented the command palette overlay opened by
global.search(mod+k) fromSearchPalette. 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, visiblemod+khint, and in-palette syntax help. - F343-F347: Added pure UI-decoupled
packages/ui/src/keyboard-shortcuts/command-palette-query.tsparser with field aliases, sigils, quoted phrases, exclusion, wildcards, fuzzy suffixes, OR/AND metadata, and$mine/$recent/$openaliases. - 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); andnpx 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
createShortcutActionanduseCatalogShortcut, 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)fromDefaultLayout. Added sharedShortcutActiveRegion, 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; andnpx 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.tsxdoes 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 akeyboard-shortcutsentry. The content route worked, but there was nothing to click.settings-ui.contract.test.tswas a grep that only asserted theid/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 toUserProfile.tsx tabContent, deletedKeyboardShortcutsSettings.tsxand itsSettingsPageregistration +Keyboardlucide import. Added'keyboard-shortcuts'toBASE_PROFILE_TABS(calendarAvailability.ts) so/msp/profile?tab=keyboard-shortcutsdeep-links. - Engine: extended
PersistedShortcutsto v2 withprofile: stringand a v1→v2 migration;SHORTCUT_PROFILESshipsdefault/vim/emacswith 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, insideresolveActionBindingsso dispatch + hints + ARIA + panel all read the same effective binding.setActionBindingsDeltanow drops overrides equal to the profile baseline, so per-action reset returns to the active profile (not raw factory). Provider exposesprofile+setProfile;useKeyboardShortcutPreferencesincludes them. Vim/Emacs deltas are best-guess pending team confirmation (open Q in handoff). - Panel:
server/src/components/keyboard-shortcuts/KeyboardShortcutsPanel.tsxrecreates variation-c using product CSS vars (--color-primary-*,--color-card,--radius-md,--font-mono— globals.css already defines these) + Radix-basedConfirmationDialog/Switch/LoadingIndicator. Realkeydowncapture via window-level capture-phase listener withstopImmediatePropagationso 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). Rewrotesettings-ui.contract.test.tsto assert the new placement + panel wiring (with@behavioralCoveragelinking the real behavioral suites). Updatedi18n.contract.test.tsto assertprofile.tabs.keyboardShortcutsinen/msp/profile.jsonand thatSettingsPageno longer references the removed tab. Added the key to all production+pseudo locales for parity and rangenerate-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.jsonclean;npm run typecheck --workspace serverclean;node scripts/guard-keyboard-shortcuts-boundary.mjsOK; meta contract-test guard OK;node scripts/validate-translations.cjsPASSED. - 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 viat(..., { 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.htmlis the side-by-side reference.