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
typematches one of the definedCommandItemTypevalues (nav,action,entity,content)- Navigation items have
route, action items havecommandId - 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
CommandItemTypevalues without updatingGROUP_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.