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.
- 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 … | 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 |
| 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 |
- 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
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.