-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
ABCrimson edited this page Mar 11, 2026
·
2 revisions
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)
Pure TypeScript state machine with zero DOM dependencies. Can be used with any framework (React, Vue, Svelte, vanilla JS) or no framework at all.
| 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 |
| 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 |
| 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 |
-
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 -
refas prop — NoforwardRefneeded -
"use client"— All components are client-only
Optional Rust-compiled trigram index for sub-1ms fuzzy search on 100K+ items.
| Mode | Function | Thread | Best For |
|---|---|---|---|
| Main thread | createWasmSearchEngine() |
Main | Simple setup, < 5K items |
| Web Worker | createWorkerWasmSearchEngine() |
Worker | Large datasets, non-blocking UI |
When cross-origin isolated (COOP/COEP headers), scores are transferred via SharedArrayBuffer — the main thread reads a Float32Array view with zero data copying.
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;
}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).