How to register palette commands
A palette item is a row in the command palette. Use them for nav shortcuts, quick toggles, anything you want users to invoke with Ctrl+K + a few keys.
dddk.palette.addItem({
id: 'go-billing',
name: 'Open Billing',
description: 'See invoices and payment history',
section: 'Navigation',
icon: '💳',
handler: (p) => {
p.close();
yourRouter.push('/billing');
},
});
That's the minimum. Drops into the palette, ranks against fuzzy matches, fires handler when chosen.
Field reference
interface PaletteItem {
id: string; // unique key, used by HeatRank
name: string; // displayed label
description?: string; // secondary line under the name
icon?: string; // emoji, or any string that renders
section?: string; // groups items in the list (Nav, AI, Settings…)
prefix?: string | string[] | PaletteItemPrefix;
prefixAcceptsAnyArg?: boolean;
fallback?: boolean;
agentTool?: PaletteAgentTool;
handler?: (handle: PaletteHandle, arg?: string) => void;
detail?: PieceNode; // optional right-pane detail
}
The handle you receive:
interface PaletteHandle {
close(): void;
replace(items: PaletteItem[]): void; // swap the list (for sub-menus)
setMode(mode: 'search' | 'chat'): void;
input: string;
setInput(value: string): void;
}
Pattern: nav shortcuts
const NAV = [
{ id: 'go-home', name: 'Home', icon: '🏠', path: '/' },
{ id: 'go-billing', name: 'Billing', icon: '💳', path: '/billing' },
{ id: 'go-settings',name: 'Settings', icon: '⚙️', path: '/settings' },
];
for (const n of NAV) {
dddk.palette.addItem({
id: n.id,
name: n.name,
icon: n.icon,
section: 'Navigation',
handler: (p) => { p.close(); router.push(n.path); },
});
}
Pattern: prefix routing — ask ai: ...
A prefix turns the item into a routing target. Anything typed after the prefix becomes arg for the handler. Useful for free-form input.
dddk.palette.addItem({
id: 'ask-ai',
name: 'Ask AI',
description: 'Ask anything — powered by webagent',
prefix: 'ask ai:',
prefixAcceptsAnyArg: true, // accept any non-empty arg
fallback: true, // also show when nothing else matches
icon: '✨',
section: 'AI',
handler: (p, arg) => {
p.close();
if (arg) dddk.startAgent(arg);
},
});
Multiple prefixes? Pass an array:
prefix: ['/ai', 'ask ai:', '?'],
Different display label vs. match key:
prefix: { match: ['/ai', 'ask:'], label: '/ai' },
Pattern: quick toggle
let darkMode = false;
dddk.palette.addItem({
id: 'toggle-dark',
name: () => darkMode ? 'Switch to light mode' : 'Switch to dark mode',
section: 'Settings',
icon: '🌓',
handler: (p) => {
p.close();
darkMode = !darkMode;
document.documentElement.dataset.theme = darkMode ? 'dark' : 'light';
},
});
(For a polished version of this exact toggle, use the built-in ThemeToggleModule — see 04-how-to-toggle-features.)
Pattern: opt-in as agent tool
Mark a palette item with agentTool and webagent can invoke it during a multi-step task.
dddk.palette.addItem({
id: 'refund-order',
name: 'Refund order',
prefix: 'refund:',
prefixAcceptsAnyArg: true,
icon: '↩',
agentTool: {
description: 'Refund the order whose ID is passed as `arg`. Requires confirmation.',
parameters: {
type: 'object',
properties: { arg: { type: 'string', description: 'Order ID like ORD-1002' } },
required: ['arg'],
},
requireConfirmation: true,
},
handler: async (p, arg) => {
p.close();
if (!arg) return;
await fetch(`/api/orders/${arg}/refund`, { method: 'POST' });
},
});
Now if the agent gets the task "refund Bob's order", it can find the order, then call refund-order with arg='ORD-1002'. Because requireConfirmation: true, the user gets a confirm dialog first.
Items WITHOUT agentTool are never exposed to the agent — opt-in by design.
Pattern: image attach (camera icon)
Show a camera icon at the right of the input — user picks a file or screenshots a tab.
const palette = new CommandPalette({
// ...
camera: {
mode: 'upload',
onCapture: (file, source) => {
// do whatever — upload to your backend, attach to the next agent call, etc.
console.log('got', source, file);
},
accept: 'image/*',
capture: true, // mobile: open camera directly
},
});
Switch to mode: 'screenshot' to use getDisplayMedia() instead. Toggle at runtime via palette.setCameraMode('screenshot').
Pattern: sub-menu via replace()
dddk.palette.addItem({
id: 'language',
name: 'Change language',
icon: '🌐',
section: 'Settings',
handler: (p) => {
p.replace([
{ id: 'lang-en', name: 'English', handler: (h) => { setLocale('en'); h.close(); } },
{ id: 'lang-zh', name: '繁體中文', handler: (h) => { setLocale('zh-TW'); h.close(); } },
{ id: 'lang-ja', name: '日本語', handler: (h) => { setLocale('ja'); h.close(); } },
]);
},
});
replace swaps the current items — close then returns to the original set.
Removing or updating items
dddk.palette.removeItem('go-billing');
dddk.palette.setItems(newList); // full replace
If you add items dynamically (per route, per user role), use removeItem on the previous set first to avoid duplicates.