Skip to main content

Widgets

Closer to a small window than to a panel. Widget is the largest contribution interface in the SDK — 23 members — and most of them are about window behaviour rather than content.

import { function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): Mod
The entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
, function widget(widgetValue: Widget): Widget
Declare a floating surface: a heads-up panel, an overlay, or a detached window of its own. Same runtime as `view`. The component is a module reference the host mounts in a sandboxed renderer webview, with no Node access, talking to the ext-host half of the mod through `ctx.action(...)` and shared state. The difference is placement and framing. A widget is not a container panel: it sits in a widget slot or detaches into a window of its own, and it carries its own size bounds, frame, transparency, and always-on-top behaviour. It can be opened by a command or a socket trigger. Docked panel, use `view`. Anything that floats over the workbench or detaches into a separate window, use `widget`. For UI that lives inside a document rather than on top of it, use `editorExtension` or `canvasElement`, which run renderer-native rather than over RPC.
@categoryAuthoring
widget
} from '@modular/sdk';
export default
defineMod<{
    readonly metadata: {
        readonly displayName: "Panels";
        readonly description: "Contributed surfaces";
    };
    readonly widgets: readonly [Widget];
}>(definition: {
    readonly metadata: {
        readonly displayName: "Panels";
        readonly description: "Contributed surfaces";
    };
    readonly widgets: readonly [Widget];
}): Mod
The entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
({
metadata: {
    readonly displayName: "Panels";
    readonly description: "Contributed surfaces";
}
metadata
: { displayName: "Panels"displayName: 'Panels', description: "Contributed surfaces"description: 'Contributed surfaces' },
widgets: readonly [Widget]widgets: [ function widget(widgetValue: Widget): Widget
Declare a floating surface: a heads-up panel, an overlay, or a detached window of its own. Same runtime as `view`. The component is a module reference the host mounts in a sandboxed renderer webview, with no Node access, talking to the ext-host half of the mod through `ctx.action(...)` and shared state. The difference is placement and framing. A widget is not a container panel: it sits in a widget slot or detaches into a window of its own, and it carries its own size bounds, frame, transparency, and always-on-top behaviour. It can be opened by a command or a socket trigger. Docked panel, use `view`. Anything that floats over the workbench or detaches into a separate window, use `widget`. For UI that lives inside a document rather than on top of it, use `editorExtension` or `canvasElement`, which run renderer-native rather than over RPC.
@categoryAuthoring
widget
({
Widget.id: stringid: 'panels.hud', Widget.title: stringtitle: 'HUD', Widget.component: ViewComponentReferencecomponent: { ViewComponentReference.module: stringmodule: './views/hud' }, Widget.detached?: boolean | undefineddetached: true, }), ], });

Members by what they decide

Widget

GroupMembers
identityid title description icon component
placementdefaultSlot defaultSize minSize maxSize resizable
windowdetached frame alwaysOnTop transparent backgroundOpacity
dismissalclickOutsideToClose escapeToClose persistent
activationcommand triggers when
escape hatchextras

detached, frame, and alwaysOnTop are the three that make "small window" concrete. A widget with all three set behaves like a floating utility window, not like part of the workbench.

Dismissal is a choice, not a default

clickOutsideToClose, escapeToClose, and persistent interact. A persistent widget that also closes on click-outside will not feel persistent to the person using it. Decide which one is true and set the others to match.