Skip to content

Architecture

ABCrimson edited this page Mar 11, 2026 · 2 revisions

Architecture

Three-Layer Design

Layer 3: Optional Plugins
  └─ modern-cmdk-search-wasm (Rust/WASM trigram index)
  └─ Web Worker Engine (off-main-thread search)

Layer 2: React 19 Adapter
  └─ modern-cmdk/react (compound components)
  └─ useSyncExternalStore, useTransition, useOptimistic

Layer 1: Core Engine
  └─ modern-cmdk (pure TypeScript state machine)
  └─ Search Engine (pluggable scorer)
  └─ Frecency Engine (Temporal.Duration decay)
  └─ Keyboard Registry (RegExp.escape parser)

Layer 1: Core Engine

Pure TypeScript state machine with zero DOM dependencies. Can be used with any framework (React, Vue, Svelte, vanilla JS) or no framework at all.

Key Modules

Module File Responsibility
State Machine machine.ts State transitions, subscriber notification, event processing
Registry registry.ts Item/group registration with Set methods (union/intersection/difference)
Search Engine search/index.ts Pluggable scorer, incremental filtering with Set.difference
Default Scorer search/default-scorer.ts Fuzzy matching with Math.sumPrecise
Frecency frecency/index.ts Temporal.Duration decay buckets, configurable weights
IDB Storage frecency/idb-storage.ts IndexedDB persistence via idb-keyval
Keyboard Parser keyboard/parser.ts Shortcut string parsing with RegExp.escape
Keyboard Matcher keyboard/matcher.ts Conflict detection via Object.groupBy
Scheduler utils/scheduler.ts Microtask batching, scheduler.yield(), isInputPending()
Event Emitter utils/event-emitter.ts Type-safe pub/sub with WeakRef listeners

State Machine Events

Event Description
SEARCH_CHANGE Update search query, trigger re-filtering
NAVIGATE Move active item (next/prev/first/last)
ITEM_SELECT Select the active item
ITEM_ACTIVATE Set a specific item as active
REGISTER_ITEM Register a new item in the registry
UNREGISTER_ITEM Remove an item from the registry
PAGE_PUSH Navigate to a sub-page
PAGE_POP Go back to previous page
OPEN / CLOSE / TOGGLE Dialog open/close state
SET_LOADING Toggle loading state for async items

Layer 2: React 19 Adapter

14 Compound Components

Component ARIA Role Description
Command application Root — creates state machine context
Command.Input combobox Search input with aria-autocomplete="list"
Command.List listbox Scrollable list with auto-virtualization
Command.Item option Selectable item with aria-selected
Command.Group group Logical grouping with aria-labelledby
Command.Empty status Shown when no results match
Command.Loading status Shown during async loading
Command.Separator separator Visual divider
Command.Dialog dialog Radix Dialog wrapper with portal/overlay
Command.Page Nested page navigation
Command.AsyncItems Suspense-powered async data loading
Command.Highlight Fuzzy match text highlighting
Command.Badge Status badge
Command.Shortcut Keyboard shortcut display

React 19 Features Used

  • useSyncExternalStore — Subscribe to machine state without tearing
  • useTransition — Non-blocking search updates
  • useOptimistic — Instant visual feedback for active item
  • useId — Stable ARIA relationship IDs
  • use() — Suspense integration for async items
  • ref as prop — No forwardRef needed
  • "use client" — All components are client-only

Layer 3: WASM Search Plugin

Optional Rust-compiled trigram index for sub-1ms fuzzy search on 100K+ items.

Two Execution Modes

Mode Function Thread Best For
Main thread createWasmSearchEngine() Main Simple setup, < 5K items
Web Worker createWorkerWasmSearchEngine() Worker Large datasets, non-blocking UI

SharedArrayBuffer Zero-Copy

When cross-origin isolated (COOP/COEP headers), scores are transferred via SharedArrayBuffer — the main thread reads a Float32Array view with zero data copying.

Plugin Interfaces

SearchEngine

interface SearchEngine extends Disposable {
  index(items: readonly CommandItem[]): void;
  search(query: string, items: readonly CommandItem[]): IteratorObject<SearchResult>;
  remove(ids: ReadonlySet<ItemId>): void;
  clear(): void;
  [Symbol.dispose](): void;
}

FrecencyStorage

interface FrecencyStorage extends Disposable {
  load(namespace: string): FrecencyData | Promise<FrecencyData>;
  save(namespace: string, data: FrecencyData): void | Promise<void>;
  [Symbol.dispose](): void;
}

Built-in: MemoryFrecencyStorage (default), IdbFrecencyStorage (IndexedDB).

Clone this wiki locally