Skip to content

Distribution API

This is a guide, not the contract. What the platform guarantees is specified under openspec/specs/. For this page: host-services · gesture-configuration · platform-composition. Where this page and a specification disagree, the specification is right, and that is a defect in this page: change the behaviour there, then explain it here.

Everything a user does in the workbench by hand, your product’s own code can do too. This area is the lookup for it: what a distribution may inject and call, the rules behind it, and one page per area.

A plugin never injects these. A weaver gets a brokered subset through ctx (ctx.ui.confirm(), ctx.registerSettingsSection(), ctx.session), and the broker checks a capability first (default-deny). That indirection is the whole isolation story: it is what lets the same weaver run sandboxed in an iframe, where a direct injection would be impossible.

Your distribution does inject them. Your composition root, your login page and your own components are the application, not a guest in it. Every service below is providedIn: 'root', so inject(TheService) is all it takes.

Shell renders the dialog and toast outlets, so there is nothing to place in a template. DialogOutlet and ToastOutlet exist for a distribution that builds its own root component instead.

The rules behind every page

  • A switch moves the control, it does not remove the capability. provideShellFeatures and FeatureSwitches take a gesture away from the user; the service that performs it keeps working for you.
  • The twin is the same code. Every action on these pages is the one the built-in control runs, with the same guards. Closing a pane from code asks about unsaved work exactly as the × does.
  • Facts are signals. What you can read re-evaluates where you read it; there is no separate event saying the same thing.
  • Prevention belongs to the owner. The surface that holds unsaved work decides whether it may be closed; a service asks it, and answers whether it ran where a caller needs to know.

I want to …

I want to …CallPage
decide what the product is made ofthe providers, indexed by intentComposition
add chrome without a pluginprovideViews, provideBarItems, provideRailItemsComposition
read a capability switchswitches.content.splitRight()Switches
change a switch while the app runsswitches.update({ content: { splitRight: false } })Switches
open a document as a tabtabs.open({ path, title }), tabs.navigateTo(path)Tabs
pin, keep or close a tabtabs.pin(path), tabs.keep(path), tabs.close(path)Tabs
bring a tab to the front where it already livestabs.revealContentTab(path)Tabs
know which tab is activetabs.activeContent(), tabs.tabs()Tabs
split the content areapanes.splitRight(), panes.splitDown(handle)Panes
close a pane or undo the splitpanes.closePane(handle), panes.unsplit()Panes
fill the area with one pane, or collapse onepanes.maximize(handle), panes.minimize(handle), panes.restore()Panes
move the address to a pane, move a tab into onepanes.focus(handle), panes.moveTab(path, handle)Panes
read the arrangementpanes.panes(), panes.isSplit(), panes.activePane()Panes
switch to a workspaceworkspaces.switchTo(id)Workspaces
save the arrangement as a workspace, or as the baselineworkspaces.saveCurrent(name), workspaces.saveBaseline()Workspaces
reset one workspace, or allawait workspaces.reset(id?), await workspaces.resetAll()Workspaces
rename or remove a saved workspaceworkspaces.rename(id, name), await workspaces.remove(id)Workspaces
know which workspace is active, and which changedworkspaces.activeId(), workspaces.hasChanges(), workspaces.changedIds()Workspaces
collapse or expand a sidebarsidebars.collapse(regionId), sidebars.expand(regionId), sidebars.toggle(regionId)Sidebars
set a sidebar’s widthsidebars.setWidth(regionId, px)Sidebars
hide a view, or show it againsidebars.hideView(viewId), sidebars.showView(viewId)Sidebars
read the sidebarssidebars.regions(), sidebars.hiddenViews()Sidebars
put the whole arrangement backawait appReset.reset({ workspaces: true })Resetting the application
ask the user somethingdialogs.confirm(…), dialogs.prompt(…), dialogs.open(MyDialog)Dialogs and toasts
show progress while a promise runsawait dialogs.withProgress({ message }, work)Dialogs and toasts
show a toast, or take it down againtoasts.show({ message, kind }), toasts.dismiss(id)Dialogs and toasts
add or open a settings sectionsettings.register(…), settings.open(sectionId)Settings
remove a settings section againhandle.dispose() on what register returnedSettings
run a command from codecommands.execute(id), await commands.run(command)Commands
show a shortcut the way the shell doesformatChord('mod+k')Commands
know who is signed inauth.authenticated(), auth.roles(), auth.meets(access)Session
follow light and dark in my own UItheme.resolvedTheme(), theme.setMode(mode)Appearance
set the text sizetextSize.setScale('lg')Appearance
open the plugin storestore.open()Plugins at runtime
turn a plugin off, revoke a capability, uninstallenablement.setEnabled(plugin, false), grants.setGranted(plugin, capability, false), install.uninstall(plugin)Plugins at runtime
ask for a picture of the workbenchawait capture.capture()A picture of the workbench
open a surface in its own windowpopout.open(target)Windows, sync and updates
make my own state follow across windowssync.register(…), sync.announce(key)Windows, sync and updates
apply a change my backend pushed from another devicesync.notifyRemoteChange(key)Windows, sync and updates
point the version at my own build infoversion.version.set(v)Windows, sync and updates
react to a new versionupdates.updateAvailable(), await updates.activateUpdate()Windows, sync and updates
check for a new version by handawait updates.checkForUpdate()Windows, sync and updates

The pages

The per-symbol reference is the package itself: @loomweaver/shell ships typed declarations with JSDoc on every public member, which your editor shows in place. A plugin never injects any of this; it goes through ctx, which is described in Authoring a weaver.