Skip to content

Building a distribution

This is a guide, not the contract. What the platform guarantees is specified under openspec/specs/. For this page: platform-composition · shell-layout. 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.

A distribution is your product: a thin app that composes @loomweaver/shell + your weaver(s), declares a layout, grants capabilities and sets branding. It’s mostly one file: the composition root.

The composition root

Everything a distribution is lives in one providers array, in src/app/app.config.ts, the file ng new and Nx both generate. Every “add this provider” instruction in these guides means that array. src/main.ts, src/app/app.ts and src/app/app.html stay as generated, except that App renders <lw-shell />; see manual setup for those three files.

src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import {
provideShell, provideShellRouter, provideLayout, providePlugins,
provideTranslationNamespaces, provideCapabilityGrants, type ShellLayout,
} from '@loomweaver/shell';
import { provideProductIdentity } from '@loomweaver/plugin-sdk';
import { notesWeaver } from '@my/notes-weaver';
const layout: ShellLayout = {
regions: [
{ id: 'top-bar', type: 'bar', dock: 'top' },
{ id: 'primary', type: 'rail', dock: 'left' },
{ id: 'left-panel', type: 'panel', dock: 'left' },
{ id: 'main', type: 'content', dock: 'center' },
{ id: 'right-panel', type: 'panel', dock: 'right' },
{ id: 'status-bar', type: 'bar', dock: 'bottom' },
],
};
export const appConfig: ApplicationConfig = {
providers: [
provideShellRouter(), // content-area routing — replaces provideRouter([])
provideShell(),
provideLayout(layout),
provideProductIdentity({ name: 'Notes Studio', tagline: 'product.tagline', logoUrl: 'logo.png' }),
provideTranslationNamespaces('notes', 'product'),
// Default-deny: grant the weaver exactly what its manifest declares.
provideCapabilityGrants({ notes: ['contributions', 'ui', 'host'] }),
...providePlugins(notesWeaver), // variadic, returns an array — note the spread
],
};

That’s the whole product wiring. The shell renders the chrome; the weaver fills it.

Which door does my decision go through?

There is no single god-provider, on purpose: each decision has its own provider, so the same decision never has two doors. The whole surface, indexed by what you want, is one page in the reference: Composition: the provider surface. Everything your own code can do at runtime is the rest of that area: Distribution API.

Seeing what you composed

In dev mode the shell puts one function on the window. Call it from the browser console once your app has finished loading:

loomweaver.report()

It prints the regions your layout declares, the capabilities you switched off and the ids you omitted, and then warns about the things that quietly land nowhere:

  • an omit that matched nothing, with the prefix you probably meant: omitting shell.permissions hides a command or item by that id, while the settings section of that name needs setting:shell.permissions, and the bare form fails in silence
  • a settings button or menu entry pointing at a command no one registers (or one your own omit removed): the shell drops the control rather than drawing a dead one, and this says why it vanished
  • a keyboard shortcut two commands both claim, naming both and saying which one the shortcut actually runs. The chord goes to whichever registered last, so the other command’s menu entry goes on offering a shortcut that now does something else. That is the part worth seeing: nothing looks broken, it simply does the wrong thing. The comparison is on the chord the keyboard resolves, so it finds a clash between two commands that spelled the same shortcut differently

One check does not wait for the console, because it is already decidable at startup. A bar, rail or view contribution aimed at a region your layout does not declare, or declares with another anatomy, is warned about immediately, and the warning names the regions of the right type that do exist. That is the status versus status-bar mistake, which otherwise ships a product whose status bar is simply empty.

The report exists in dev only; nothing of it reaches a production build.

The pages

Each page below holds one area a distribution configures, with the provider that does it. Where a decision needs the reasoning first, the page names the concept page under concepts/ that carries it.

Distribution API is what your own code can do once the product runs.