4.6 KiB
4.6 KiB
Tasks: enterprise-react-hotkeys
Change ID: enterprise-react-hotkeys
Package: @suaveplan/react-hotkeys
Target path: react/ui/react-hotkeys
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/react-translate
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold
- Create directory tree per proposal layout.
- Write
package.jsonat0.1.0with"sideEffects": false, exports map. - Dependencies:
@suaveplan/error workspace:^,@suaveplan/react-translate workspace:^,zod catalog:forms,tslib catalog:build. - Write
tsconfig.json,vitest.config.ts, scripts.
2. Error codes and types
- Define
HotkeyErrorextendingSuaveplanError; codes:HOTKEY_CONFLICT,HOTKEY_INVALID_COMBO. - Define all types:
HotkeyCombo(single token + modifier flags),HotkeyBinding(combo string + handler + description),HotkeyOptions,HotkeyRegistryEntry,RegistryEntry. - Write
HotkeyBindingSchemaZod schema.
3. Combo parser (src/utils/combo-parser/)
- Implement
parseCombo(raw: string): HotkeyCombo | HotkeyCombo[]— supportsctrl+shift+k,cmd+k,mod+k(platform-aware),g g(sequence as array),option+/. - Implement
matchesCombo(event: KeyboardEvent, combo: HotkeyCombo): boolean. - Implement
formatCombo(combo: HotkeyCombo): string—⌘Kon mac,Ctrl+Kon other. - Implement
isModifierKey(event): boolean,isMac(): boolean. - Write
combo-parser.test.ts(≤500 lines): all modifier combos, sequence combos, platform-aware mod, edge cases (/,space).{ timeout: 5000 }. - Write
combo-parser.md.
4. Store (src/store/hotkey-store/)
- Implement
createHotkeyStore()—useSyncExternalStore-compatible external store holdingMap<scope, HotkeyRegistryEntry[]>. register(scope, entry): () => void— adds entry, returns unsubscribe; throwsHotkeyError(HOTKEY_CONFLICT)if same scope+combo already registered.getEntries(scope): HotkeyRegistryEntry[],subscribe(listener): () => void.- Sequence buffer: tracks last N keydowns; matches against sequence combos.
- Write
hotkey-store.test.ts,hotkey-store.md.
5. Hooks
useHotkey
- Registers single combo; installs
keydownlistener ondocument(SSR-safe viauseEffect). prevent-in-inputguard.- Returns
{ isPressed: boolean }. - Write test (combo fires, in-input blocked, SSR-safe), stories (playground toggle), md.
useHotkeys
- Bulk-registers
HotkeyBinding[]; detects conflicts via store and emitsHotkeyError(HOTKEY_CONFLICT)(non-throwing — logs viaconsole.warnin dev, captures OTel event). - Write test, md.
useHotkeyRegistry
- Subscribes to store via
useSyncExternalStore; returns current entries for active scope. - Write test (mount/unmount triggers re-render), md.
6. Components
HotkeyScope
- Provides scope name via context; routes events only when scope is active.
- Write test, stories (scoped-routing — two scopes, only active one fires), md.
HotkeyCheatSheet
- Renders grouped registry entries (scope → table of combo + description).
- Uses
useTranslationfor chrome strings ("Keyboard Shortcuts", "Description", "Shortcut"). - Re-renders live when entries change (via
useHotkeyRegistry). - Write test, stories (default, live-update on mount, playground), md.
7. E2E
e2e/hotkeys.spec.ts— Playwright:- Press
ctrl+k(ormeta+kon mac), verify handler fires. - Press
g gsequence, verify handler fires. - Focus
<input>, press hotkey, verify handler does NOT fire. - Switch
HotkeyScope, verify only active scope responds.
- Press
8. Verify and publish
bunx turbo lint typecheck test --filter=@suaveplan/react-hotkeys— green.bunx turbo test:coverage --filter=@suaveplan/react-hotkeys— 100%.bun scripts/publish.ts --package @suaveplan/react-hotkeys.- Archive change folder.