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.
provideShellFeaturesandFeatureSwitchestake 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 … | Call | Page |
|---|---|---|
| decide what the product is made of | the providers, indexed by intent | Composition |
| add chrome without a plugin | provideViews, provideBarItems, provideRailItems | Composition |
| read a capability switch | switches.content.splitRight() | Switches |
| change a switch while the app runs | switches.update({ content: { splitRight: false } }) | Switches |
| open a document as a tab | tabs.open({ path, title }), tabs.navigateTo(path) | Tabs |
| pin, keep or close a tab | tabs.pin(path), tabs.keep(path), tabs.close(path) | Tabs |
| bring a tab to the front where it already lives | tabs.revealContentTab(path) | Tabs |
| know which tab is active | tabs.activeContent(), tabs.tabs() | Tabs |
| split the content area | panes.splitRight(), panes.splitDown(handle) | Panes |
| close a pane or undo the split | panes.closePane(handle), panes.unsplit() | Panes |
| fill the area with one pane, or collapse one | panes.maximize(handle), panes.minimize(handle), panes.restore() | Panes |
| move the address to a pane, move a tab into one | panes.focus(handle), panes.moveTab(path, handle) | Panes |
| read the arrangement | panes.panes(), panes.isSplit(), panes.activePane() | Panes |
| switch to a workspace | workspaces.switchTo(id) | Workspaces |
| save the arrangement as a workspace, or as the baseline | workspaces.saveCurrent(name), workspaces.saveBaseline() | Workspaces |
| reset one workspace, or all | await workspaces.reset(id?), await workspaces.resetAll() | Workspaces |
| rename or remove a saved workspace | workspaces.rename(id, name), await workspaces.remove(id) | Workspaces |
| know which workspace is active, and which changed | workspaces.activeId(), workspaces.hasChanges(), workspaces.changedIds() | Workspaces |
| collapse or expand a sidebar | sidebars.collapse(regionId), sidebars.expand(regionId), sidebars.toggle(regionId) | Sidebars |
| set a sidebar’s width | sidebars.setWidth(regionId, px) | Sidebars |
| hide a view, or show it again | sidebars.hideView(viewId), sidebars.showView(viewId) | Sidebars |
| read the sidebars | sidebars.regions(), sidebars.hiddenViews() | Sidebars |
| put the whole arrangement back | await appReset.reset({ workspaces: true }) | Resetting the application |
| ask the user something | dialogs.confirm(…), dialogs.prompt(…), dialogs.open(MyDialog) | Dialogs and toasts |
| show progress while a promise runs | await dialogs.withProgress({ message }, work) | Dialogs and toasts |
| show a toast, or take it down again | toasts.show({ message, kind }), toasts.dismiss(id) | Dialogs and toasts |
| add or open a settings section | settings.register(…), settings.open(sectionId) | Settings |
| remove a settings section again | handle.dispose() on what register returned | Settings |
| run a command from code | commands.execute(id), await commands.run(command) | Commands |
| show a shortcut the way the shell does | formatChord('mod+k') | Commands |
| know who is signed in | auth.authenticated(), auth.roles(), auth.meets(access) | Session |
| follow light and dark in my own UI | theme.resolvedTheme(), theme.setMode(mode) | Appearance |
| set the text size | textSize.setScale('lg') | Appearance |
| open the plugin store | store.open() | Plugins at runtime |
| turn a plugin off, revoke a capability, uninstall | enablement.setEnabled(plugin, false), grants.setGranted(plugin, capability, false), install.uninstall(plugin) | Plugins at runtime |
| ask for a picture of the workbench | await capture.capture() | A picture of the workbench |
| open a surface in its own window | popout.open(target) | Windows, sync and updates |
| make my own state follow across windows | sync.register(…), sync.announce(key) | Windows, sync and updates |
| apply a change my backend pushed from another device | sync.notifyRemoteChange(key) | Windows, sync and updates |
| point the version at my own build info | version.version.set(v) | Windows, sync and updates |
| react to a new version | updates.updateAvailable(), await updates.activateUpdate() | Windows, sync and updates |
| check for a new version by hand | await updates.checkForUpdate() | Windows, sync and updates |
The pages
- Composition: the provider surface, and chrome without a plugin
- Switches · Tabs · Panes · Workspaces · Sidebars · Resetting the application
- Dialogs and toasts · Settings · Commands · Session
- Appearance · Plugins at runtime · Windows, sync and updates
- A picture of the workbench
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.