Skip to content

Interface: UiService

Defined in: packages/sdk/src/ui-service.ts:291

The user-interaction domain, exposed as ExtensionContext.ui. The host renders the chrome; an extension only asks. Interactions today:

  • Native OS dialogspickFolder, pickFile, savePath. Thin wrappers over the platform dialogs the host owns; each resolves to an absolute path or null when the user cancels.
  • Notificationsnotify shows a transient toast, optionally with a title and action buttons (see NotifyOptions). The only way an extension can proactively message the user.
  • MenusshowMenu pops a context menu or button dropdown, themed to match the rest of the app.
  • Modal dialogsconfirm and prompt pop a host-owned modal and resolve on the user's choice; showModal pops one around your own custom content (a form or bespoke layout).
  • External linksopenExternal hands a URL to the OS (browser / mail client), the host's gateway to the world outside the app.

Mirrors VS Code's window.show*. More host-rendered chrome (quick-pick, progress) is planned — see the roadmap.

Methods

pickFolder()

ts
pickFolder(opts?): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:298

Show the native folder picker. Resolves to the chosen absolute path, or null if the user cancelled.

Parameters

opts?
defaultPath?

string

Absolute path to open the dialog at.

Returns

Promise<string | null>


pickFile()

ts
pickFile(opts?): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:306

Show the native open-file picker (single selection). Resolves to the chosen absolute path, or null if the user cancelled.

Parameters

opts?
defaultPath?

string

Absolute path to open the dialog at.

filters?

FileFilter[]

Restrict the selectable file types (see FileFilter).

Returns

Promise<string | null>


savePath()

ts
savePath(opts?): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:317

Show the native save dialog. Resolves to the chosen destination's absolute path, or null if the user cancelled.

Parameters

opts?
defaultPath?

string

Seeds the dialog's location and suggested filename.

filters?

FileFilter[]

Restrict the file-type dropdown (see FileFilter).

Returns

Promise<string | null>


notify()

ts
notify(
   level, 
   message, 
   options?): void;

Defined in: packages/sdk/src/ui-service.ts:352

Show a transient toast notification to the user. Fire-and-forget — the host renders it (and, for info / warn without actions, auto-dismisses it). level drives the icon and accent.

