171 lines
5.8 KiB
Markdown
171 lines
5.8 KiB
Markdown
# 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.
|