# OpenSpec Change: enterprise-browser-react-command-palette - **Change ID**: enterprise-browser-react-command-palette - **Package**: `@suaveplan/react-command-palette` - **Category**: browser - **Target path**: `browser/react/react-command-palette` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/react-command-palette.md` --- ## Summary Deliver `@suaveplan/react-command-palette` on the genesis branch at enterprise quality. The package provides a headless `useCommandPalette` hook with `CommandHistory` (injectable storage adapter) and `fuzzySearch`/`fuzzyScore`. Genesis adds a reference `CommandPalette` component with Storybook stories, injects a `StorageAdapter` into `CommandHistory`, adds Zod schemas for `CommandItem`, OTel metrics, i18n, and accessibility attributes — all with no `react-dom` runtime dep and no `@ts-expect-error` in source. --- ## Current-state audit (genesis) Fresh build — does not exist on genesis yet. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Typed errors for history storage failures | | `@suaveplan/events` (`workspace:^`) | Optional event bus for palette lifecycle | | `@suaveplan/logger` (`workspace:^`) | Structured logging | | `@suaveplan/schema-registry` (`workspace:^`) | `CommandItem` schema registration | | `zod` (`catalog:forms`) | `CommandItem` Zod schema | | `@opentelemetry/api` (`catalog:observability`) | Palette open/execute/history metrics | | `tslib` (`catalog:build`) | Runtime helpers | Dev: `vitest`, `@vitest/coverage-istanbul`, `happy-dom`, `@testing-library/react`, `@suaveplan/testing`, `@suaveplan/testing-dom`. No `react-dom` in runtime `dependencies` — peer only. --- ## Preserved public API surface ```ts // Hook function useCommandPalette(config: CommandPaletteConfig): [CommandPaletteState, CommandPaletteActions, CommandPaletteInputProps] // History (with injectable storage) class CommandHistory { constructor(config: HistoryConfig) add(item: CommandItem): void get(): CommandItem[] clear(): void } // Fuzzy search function fuzzySearch(items: CommandItem[], query: string): FuzzyMatch[] function fuzzyScore(item: CommandItem, query: string): number // Reference component (new) function CommandPalette(props: CommandPaletteProps): JSX.Element // Types interface CommandItem { id: string; title: string; description?: string; group?: string; keywords?: string[] } interface CommandPaletteConfig { triggerKey?: string; placeholder?: string; maxResults?: number; history?: HistoryConfig } interface CommandPaletteState { isOpen: boolean; query: string; results: FuzzyMatch[]; selectedIndex: number } interface CommandPaletteActions { open(): void; close(): void; setQuery(q: string): void; execute(item: CommandItem): void } interface CommandPaletteInputProps { value: string; onChange(e: ChangeEvent): void; role: string; 'aria-expanded': boolean; 'aria-activedescendant': string } ``` Subpath exports: `"."`, `"./utils/fuzzy-search"`, `"./core/history/history"`, `"./core/command-palette/command-palette"`, `"./components/command-palette"`. --- ## New additions for genesis ### 1. Reference `CommandPalette` component + stories `src/components/command-palette/command-palette.tsx` — reference visual component built on `useCommandPalette`. Ships `.stories.tsx` (default + playground + variants: groups, async commands, history). Required by CLAUDE.md for every React component. ### 2. Injectable `StorageAdapter` for `CommandHistory` `CommandHistory` accepts `StorageAdapter` interface; default implementation wraps `localStorage`. Consumers can pass an `IndexedDBAdapter` backed by `@suaveplan/storage-browser`. ### 3. Zod schema for `CommandItem` `CommandItemSchema` registered in `@suaveplan/schema-registry`. Runtime validation on `history.add()` and `fuzzySearch()` inputs. ### 4. OTel metrics Counter `palette.open_total`, counter `palette.execute_total`, counter `palette.history_hit_total`, histogram `palette.filter_ms`. ### 5. i18n — no hard-coded strings in reference component Reference component accepts `placeholder` prop; falls through to `@suaveplan/i18n` message resolver hook for default text. ### 6. Accessibility attributes `CommandPaletteInputProps` includes `role="combobox"`, `aria-expanded`, `aria-activedescendant`, `aria-autocomplete`. Reference component passes axe-core gate. ### 7. Error codes `HISTORY_STORAGE_FAILED`, `COMMAND_INVALID`. --- ## File layout ```text browser/react/react-command-palette/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts core/ command-palette/ command-palette.ts command-palette.test.ts (split: open/close, filtering, navigation, history, config) command-palette.md command-palette.types.ts index.ts history/ history.ts history.test.ts history.md index.ts storage-adapter/ storage-adapter.ts (StorageAdapter interface + LocalStorageAdapter) storage-adapter.test.ts storage-adapter.md index.ts components/ command-palette/ command-palette.tsx command-palette.stories.tsx command-palette.test.tsx command-palette.md index.ts utils/ fuzzy-search/ fuzzy-search.ts fuzzy-search.test.ts fuzzy-search.md index.ts schema/ command-item-schema/ command-item-schema.ts command-item-schema.md index.ts types/ common.ts common.md constants/ index.ts tests/ integration.test.ts ``` --- ## Impact **Downstream**: any Suaveplan browser app using a command palette. **Breaking changes**: None — new factory + component are additive. **Prerequisites**: `@suaveplan/error`, `@suaveplan/schema-registry` on Verdaccio.