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.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.condition, function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): ModThe 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.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.condition({ ConditionInput<"hello.hasSelection">.id: "hello.hasSelection"Identifier, unique within the mod.id: 'hello.hasSelection', ConditionInput<TId extends string = string>.title?: string | undefinedLabel shown where the host explains why something is disabled.title: 'Something is selected', ConditionInput<TId extends string = string>.when: ConditionPredicateEvaluated 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 => { constconst 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; } | nullselection = 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; } | nullCurrent value. Reading it inside a reactive body subscribes to it.value; returnconst 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; } | nullselection !== 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 defaultdefineMod<{ 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>]; }): ModThe 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.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.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 => { constconst 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; } | nullselection = 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; } | nullCurrent 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; } | nullselection === 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(): stringConverts all the alphabetic characters in a string to uppercase.toUpperCase(), }); }, }), ], });
Members
Condition — id, 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.