Core Concepts

Prev Next

Core Concepts

Every component in the Command Center exists to serve one question:

"How does the user's intent become a system action?"

This page defines the five primitives that answer it.

IndexItem

The canonical data shape of the Command Center. Every searchable entity — a route, an action, a setting, a piece of content — must be expressed as an IndexItem before it enters the system.

type CommandItemType = 'nav' | 'action' | 'entity' | 'content';

type IndexItem = {
  id: string;
  type: CommandItemType;
  title: string;
  description?: string;
  keywords?: string[];
  route?: string; // navigation target
  commandId?: string; // action execution target
  meta?: {
    breadcrumb?: string;
    module?: string;
    weight?: number;
  };
};

Rules

  • id must be globally unique and stable. Convention: <type>.<domain>.<item> (e.g. nav.monetization.plans)
  • type determines grouping in the UI and execution strategy
  • route and commandId are mutually exclusive execution targets
  • keywords directly control search recall — this is where search quality lives
  • meta is for adapter-specific enrichment; the core system does not read it

IndexItem is the only shape that crosses the adapter boundary. If data cannot be expressed as an IndexItem, the Command Center cannot index it.

Adapter

A pure function that converts domain-specific data into IndexItem[].

Adapters are the transformation boundary between raw data (JSON configs, API responses, static definitions) and the unified index.

type IndexProvider = () => IndexItem[];

Responsibilities

  • Flatten nested structures into individual items
  • Generate stable, deterministic IDs
  • Enrich keywords with parent context for search quality
  • Skip inactive or irrelevant entries
  • Return IndexItem[] — nothing else

Non-responsibilities

  • Adapters do not mutate source data
  • Adapters do not contain UI logic, execution logic, or side effects
  • Adapters do not access browser APIs or cookies

An adapter receives data and returns index items. That is its entire contract.

Registry

The IndexRegistry collects adapters and composes them into a unified index.

indexRegistry.register('routes', () => normalizeRoutesForFuseAdapter());
indexRegistry.register('settings', () => normalizeSettingsForFuseAdapter());

When build() is called, the registry invokes every registered provider and concatenates the results into a single IndexItem[]. This is the unified index that the engine searches over.

The registry is append-only during bootstrap. Once the index is built, the registry's job is done.

:::tip Why a registry
Without it, the composer would need to import and call every adapter directly — creating tight coupling between the composition layer and every data domain. The registry inverts this: domains register themselves, and the composer just calls build().
:::

Engine

The CommandEngine takes the unified index and makes it searchable.

It wraps a fuzzy search implementation and exposes a single method:

commandEngine.search(query) → CommandResult[]

CommandResult is the output shape — a search result enriched with score, display metadata, and match ranges:

type CommandResult = {
  id: string;
  title: string;
  type: CommandItemType;
  subtitle?: string;
  icon?: ComponentType<SVGProps<SVGSVGElement>>;
  route?: string;
  commandId?: string;
  score?: number;
  matches?: readonly FuseResultMatch[];
};
  • subtitle provides contextual breadcrumbs (e.g. "AppCMS / Settings / Web Push")
  • matches carries Fuse.js match ranges for highlighted rendering in the UI
  • icon allows adapters to attach domain-specific icons to results

The engine is initialized once after the index is built. It does not know what domains exist, what adapters produced the data, or how results will be used. It scores and ranks.

Key properties:

  • Search is performed over title, keywords, and description
  • Results are transformed from library-specific output into CommandResult via the result adapter
  • The search library is an implementation detail — replaceable without affecting any other layer

Executor

The CommandExecutor receives a CommandResult and dispatches the appropriate action.

executor.execute(result);

Two execution paths:

  • route → delegates to the router (navigate(path))
  • commandId → looks up the handler in CommandRegistry and invokes it

The executor does not decide what to execute. That decision was made when the adapter assigned route or commandId to the IndexItem. The executor just follows through.

CommandRegistry

A simple map of commandId → handler.

commandRegistry.register({
  id: 'theme.toggle',
  handler: () => themeManager.toggle(),
});

Actions are registered independently of the index. An IndexItem with commandId: 'theme.toggle' will find and invoke this handler at execution time.

How They Connect

Image

Each boundary is enforced by type:

  • Adapters produce IndexItem[]
  • The registry collects IndexItem[]
  • The engine consumes IndexItem[] and produces CommandResult[]
  • The executor consumes CommandResult

No component reaches across its boundary. This is what makes the system extensible without modification.