5.8 KiB
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
// 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
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.