This is a guide, not the contract. What the platform guarantees is specified under
openspec/specs/. For this page:scaffolding·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.
Scaffold a running product in about five minutes: the LoomWeaver chrome, branded, with one plugin of your own already contributing to it. Every command below was run against a fresh Angular app to produce exactly what the last step shows. The live demo runs the same shell, built the same way.
If you would rather understand each file instead of generating it, set it up by hand. Same result, roughly fifteen minutes, and it explains what the generators write.
Prerequisites: Node 24 and an Angular 22 workspace. Both flavours work and nothing below is specific to either: the Angular CLI (
ng new) and Nx (nx g @nx/angular:application) generate the same application shape. Where they differ, in one file name or one path, it is called out. See also LoomWeaver and Nx.
ng new my-studio --style=css --ssr=false
cd my-studioAlready have an application (Angular CLI or Nx)? Skip this step and run the rest inside it.
--ssr=false because the shell is a client-rendered application chrome. If your workspace has
SSR switched on, as the Nx Angular template does, see SSR;
it is one line, not a blocker.
npm install @loomweaver/shell @loomweaver/plugin-sdk @angular/cdk @jsverse/transloco @ng-icons/heroicons \
@angular/service-worker@$(node -p "require('@angular/core/package.json').version")
npm install -D tailwindcss @tailwindcss/postcss @tailwindcss/typographyThese are runtime dependencies of your application, not dev tooling: @angular/cdk powers drag-drop
reorder and accessibility, Transloco the translations, @ng-icons/heroicons the first-party icon
set. Tailwind is build-time only.
The last argument pins @angular/service-worker, a peer dependency of the shell, to the Angular version
you already have; leave it off and the install fails with ERESOLVE.
Manual setup → Install explains why, and why the version is read from
node_modules rather than package.json.
A distribution is your product: the composition root that assembles the platform into something shippable.
npx @loomweaver/cli distribution --name my-studio --title "My Studio" --out . --forceIt writes eleven files and deletes nothing:
src/main.ts bootstraps App with appConfig (Angular's own shape)
src/app/app.config.ts ← everything your product is made of lives here
src/app/app.config.spec.ts
src/app/app.ts renders <lw-shell /> inside <app-root>
src/app/app.html
src/index.html title, CSP, manifest link
src/styles.css Tailwind + the LoomWeaver theme
ngsw-config.json
public/logo.svg placeholder mark, so the top bar has something to show
public/manifest.webmanifest
LOOMWEAVER.md what was written, and the little that is still yours
--force lets it replace the six of those that ng new just produced; all six are bootstrap wiring.
Your README.md stays yours, because the scaffold keeps its own notes in LOOMWEAVER.md. If you want
the list first, run it without --force: the CLI names each file it would replace and writes nothing.
It also wires your workspace; the run names every file it touched and every line it added.
Nothing to do here; the wiring is worth knowing about, not waiting for you.
.postcssrc.json, beside your package.json, so Tailwind runs at all. Without it the chrome renders
unstyled while the build reports success (Styles).
Your build target, in angular.json (or project.json under Nx), gains four things:
and, in the production configuration:
"serviceWorker": "ngsw-config.json",
"optimization": { "styles": { "inlineCritical": false } }Each has a reason, told where the manual setup makes the same edit. The i18n glob serves the shell's
own strings (Serve the host translations). The
frame-kit glob matters only once you host sandboxed plugins (Frame plugins).
serviceWorker and inlineCritical: false are the PWA side (PWA and delivery).
One trap belongs here: inlineCritical: false is not optional. The generated index.html ships a
strict script-src 'self' that blocks Angular's inline critical-CSS handler, and the app then renders
unstyled, only in production builds.
The scaffold only adds. A setting you had already made is left exactly as you made it, so re-running a scaffold over a workspace you have configured changes nothing.
Don't want Tailwind? Re-run this step with
--styles precompiledand skip the Tailwind packages from step 2;src/styles.cssbecomes one import of the stylesheet@loomweaver/shellships pre-compiled. Bringing your own CSS framework says what you give up and how a Bootstrap theme fits.
When a scaffold cannot do something, the run says so and says what it costs to leave undone. Scaffolding lists those cases.
A weaver is a plugin: where all your own UI and logic live.
npx @loomweaver/cli weaver --id notes --command --shortcut 'mod+shift+n' --out src/notesWrite the chord with the mod token rather than cmd or ctrl; the host binds and displays it
per platform. You get a manifest, a routable surface, a rail item, a command, both translation
bundles and a starter test, with the capabilities it needs already declared.
Also nothing to do. The weaver scaffold registered the plugin for you, in src/app/app.config.ts:
one import at the top of the file,
import { notesPlugin } from '../notes/src';and three lines in the providers array.
provideTranslationNamespaces('notes'),
provideCapabilityGrants({ notes: ['contributions', 'ui', 'navigation'] }),
...providePlugins(notesPlugin),The grants are exactly what the weaver's own manifest declares, because the broker is default-deny:
an ungranted plugin throws CapabilityError rather than quietly doing less. It also added the assets
glob that serves the weaver's translations and the @source entry that emits the utilities its
templates use.
It does this only while the composition root still presents the shape the distribution scaffold generated, since that is the shape it knows how to edit. Once you have reshaped it, the scaffold does not guess: it leaves the file untouched, prints the lines above, and says the plugin was not registered.
ng serveYou get the branded chrome: a top bar with your name, logo and the theme and language controls, a
rail on the left with your weaver's icon in it, and a collapsible sidebar on each side.
Click that icon and the app navigates to /notes, where your surface fills the content area.
That icon in the rail is the whole point: the platform drew every piece of chrome around it, and your plugin only declared what it wanted to contribute.
Navigating there opens a tab, so the pane draws a tab strip above your surface, as it does for every routable surface (The content area).
One thing is deliberately empty, and it names the next thing to build: the home route renders
nothing, because no surface claims / yet.
The status bar along the bottom shows the running version, which the shell contributes itself. To put
something of your own there, scaffold with --bar-item or copy one behaviour, many
triggers.
mod+shift+n fires the command the scaffold registered, which raises a toast: a placeholder action
on a real shortcut, there to be replaced.
Two shortcuts are the shell's own and work from this first run: mod+k opens the command
search, mod+p the search over everything you have open. The scaffold put both on screen as
badges that print their own chord, two lines in your app.config.ts and yours to move or delete;
LOOMWEAVER.md says how.
Your first production build warns bundle initial exceeded maximum budget; raise the budgets in your
build target as Manual setup → Run describes.
Two files from ng new are now unreferenced: src/app/app.routes.ts and src/app/app.css. The
shell owns content routing, and Routing shows that the router itself is
unchanged. The starter test src/app/app.spec.ts was replaced by one that boots the shell with your
composition root's providers. Beside it, app.config.spec.ts tests something worth testing without
mounting anything: that the layout still declares the region ids your contributions target.
ng test is green as generated.
The logo at public/logo.svg is the LoomWeaver mark, there so the top bar renders something from the
first run; replace it with your own square image whenever you like. It is the app icon too. Until you
add a 192 and a 512 raster icon, the browser does not offer installation: see
PWA and delivery.
Next:
- Samples: complete, copyable recipes for the things you will build next.
- Authoring a weaver: the full contract behind what you just scaffolded.
- Manual setup: the same app wired by hand, if you want to see every seam.
