Slots and actions
A slot is a place an extension can add to, such as a node's header, the canvas menu or the editor's status bar.
Three things go in slots:
defineActionis a button in a bar, or an item in a menu.defineMenuis a menu of your own: a submenu in a menu, a dropdown in a bar.defineWidgetis your own React.
Actions
defineAction({
id: 'summarize',
title: 'Summarize',
icon: 'sparkles',
shortcut: 'mod+alt+u',
slots: {
selectionBar: ({ nodes }) => nodes.length > 1,
nodeMenu: true
},
async run({ nodes, signal }) {
// ...
}
});Each slot gets a function that receives what is true at that place and answers:
falseto hide the action there,trueto show it as it is,- an object to change what it shows there:
title,icon,badge,order, ordisabledwith an optional reason.
slots: {
nodeHeader: ({ node }) => node.kind === 'terminal' && {
badge: 'new'
}
}While run is pending, the nodes it is about show a spinner, which also stops the action through signal, and its button is disabled for those nodes. When run throws, the person sees the error. For work that is no action, host.busy shows the same spinner. Add a button builds an action for a terminal's header and the canvas menu.
An action without slots shows only where a menu of yours puts it, and gets that menu's context. Type that context yourself with defineAction<{ node: NodeInfo }>.
A shortcut needs mod or alt. mod is Cmd on macOS and Ctrl elsewhere, so 'mod+alt+u' is CtrlAltU on this computer. Pressed, it runs with the context of the command palette. Ruimte's own shortcuts come first. Add a command walks through one.
Menus
defineMenu({
id: 'standup',
title: 'Standup',
icon: 'calendar',
slots: { nodeMenu: true, canvasMenu: true },
items: (context) => [
summarize,
'separator',
{
title: 'Settings',
run: () => host.openSettings()
}
]
});An item is an action, 'separator', an item with title and run (and checked for a toggle), or a submenu with title and items. In a menu of Ruimte's own, an extension always comes below Ruimte's items.
Widgets
defineWidget({
id: 'count',
slots: { editorStatusBar: true },
component: () => import('./WordCount')
});The component gets context, the same object a slot function gets. A widget works in every slot, also in a menu.
Every slot
Every slot also gets the base context: the project, the view in front, the machine and the theme.
selectionBar{ nodes: NodeInfo[] }nodeHeader{ node: NodeInfo }nodeMenu{ node: NodeInfo; nodes: NodeInfo[] }nodesis the selection if the node is in it.canvasMenu{ at: { x: number; y: number } }viewToolbarnothing moreviewMenu{ target: { id: string; kind: string } }panelHeader{ panel: string }commandPalette{ nodes: NodeInfo[] }sidebarRow{ node: NodeInfo }tabMenu{ target: { id: string } }emptyCanvasnothing morestartScreennothing moreprojectBannernothing morethreadHeader{ chat: { id: string }; node: NodeInfo }emptyThread{ chat: { id: string } }toolActions{ chat: { id: string }; tool: ToolCall }fileRowBadge{ file: FileInfo }fileBreadcrumb{ file: FileInfo }terminalMenu{ node: NodeInfo }browserMenunothing moretextMenu{ text: { id: string } }noteToolbar{ node: NodeInfo }routineMenu{ routine: { id: string } }composer{ chat: { id: string }; node: NodeInfo }projectMenunothing moresidebarFooternothing moreeditorStatusBar{ file: FileInfo }messageActions{ chat: { id: string }; message: { id: string; kind: 'user' | 'assistant'; text: string } }lineMenu{ from: NodeInfo; to: NodeInfo }fileMenu{ file: FileInfo; files: FileInfo[] }editorToolbar{ file: FileInfo }editorMenu{ file: FileInfo; files: FileInfo[] }diffToolbar{ repository: string }terminalToolbar{ node: NodeInfo }browserToolbar{ node: NodeInfo; url: string }
The command palette
commandPalette puts an action in the palette. definePaletteSource adds rows for what a person types, from your own search:
definePaletteSource({
id: 'issues',
title: 'Issues',
search: async (query) => {
const issues = await findIssues(query);
return issues.map((issue) => ({
id: issue.id,
title: issue.title,
detail: issue.state,
run: () => host.openUrl(issue.url)
}));
}
});It runs after a pause in the typing. A source that is slow or fails costs only its own rows. Each row shows its detail beside the title, or the source's title without one:
defineCommand adds a plain command to the palette, with no slots and no context:
defineCommand({
id: 'standup',
title: 'Open the standup',
run: () => standup.open()
});A person finds a command and an action alike, by a word of the title:
Templates
defineTemplate adds an entry under Extensions in the canvas menu. create gets the spot the person clicked as at. A canvas call places a node beside the node it is near, or in the middle of the canvas, so move what you made to at:
defineTemplate({
id: 'review',
title: 'Review setup',
icon: 'list-checks',
create: ({ at }) => {
const note = canvas.createNote({
title: 'Review',
text: 'What to check'
});
if (note.ok) {
canvas.update(note.value, {
x: at.x,
y: at.y
});
}
}
});Finding the slots
While ruimte extension dev runs, "Show extension slots" in the command palette outlines every slot on the screen with its name. Its tooltip lists what the slot tells.