Composition: the provider surface
This is a guide, not the contract. What the platform guarantees is specified under
openspec/specs/. For this page:platform-composition·host-services. 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.
There is no single god-provider, on purpose: each decision has its own provider, so the same decision never has two doors. This page is the whole surface indexed by what you want; each row points at the guide section that tells the story.
What the product is
| I want to … | provider |
|---|---|
| set the name, logo and tagline | provideProductIdentity (Branding) |
| decide which regions exist and where | provideLayout (Layout) |
| recolour the whole app | the design tokens (tokens) |
| change sizes, radii, density | your own CSS on the class contracts (tokens) |
| replace a built-in icon | provideIcons (Icons) |
| reword the shell itself (“Folder” instead of “View”) | provideTranslationOverrides (Rewording) |
| ship my own translations | provideTranslationNamespaces (i18n) |
What users are allowed to do
| I want to … | provider |
|---|---|
| take a gesture away (splitting, pinning, pop-out, shortcuts …) | provideShellFeatures (Switching capabilities off) |
| change a switch while the app runs, or read it | FeatureSwitches (Switches) |
| offer a pane, workspace, sidebar or reset action from my own control | the services on these pages (Panes, Workspaces, Sidebars, Resetting the application) |
| drop a built-in command, item, settings row, menu entry or route | provideShell({ omit }) (Recomposing host chrome) |
| hand out layouts the product defines | provideWorkspaces (Developer-defined workspaces) |
| let users put the arrangement back | nothing: shell.app.reset ships (Resetting the arrangement) |
What ships inside
| I want to … | provider |
|---|---|
| compose my weaver | providePlugins + provideCapabilityGrants (Capabilities) |
| keep a plugin from being switched off | provideRequiredPlugins (A plugin your application cannot run without) |
| run an isolated plugin | provideFramePlugins (Frame plugins) |
| offer a plugin catalogue | providePluginCatalog (Plugin store) |
| add chrome of my own | provideBarItems, provideRailItems, provideViews (Do it below) |
| put a search entry in a bar | provideCommandPaletteEntry, provideQuickOpenEntry (Command palette entry) |
What it talks to
| I want to … | provider |
|---|---|
| feed the signed-in user in | provideAuthSource (Auth integration) |
| send gated routes to my login | provideUnauthorizedRedirect (Redirect) |
| store settings in my backend | provideSettingsStore (Persistence stores) |
| store working state in my backend | provideWorkingStateStore (Persistence stores) |
| keep two users in one browser apart | provideIdentityScopedStores (Identity-scoped stores) |
| compute a following tab’s address myself | provideTabAddressResolver (Following tabs) |
| route the content area | provideShellRouter (Content-area routing) |
| ship without a service worker | provideShell({ serviceWorker: false }) (PWA) |
| keep hidden surfaces alive by default | provideShell({ retention }) (Surface retention) |
Do it
A distribution does not need a plugin to add chrome. Three providers contribute the same shapes a weaver contributes, statically at composition time:
// src/app/app.config.ts — in the providers array...provideViews({ id: 'acme.inspector', title: 'acme.inspector.title', region: 'left-panel', component: Inspector }),...provideBarItems({ id: 'acme.status', bar: 'status-bar', slot: 'end', component: BuildStatus }),...provideRailItems({ id: 'acme.help', rail: 'primary', icon: 'help', title: 'acme.help', anchor: 'bottom', command: 'acme.openHelp' }),Read it
ContributionRegistry is the registry underneath the providers. Three of its signals answer
questions about your own composition, and are what
loomweaver.report() reads:
| Signal | Holds |
|---|---|
omitted |
the ids your omit list names, exactly as you wrote them, prefixes and all |
registeredIds |
every id registered so far, of any kind, including the ones omit hides |
registeredCommands |
every command with the plugin the host stamped on it as its owner (RegisteredCommand); the shell’s own commands carry no owner, which is what tells one plugin’s commands from another’s |
What asks about unsaved work
Nothing on this page asks; a provider runs once at composition time, before any surface exists.
Switched off
No switch governs the providers. provideShellFeatures is where the switches themselves are declared; Switches reads and changes them while the app runs.
In depth
Ids address regions. region, bar and rail must name a region id declared in your
provideLayout; the ids above match the getting-started layout. A
contribution addressing an id no region declares renders nowhere, and a view logs a dev-mode warning.
Ids replace. Because ids are the addressing scheme everywhere, using an existing id replaces
that contribution, which is how the demo moves the update badge into a sidebar footer.
provideShell({ omit: [...] }) removes one.
At runtime. Injecting ContributionRegistry lets you add and remove contributions while the
app runs; addRailItem returns a disposer. Prefer the providers when the answer is known at
composition time.
Reading registeredIds. It is the only way to tell an omit that hid something from one that
hit nothing at all, because an omitted contribution is by construction absent from every other
signal. Reach for the report first: it already phrases the answer, including which prefix you
probably meant.
Where the story is told
- Building a distribution: the composition root and every provider in context.
- Seeing what you composed: the development-time composition report.
- Recomposing host chrome: the ids you can omit, and where a contribution of yours lands.