Plugin development Reference

Renderer context

The PluginRendererContext API for a plugin's renderer.js entry — slots, commands, surfaces, and the tab-close interceptor.

A plugin’s renderer entry (renderer.js) receives a PluginRendererContext as the ctx argument to activate(ctx). It runs inside the app’s renderer and is where you contribute UI to slots, register palette commands and pane surfaces, listen to renderer hooks, and call into your plugin’s main half.

PluginRendererContext

APIPurpose
ctx.hooks.on(name, fn)Listen to a renderer hook (see the Hook catalog)
ctx.slots.contribute(slot, item)Contribute UI to a slot (see the Slot catalog)
ctx.commands.register(cmd)Palette command (sugar for the commands slot); prefix ids <plugin-id>.
ctx.surfaces.register(type, def)Register a pane SURFACE renderer (see Pane surfaces & panels); prefix types <plugin-id>.
ctx.workspace.openSurface(type, {state?, title?})Open one of this plugin’s surfaces as a pane-tab
ctx.workspace.onTabClosing(fn)Intercept a pane-tab close (below)
ctx.invoke(name, payload?)Call this plugin’s main ctx.ipc.handle(name)
ctx.reactThe app’s React instance
ctx.track(d)Tie an arbitrary disposable to the plugin lifetime

Use ctx.react for all elements — const h = ctx.react.createElement. Never bundle your own React; the renderer entry is plain ESM with no imports.

Tab-close interceptor

ctx.workspace.onTabClosing(fn) is different from the fire-and-forget renderer hooks: it’s an interceptor that can take over (veto / replace) a pane-tab close.

The callback receives { tab, running, close }:

  • tab — the pane-tab being closed.
  • running — the busy label (foreground process or detected agent), or null when idle.
  • close() — performs the actual layout close.

Return true to claim the close: the core’s default confirm dialog is skipped and you become responsible for calling close() yourself (or not, if the user cancels). Return false to fall through to the next interceptor / the default flow. The first interceptor to claim wins.

onTabClosing returns a tracked Disposable, so the interceptor is removed when the plugin deactivates.

const sub = ctx.workspace.onTabClosing(({ tab, running, close }) => {
  if (!running) return false; // idle — let the default flow handle it
  // show your own Kill / Keep-running / Cancel prompt, then call close() yourself
  showPrompt({ onKill: () => close() });
  return true; // claimed
});

The Sprites core plugin uses this to prompt Kill / Keep-running / Cancel when closing a sprite tab.

Next

See the Slot catalog for every UI surface you can contribute to, and Pane surfaces & panels for rendering whole pane-tabs and building movable feature panels.