Files
2026-05-27 23:29:53 +01:00

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 status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in 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.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create directory tree per proposal layout.
  • Write package.json at 0.1.0 with "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 HotkeyError extending SuaveplanError; codes: HOTKEY_CONFLICT, HOTKEY_INVALID_COMBO.
  • Define all types: HotkeyCombo (single token + modifier flags), HotkeyBinding (combo string + handler + description), HotkeyOptions, HotkeyRegistryEntry, RegistryEntry.
  • Write HotkeyBindingSchema Zod schema.

3. Combo parser (src/utils/combo-parser/)

  • Implement parseCombo(raw: string): HotkeyCombo | HotkeyCombo[] — supports ctrl+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 — ⌘K on mac, Ctrl+K on 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 holding Map<scope, HotkeyRegistryEntry[]>.
  • register(scope, entry): () => void — adds entry, returns unsubscribe; throws HotkeyError(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 keydown listener on document (SSR-safe via useEffect).
  • prevent-in-input guard.
  • 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 emits HotkeyError(HOTKEY_CONFLICT) (non-throwing — logs via console.warn in 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 useTranslation for 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 (or meta+k on mac), verify handler fires.
    • Press g g sequence, verify handler fires.
    • Focus <input>, press hotkey, verify handler does NOT fire.
    • Switch HotkeyScope, verify only active scope responds.

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.