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, though it is being rebuilt and is thin at the moment.
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 — one file name, one path — it is called
out. See also LoomWeaver and Nx.
Package availability: the @loom/* packages are not on the public npm registry yet — the first
public release publishes them. Until then they come from LoomWeaver’s own package feed; if you are
building against a pre-release, configure that registry for the @loom scope in an .npmrc next
to your package.json and authenticate as your feed requires. Everything below is otherwise
identical.
1 · An application to put it in
Terminal window
ngnewmy-studio--style=css--ssr=false
cdmy-studio
Already 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 — the Nx Angular template does — see SSR;
it is one line, not a blocker.
These 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: Angular packages
peer-depend on each other by exact version, so the one Angular package your app does not already
have is the one npm gets to choose, and it picks the newest, which demands a newer @angular/core
than your workspace pins.
Note that it reads the version from node_modules, not from package.json. What stands in
package.json is a range — ^22.1.0 — and its lower bound is not what npm installed: a fresh
ng new resolves that range to the newest matching patch. Pinning to the bound therefore asks for
an older service worker than the core you have, which is the same ERESOLVE, arrived at from the
other side.
3 · Scaffold the distribution
A distribution is your product: the composition root that assembles the platform into something
shippable.
public/logo.svg placeholder mark, so the top bar has something to show
public/manifest.webmanifest
LOOMWEAVER.md what was written and what to wire next
--force is what lets it replace the six of those that a generated app already has — all of them
bootstrap wiring ng new just produced. Nothing you wrote is at risk: the scaffold keeps its own
notes in LOOMWEAVER.md precisely so it never touches your README.md. Run it without --force
first if you want the list; the CLI names each file it would replace and writes nothing.
Three files from ng new are now unreferenced: src/app/app.routes.ts (the shell owns content
routing), src/app/app.css, and src/app/app.spec.ts — that last one now fails, because App
pulls the whole shell into a bare TestBed. Delete it; the generated app.config.spec.ts is the
replacement starting point, and it tests something worth testing (that the layout still declares the
region ids your contributions target) without mounting anything.
The logo the scaffold drops 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; logoUrl
resolves against your served root. That same file is the app icon too — the browser tab reads it and
the manifest names it. One gap is left on purpose, because a scaffold writes text and cannot invent
your artwork: Chromium offers installation only once the manifest names a 192 and a 512 raster
icon. Until you add those the app runs and caches offline, it just is not offered for installation;
LOOMWEAVER.md says exactly what to drop in.
4 · Three build settings
Tailwind needs its PostCSS plugin, so create this next to package.json:
.postcssrc.json
{ "plugins": { "@tailwindcss/postcss": {} } }
Don’t want Tailwind? You don’t need it. Re-run step 3 with --styles precompiled and skip
this file and the Tailwind packages from step 2 entirely — src/styles.css becomes one line:
src/styles.css
@import'@loom/shell/styles/shell.css';
That is the same stylesheet pre-compiled by us — tokens, the .lw-* class contracts and every
utility the shell’s own templates use, 67 KB minified. You give up writing Tailwind utilities in
your own templates; the --lw-* tokens stay available to any CSS you write. Themed with
Bootstrap? npx @loom/cli theme --name acme --preset bootstrap writes the token mapping too. See
bringing your own CSS framework — the cascade
layer it describes is not optional.
The shell loads its own UI strings at runtime, so the build has to ship them. Add the glob to your
build target’s assets — angular.json under projects.<name>.architect.build.options with the
Angular CLI, apps/<name>/project.json under targets.build.options in Nx. The path is the same
either way, because it is resolved from the workspace root:
serviceWorker emits the worker that provideShell() registers for you. inlineCritical: false is
not optional here: Angular’s critical-CSS pass loads the stylesheet with an inline onload
handler, which the strict script-src 'self' in the generated index.html blocks — the app then
renders completely unstyled, and only in production builds. (Prefer to ship no worker at all? Pass
provideShell({ serviceWorker: false }) and drop ngsw-config.json.)
5 · Scaffold a weaver
A weaver is a plugin — where all your own UI and logic live.
Write 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.
6 · Wire the weaver in
Three providers in src/app/app.config.ts, inside the existing providers array:
src/app/app.config.ts
import {
providePlugins,
provideCapabilityGrants,
provideTranslationNamespaces,
} from'@loom/shell';
import { notesPlugin } from'../notes/src'; // Nx: import through the workspace alias instead
Note the spread: providePlugins is variadic and returns an array. And grant exactly what the
weaver’s manifest declares — the broker is default-deny, so an ungranted plugin throws
CapabilityError rather than quietly doing less.
Its translations need serving too, so add one more assets glob next to the one from step 4:
You get the branded chrome: a top bar with your name, logo and the theme and language controls, an
activity 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.
The status bar along the bottom shows the running version, which the shell contributes itself.
Navigating there opens a tab for it, so the pane draws a tab strip above your surface — that is the
rule for every routable surface; only one that declares chromeless: true fills the area without a
strip. See 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.
mod+shift+n fires the command the scaffold registered, which raises a toast — a placeholder action
on a real shortcut, there to be replaced.
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.
Your first production build warns bundle initial exceeded maximum budget against Angular’s 500 kB
default. The shell is a whole application chrome, so raise the budgets in your build target —
maximumError sits at 1 MB and a product of any size will reach it.
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.