Files
suaveplan/openspec/changes/enterprise-react-hotkeys/tasks.md
2026-05-27 23:29:53 +01:00

107 lines
4.6 KiB
Markdown

# 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.