Extending

Prev Next

Extending

New capability = new data. Write an adapter, register it, and the system indexes it automatically.

This is the page you will reach for when onboarding a new domain into the Command Center. The pattern is always the same: define → normalise → register → done.

Adding Navigation Items

If you have a set of routes or pages that users should be able to reach from the command palette:

1. Create the adapter

// adapters/settings.adapter.ts
import { IndexItem } from '../core/types';

export function normalizeSettingsForFuseAdapter(): IndexItem[] {
  return [
    {
      id: 'nav.settings.general',
      type: 'nav',
      title: 'General Settings',
      route: '/settings/general',
      keywords: ['settings', 'general', 'preferences', 'config'],
      description: 'Settings',
      meta: { breadcrumb: 'Settings / General' },
    },
    {
      id: 'nav.settings.billing',
      type: 'nav',
      title: 'Billing',
      route: '/settings/billing',
      keywords: ['billing', 'settings billing', 'payments', 'invoices'],
      description: 'Settings',
      meta: { breadcrumb: 'Settings / Billing' },
    },
  ];
}

2. Register in the composer

// core/composer.ts
import { normalizeSettingsForFuseAdapter } from '../adapters/settings.adapter';

export function setupCommandCenter() {
  indexRegistry.register('routes', () => normalizeRoutesForFuseAdapter());
  indexRegistry.register('settings', () => normalizeSettingsForFuseAdapter());
}

That is it. The next time indexer.build() runs, the settings items appear in search results.

Adding Actions

Actions are non-navigational commands — toggling dark mode, clearing cache, opening a modal. They need both an IndexItem (so they appear in search) and a Command registration (so they can execute).

1. Create the adapter

// adapters/actions.adapter.ts
import { IndexItem } from '../core/types';

export function normalizeActionsForFuseAdapter(): IndexItem[] {
  return [
    {
      id: 'action.theme.toggle',
      type: 'action',
      title: 'Toggle Dark Mode',
      commandId: 'theme.toggle',
      keywords: ['dark mode', 'light mode', 'theme', 'toggle theme', 'appearance'],
    },
  ];
}

2. Register the command handler

// somewhere at app init
import { commandRegistry } from '@/lib/internal/CommandCenter';

commandRegistry.register({
  id: 'theme.toggle',
  handler: () => document.documentElement.classList.toggle('dark'),
});

3. Register the adapter in the composer

indexRegistry.register('actions', () => normalizeActionsForFuseAdapter());

The item appears in search under the "Suggestions" group (because type: 'action'). When selected, the executor finds no route, falls through to commandId, looks up theme.toggle in the CommandRegistry, and calls the handler.

Adding Deep Links

Deep links are navigation items that point to specific entities or records — a particular user, a specific video, a content page by ID. They follow the same pattern as navigation, but IDs and routes are dynamic.

// adapters/content.adapter.ts
import { IndexItem } from '../core/types';

export function normalizeContentForFuseAdapter(items: ContentItem[]): IndexItem[] {
  return items.map((item) => ({
    id: `content.${item.type}.${item.id}`,
    type: 'content',
    title: item.title,
    route: `/content/${item.type}/${item.id}`,
    keywords: [item.title.toLowerCase(), item.type, item.category].filter(Boolean),
    description: item.type,
    meta: { module: 'content' },
  }));
}

The adapter signature changes slightly — it accepts data as an argument rather than importing a static config. The composer passes the data at registration time:

indexRegistry.register('content', () => normalizeContentForFuseAdapter(contentItems));

Checklist

When adding a new domain, verify:

  • Adapter returns IndexItem[] with all required fields (id, type, title)
  • IDs follow the <type>.<namespace>.<slug> convention
  • IDs are deterministic — same input always produces same IDs
  • Keywords include the lowercased title, parent context, and domain-specific synonyms
  • type matches one of the defined CommandItemType values (nav, action, entity, content)
  • Navigation items have route, action items have commandId
  • Action items have a corresponding commandRegistry.register() call
  • Adapter is registered in composer.ts
  • No core files were modified

What Not to Do

  • Do not modify the engine for new domains. If your items are not surfacing, the problem is keyword quality, not search config.
  • Do not add new CommandItemType values without updating GROUP_LABELS. The UI groups results by type, and unlabelled types render without a section header.
  • Do not register adapters outside composer.ts. All registrations live in one file for auditability.
  • Do not fetch data inside an adapter. Adapters are synchronous and pure. If your data requires a network call, fetch it first and pass it as an argument.