Pass NotifyOptions for a bold title, footer actions, or an explicit durationMs. Errors and toasts with actions stay until dismissed (so a "View details" action isn't lost to the timer); everything else auto-dismisses after ~4s.

Parameters

level

"info" | "warn" | "error"

message

string

options?

NotifyOptions

Returns

void

Example

ts
// a plain info toast (auto-dismisses)
ctx.ui.notify("info", "Theme exported.");

// an error with a title and an action that opens the full detail in a modal
ctx.ui.notify("error", String(err), {
  title: "Commit failed",
  actions: [
    {
      label: "View details",
      run: () =>
        ctx.ui.showModal((close) => <pre>{String(err)}</pre>, {
          title: "Commit failed",
          dismissible: true,
        }),
    },
  ],
});

showMenu()

ts
showMenu(opts): Promise<void>;

Defined in: packages/sdk/src/ui-service.ts:387

Pop a menu — the same themed primitive behind every context menu and dropdown in Silo. Supply the rows and where to place it (see ShowMenuOptions); the host renders it, runs the chosen item's run, and dismisses on outside-click or Escape.

Only one menu is open at a time — calling showMenu again replaces it, except that re-opening with the same anchor toggles it closed (a second click on a dropdown button dismisses it); opt out with toggle: false. Resolves once an item runs or the menu is dismissed.

Parameters

opts

ShowMenuOptions

Returns

Promise<void>

Example

ts
// A right-click context menu at the cursor.
element.addEventListener("contextmenu", (e) => {
  e.preventDefault();
  ctx.ui.showMenu({
    items: [
      { label: "Rename", run: rename },
      { type: "separator" },
      { label: "Delete", danger: true, run: del },
    ],
  });
});

// A dropdown anchored under a button.
ctx.ui.showMenu({ items, anchor: buttonEl });

confirm()

ts
confirm(opts): Promise<boolean>;

Defined in: packages/sdk/src/ui-service.ts:406

Pop a host-rendered confirm dialog and resolve to the user's choice — true for confirm, false for cancel. Always dismissible: Escape and backdrop-click both resolve false. The dialog stacks above all host chrome via the modal manager, so it works from anywhere.

Parameters

opts

ConfirmOptions

Returns

Promise<boolean>

Example

ts
if (await ctx.ui.confirm({
  title: "Delete workspace?",
  body: `"${name}" and its saved terminals will be permanently removed.`,
  confirmLabel: "Delete",
  danger: true,
})) {
  service.delete(id);
}

prompt()

ts
prompt(opts): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:417

Pop a host-rendered single-line input dialog and resolve to the entered string, or null if the user cancelled (Escape / backdrop / Cancel).

Parameters

opts

PromptOptions

Returns

Promise<string | null>

Example

ts
const name = await ctx.ui.prompt({ title: "Rename", initialValue: current });
if (name !== null) rename(name);

showModal()

ts
showModal<T>(render, options?): Promise<T | undefined>;

Defined in: packages/sdk/src/ui-service.ts:454

Pop a host-rendered modal around your own custom content — the escape hatch beyond confirm / UiService.prompt | prompt when you need a form or bespoke layout. The host owns the hard parts (backdrop, central z-stacking above all chrome, focus trap, restore-focus-on-close); you own the content.

Supply a render callback that receives a close function and returns the modal's content; wire your own buttons to close(result) (or close() to cancel). The returned promise resolves with the value passed to close, or undefined if the modal was dismissed (only possible when ModalOptions.dismissible is set) or close() was called with no argument — paralleling confirmfalse / promptnull. If you must tell "dismissed" from "closed with no result" apart, pass a distinct sentinel.

Not dismissible by default: unless you set ModalOptions.dismissible, Escape and backdrop-click do nothing and the modal stays open until your content calls close. A non-dismissible modal whose content never calls close leaves the promise pending forever — by design, so staged edits can't be lost to an accidental click-away.

Type Parameters

T

T = void

The result type your content resolves with via close.

Parameters

render

(close) => ReactNode

Returns the modal content; receives close to settle it.

options?

ModalOptions

Presentation options (see ModalOptions).

Returns

Promise<T | undefined>

Example

tsx
const changes = await ctx.ui.showModal<Changes>(
  (close) => (
    <MyForm onCancel={() => close()} onSave={(c) => close(c)} />
  ),
  { title: "Properties", size: "md" },
);
if (changes) apply(changes);

openExternal()

ts
openExternal(url): Promise<void>;

Defined in: packages/sdk/src/ui-service.ts:486

Hand a URL to the operating system — open an http/https link in the user's default browser, or a mailto: link in their mail client. The host owns the privileged platform access; this is an extension's only sanctioned way to send the user out of the app.

Scheme-guarded. Only http:, https:, and mailto: URLs are opened; any other scheme (notably file: and javascript:) is rejected — the returned promise rejects, nothing is opened. This makes it safe to pass untrusted URLs (e.g. links inside a rendered Markdown document) straight through without first vetting the scheme yourself.

Parameters

url

string

The URL to open. Must be http:, https:, or mailto:.

Returns

Promise<void>

Throws

If url has any other scheme (or is unparseable).

Example

ts
// open a docs link in the browser
await ctx.ui.openExternal("https://silo.dev/docs");

// route a clicked Markdown link safely — bad schemes just reject
try {
  await ctx.ui.openExternal(href);
} catch {
  ctx.ui.notify("warn", "That link can't be opened.");
}

getActiveSelectionText()

ts
getActiveSelectionText(): string | null;

Defined in: packages/sdk/src/ui-service.ts:501

The text currently selected in the focused surface — the active editor or a focused terminal — or null when nothing is selected. Reads the most-recently-focused of the two, so a command (e.g. "Find in Files") can seed itself with whatever the user has highlighted regardless of which surface has focus. Returns null (never throws) when no surface is focused or the selection is empty.

Returns

string | null

Example

ts
const seed = ctx.ui.getActiveSelectionText();
if (seed) runSearch(seed);