Context: UI and Shadow-DOM isolation
context.ui
Section titled “context.ui”showNotification(message: string, type?: "error" | "info" | "warning"): void;
showStatusBarItem(text: string, align?: "left" | "right"): StatusBarItem;// StatusBarItem = { setText(text: string): void; dispose(): void }
addStyle(css: string): Disposable;
addSidebarPanel(opts: PluginSidebarPanelOptions): Disposable;addSettingsTab(opts: PluginSettingsTabOptions): Disposable;// PluginSidebarPanelOptions = { id: string; title: string; icon?: string;// onMount(el: HTMLElement): void; onUnmount?(el: HTMLElement): void }// PluginSettingsTabOptions = { id: string; title: string;// onMount(el: HTMLElement): void; onUnmount?(el: HTMLElement): void }
registerFileViewer(opts: PluginFileViewerOptions): Disposable;// PluginFileViewerOptions = { id: string; extensions: string[];// onMount(el: HTMLElement, ctx: PluginFileViewerContext): void;// onUpdate?(el: HTMLElement, ctx: PluginFileViewerContext): void;// onUnmount?(el: HTMLElement): void }// PluginFileViewerContext = { assetUrl: string; filePath: string;// refreshKey: number; zoomLevel: number }context.ui itself is available whenever the manifest declares sidebar,
statusbar, settings, or viewer (any one unlocks the object), but each
method has its own per-method gate:
| Method | Requires capability |
|---|---|
showStatusBarItem |
statusbar |
addSidebarPanel |
sidebar |
addSettingsTab |
settings |
registerFileViewer |
viewer |
showNotification |
any of sidebar / statusbar / settings / viewer |
addStyle |
any of sidebar / statusbar / settings / viewer |
Notes:
showStatusBarItemreturns aStatusBarItemobject — call.setText(...)to update the text in place,.dispose()to remove it. Its second parameter isalign: "left" | "right"and defaults to"right".addStyle(css)injects a<style>tag intodocument.head(light DOM). It cannot style content inside a Shadow-DOM sidebar panel or settings tab — see Shadow-DOM UI isolation.addSidebarPanel/addSettingsTabboth mount into an isolated Shadow-DOM subtree viaonMount(el)— see the next section.registerFileViewermakes the app open files with the listed extensions in your viewer instead of the code editor. The host handsonMounta plain (light-DOM) element plus a context:assetUrlis the file served over theasset:protocol (already cache-busted withrefreshKey), andzoomLevelis the shared editor zoom (Cmd+= / Cmd+- / Cmd+0, Ctrl+wheel) — scaling your content with it is your viewer’s job.onUpdatefires when the context changes while mounted (zoom, save, external reload). For text extensions the app keeps its preview ↔ source toggle: your viewer renders the preview side, CodeMirror the source side. Binary extensions are viewer-only, and the app’s binary guards (no UTF-8 reads, no text saves) apply whether or not your plugin is enabled. The built-inmedia-viewerplugin (src/plugins/builtin/media-viewer.ts) is the reference implementation.
Shadow-DOM UI isolation
Section titled “Shadow-DOM UI isolation”addSidebarPanel and addSettingsTab don’t render your markup directly
into the app’s DOM tree. Instead, the host attaches an open Shadow DOM
to a mount point and calls your onMount(el) with a <div> that lives
inside that shadow root. This isolates the panel’s CSS from the rest of
the app (and vice versa) — the app’s global stylesheets do not leak in, and
whatever CSS the panel injects does not leak out.
Practical consequences:
-
Style shadow content by appending a
<style>element toelinsideonMount— not withcontext.ui.addStyle(), which targetsdocument.head(light DOM) and never reaches shadow content. Both example plugins do this:function appendStyle(el: HTMLElement, css: string): void {const style = document.createElement("style");style.textContent = css;el.appendChild(style);}onMount(el) {appendStyle(el, PANEL_STYLE);// ...build your panel's DOM under el} -
CSS custom properties inherit across the shadow boundary. The app’s design-token variables (
var(--color-text-default),var(--color-border-default),var(--color-bg-subtle), etc.) are still visible inside your shadow root, so you can theme your panel against the live app theme without duplicating token values. Seeexamples/plugins/ai-summary/src/index.tsfor a full example that themes entirely off inherited custom properties. -
onMount(el)receives the shadow root’s inner content<div>, not theShadowRootobject itself (aShadowRoothas no.style/.classList).onUnmount(el), if provided, is called before the host removes the subtree — use it for teardown that isn’t a trackedDisposable(timers, manual event listeners you added directly toel’s descendants, etc).

