Skip to main content

Conditions

A named boolean that other contributions point at through their when field.

import { function command<TId extends string>(commandValue: CommandInput<TId>): Command<TId>
Declare an action the user runs from the command palette or a keybinding. Reach for this when a person triggers the behavior. The `title` is what they read in the palette, `category` groups the entry, and `when` decides whether it shows up at all. The handler takes no argument, only a context. Two neighbours are easy to confuse. `slashCommand` is also user-triggered, but it is typed inside a chat session and receives free text plus the live AI session. `tool` is not user-triggered at all, the model decides to call it mid-turn. The result goes in the mod's top-level `commands` array, alongside any slash commands. The `id` is the address a keybinding or a `command:` hook targets, so keep it stable.
@categoryAuthoring
command
, function condition<TId extends string>(input: ConditionInput<TId>): Condition<TId>
Define a rule that gates whether something is enabled and visible. Declare it once, list it in `defineMod`'s `conditions` slot, and reference the same value from every `when` that needs it. Reach for this instead of checking state inside a handler when the user should see the command greyed out rather than have it fail on click.
@exampleOnly offer a command while text is selected ```ts const hasSelection = condition({ id: 'my-mod.hasSelection', when: ctx => { const selection = ctx.state('editor.selection').value; return selection !== null && selection.charCount > 0; }, }); command({ id: 'my-mod.shout', title: 'Shout', when: hasSelection, run }); ```@categoryState
condition
, 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
} from '@modular/sdk';
const const hasSelection: Condition<"hello.hasSelection">hasSelection = condition<"hello.hasSelection">(input: ConditionInput<"hello.hasSelection">): Condition<"hello.hasSelection">
Define a rule that gates whether something is enabled and visible. Declare it once, list it in `defineMod`'s `conditions` slot, and reference the same value from every `when` that needs it. Reach for this instead of checking state inside a handler when the user should see the command greyed out rather than have it fail on click.
@exampleOnly offer a command while text is selected ```ts const hasSelection = condition({ id: 'my-mod.hasSelection', when: ctx => { const selection = ctx.state('editor.selection').value; return selection !== null && selection.charCount > 0; }, }); command({ id: 'my-mod.shout', title: 'Shout', when: hasSelection, run }); ```@categoryState
condition
({
ConditionInput<"hello.hasSelection">.id: "hello.hasSelection"
Identifier, unique within the mod.
id
: 'hello.hasSelection',
ConditionInput<TId extends string = string>.title?: string | undefined
Label shown where the host explains why something is disabled.
title
: 'Something is selected',
ConditionInput<TId extends string = string>.when: ConditionPredicate
Evaluated synchronously whenever the state it reads changes. Return `true` to enable or reveal whatever this condition gates. Keep it pure, the host may call it often.
when
: ctx: ConditionContextctx => {
const
const selection: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null
selection
= ctx: ConditionContextctx.
ConditionContext.state<"editor.selection">(name: "editor.selection"): State<{
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null> (+1 overload)
Read built-in host state, such as `'editor.selection'`.
state
('editor.selection').
State<{ workspaceId: string; docId: string; text: string; preview: string; charCount: number; wordCount: number; capturedAt: string; origin: "lexical"; startBlock: number; endBlock: number; startLineNumber?: number | undefined; endLineNumber?: number | undefined; } | { ...; } | null>.value: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null
Current value. Reading it inside a reactive body subscribes to it.
value
;
return
const selection: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null
selection
!== null &&
const selection: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
}
selection
.charCount: numbercharCount > 0;
}, }); export default
defineMod<{
    readonly metadata: {
        readonly displayName: "Hello";
        readonly description: "A first mod";
    };
    readonly conditions: readonly [Condition<"hello.hasSelection">];
    readonly commands: readonly [Command<string>];
}>(definition: {
    readonly metadata: {
        readonly displayName: "Hello";
        readonly description: "A first mod";
    };
    readonly conditions: readonly [Condition<"hello.hasSelection">];
    readonly commands: readonly [Command<string>];
}): 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: "Hello";
    readonly description: "A first mod";
}
metadata
: { displayName: "Hello"displayName: 'Hello', description: "A first mod"description: 'A first mod' },
conditions: readonly [Condition<"hello.hasSelection">]conditions: [const hasSelection: Condition<"hello.hasSelection">hasSelection], commands: readonly [Command<string>]commands: [ command<string>(commandValue: CommandInput<string>): Command<string>
Declare an action the user runs from the command palette or a keybinding. Reach for this when a person triggers the behavior. The `title` is what they read in the palette, `category` groups the entry, and `when` decides whether it shows up at all. The handler takes no argument, only a context. Two neighbours are easy to confuse. `slashCommand` is also user-triggered, but it is typed inside a chat session and receives free text plus the live AI session. `tool` is not user-triggered at all, the model decides to call it mid-turn. The result goes in the mod's top-level `commands` array, alongside any slash commands. The `id` is the address a keybinding or a `command:` hook targets, so keep it stable.
@categoryAuthoring
command
({
id: stringid: 'hello.shout', title: stringtitle: 'Shout the selection', when?: Condition<string> | undefinedwhen: const hasSelection: Condition<"hello.hasSelection">hasSelection, run: (ctx: CommandContext) => Awaitable<unknown>run: async ctx: CommandContextctx => { const
const selection: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null
selection
= ctx: CommandContextctx.
ContextBase.state<"editor.selection">(name: "editor.selection"): State<{
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null> (+1 overload)
Acquire a live state handle. Passing your own `state(...)` declaration gives an {@link OwnedState } you can write with `set`. Passing a built-in name gives a read-only {@link State } : host state changes only through actions.
state
('editor.selection').
State<{ workspaceId: string; docId: string; text: string; preview: string; charCount: number; wordCount: number; capturedAt: string; origin: "lexical"; startBlock: number; endBlock: number; startLineNumber?: number | undefined; endLineNumber?: number | undefined; } | { ...; } | null>.value: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null
Current value. Reading it inside a reactive body subscribes to it.
value
;
if (
const selection: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
} | null
selection
=== null) {
return; } await ctx: CommandContextctx.ContextBase.action<"clipboard.writeText">(id: "clipboard.writeText", input: ClipboardWriteTextInput, options?: HostActionClientRunOptions): Promise<ClipboardWriteTextResult>
Dispatch an action. This is the only way a mod changes the app. Takes a host action id or a bare command id. Inside an event listener, prefer `occurrence.action(...)` so the causal link survives.
action
('clipboard.writeText', {
ClipboardWriteTextInput.text: stringtext:
const selection: {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "lexical";
    startBlock: number;
    endBlock: number;
    startLineNumber?: number | undefined;
    endLineNumber?: number | undefined;
} | {
    workspaceId: string;
    docId: string;
    text: string;
    preview: string;
    charCount: number;
    wordCount: number;
    capturedAt: string;
    origin: "monaco";
    startLineNumber: number;
    startColumn: number;
    endLineNumber: number;
    endColumn: number;
}
selection
.text: stringtext.String.toUpperCase(): string
Converts all the alphabetic characters in a string to uppercase.
toUpperCase
(),
}); }, }), ], });

Members

Conditionid, title, when, kind.

Four members, and title is the one that tells you this is a shared thing. A purely internal predicate would not need a human-readable name.

Where they get used

Almost every contribution kind has a when:

Command.when        SlashCommand.when     Keybinding.when
TreeView.when       Widget.when           Walkthrough.when

Declaring the predicate once as a condition and pointing several when fields at its id beats repeating the expression, for the ordinary reason: one place to change it.

Context keys

conditionContextKey is exported alongside condition for working with the underlying context-key names. Reach for it when you need the key itself rather than the declaration.