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

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.