Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# OpenSpec Change: enterprise-react-hotkeys
- **Change ID**: enterprise-react-hotkeys
- **Package**: `@suaveplan/react-hotkeys`
- **Category**: react
- **Target path**: `react/ui/react-hotkeys`
- **Status**: PROPOSED
- **Date**: 2026-04-24
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/react-hotkeys.md`
---
## Summary
Deliver `@suaveplan/react-hotkeys` on genesis from scratch at enterprise quality. Keyboard-shortcut primitives: `useHotkey`, `useHotkeys`, `HotkeyScope`, `HotkeyCheatSheet`, combo parser/matcher/formatter. Genesis replaces the module-level mutable registry with a `useSyncExternalStore`-backed store, adds sequence combos (e.g. `g g`), platform-aware mod key, conflict detection, `prevent-in-input` guard, and full Storybook + Playwright coverage.
---
## Current-state audit (genesis)
Package does not exist on genesis — fresh build.
---
## Available workspace deps on genesis
| Package | Role |
|---|---|
| `@suaveplan/error` (`workspace:^`) | `HotkeyError` |
| `@suaveplan/react-translate` (`workspace:^`) | i18n for cheat-sheet chrome strings |
| `zod` (`catalog:forms`) | Hotkey binding schemas |
External: `tslib catalog:build`. React 18 peer.
---
## Preserved public API surface
```ts
HotkeyCheatSheet — renders all registered entries with descriptions
HotkeyScope — provider routing events to active scope
useHotkey(combo, handler, options?)
useHotkeys(bindings: HotkeyBinding[], options?)
useHotkeyRegistry(): HotkeyRegistryEntry[]
parseCombo(raw: string): HotkeyCombo
matchesCombo(event: KeyboardEvent, combo: HotkeyCombo): boolean
formatCombo(combo: HotkeyCombo): string
// Types
HotkeyBinding, HotkeyCombo, HotkeyOptions, HotkeyRegistryEntry, RegistryEntry
```
---
## New additions for genesis
1. **`useSyncExternalStore`-backed registry** — cheat-sheet re-renders on mount/unmount without parent re-render.
2. **Sequence combos** — `g g`, `ctrl+k ctrl+p` parsed as `HotkeyCombo[]` array; matched against a keydown sequence buffer.
3. **Platform-aware mod key** — `mod` expands to `cmd` on macOS, `ctrl` on Windows/Linux.
4. **Conflict detection** — `useHotkeys` warns (via `@suaveplan/error` `HotkeyError`) when two bindings share the same scope + combo.
5. **`prevent-in-input` guard** — `options.preventDefault` skips when target is `INPUT`, `TEXTAREA`, `[contenteditable]`.
6. **Stories** — `HotkeyCheatSheet` default + live-update, `HotkeyScope` scoped-routing, `useHotkey` playground.
---
## File layout
```text
react/ui/react-hotkeys/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
hooks/
use-hotkey/ (ts + types + md + test + stories + index)
use-hotkeys/ (ts + types + md + test + index)
use-hotkey-registry/ (ts + md + test + index)
components/
hotkey-scope/ (tsx + types + md + test + stories + index)
hotkey-cheat-sheet/ (tsx + types + md + test + stories + index)
store/
hotkey-store/ (ts + md + test + index)
types/
hotkey.types.ts
utils/
combo-parser/ (ts + md + test + index)
e2e/
hotkeys.spec.ts
```

View File

@@ -0,0 +1,106 @@
# 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.