Skip to content

Architecture

LoomWeaver is a domain-agnostic plugin & UI platform — the loom on which products weave. This page is the mental model you need before building on it. (The live demo is a product built exactly this way — currently being rebuilt, so it is thin at the moment.)

┌───────────────────────────────────────────────┐
│ DISTRIBUTION — your deployable product │ branding · layout · grants (~1 file)
│ │
│ Weaver A Weaver B … │ your domain UI (plugins)
│ └─────────── ctx ──┘ │ one uniform contract
│ │
│ LoomWeaver platform (@loom/shell) │ chrome · broker · routing
│ │ — zero domain logic
└───────────────────────┬───────────────────────┘
│ frontend ports (settings · working state · auth)
your own backend — any stack, or none

The one rule: the core has zero domain logic

The model follows VS Code / Backstage / Eclipse Theia: a thin core that can only do plugins. The “actual app” — whatever product you build on the platform — is a first-party plugin bundle, mechanically indistinguishable from a third-party one. Domain concepts (documents, trees, tickets) live in a plugin, never in the platform. The explicit anti-pattern is Obsidian, whose core grew to “do too much.”

Three roles, and nothing blurs them:

  • Platform (LoomWeaver): renders neutral chrome, holds contributions, brokers capabilities, routes RPC. Knows nothing about any domain.
  • Weaver (a plugin): contributes views, commands and content. All domain logic lives here.
  • Distribution (a product): a thin composition that picks a layout, loads weavers, and brands itself. This is what you deploy.

Product = distribution

A product is a distribution of LoomWeaver, the way VSCodium is a distribution of VS Code: it consumes the platform packages and composes them — it never forks the core.

  • Frontend: a thin Angular app composes @loom/shell (the host chrome) + one or more weavers, declares a layout, grants capabilities, and sets its branding. @loom/shell and the weaver both come from packages; the distribution is ~1 file of wiring. See Building a distribution.
  • Backend (optional, product-owned): the platform ships no server. It defines frontend ports with local/anonymous defaults (the settings store, the working-state store and the auth source — provideSettingsStore, provideWorkingStateStore, provideAuthSource); a product implements them against its own backend (any stack). The UI runs standalone with no backend. See Backend integration.

You never need this repository to build a distribution — the published packages plus these docs are enough. (Building a real product from the outside is exactly how we validate that.)

The plugin contract: one uniform ctx

A weaver is an object with a manifest and an activate(ctx) method. On activation it receives a uniform ctx (a PluginContext) and contributes through it — the same surface for every plugin, trusted or sandboxed:

src/lib/plugin/notes.plugin.ts
import { Plugin } from '@loom/plugin-sdk';
export const myWeaver: Plugin = {
manifest: { id: 'my', name: 'My Weaver', capabilities: ['contributions', 'ui', 'host'] },
activate(ctx) {
ctx.registerSurface(/* … */);
ctx.registerCommand(/* … */);
// ctx.ui.confirm(...), ctx.host.version(), …
},
};

There is no privileged host API. A domain capability like acme.search is provided by the product’s own weaver and consumed by others through the broker — the same path a third-party plugin uses. Everything a plugin may import is in @loom/plugin-sdk; nothing else is public API.

Capabilities are default-deny

A plugin declares the capabilities it needs in its manifest; the distribution grants them (provideCapabilityGrants). A declaration alone grants nothing — a plugin that was not granted a capability gets a loud CapabilityError, not a silent no-op. The capabilities are coarse on purpose (contributions, ui, host, navigation, session, theme) and can split into finer ones later without changing the model. Wiring and examples: building a distribution → capabilities.

Auth-aware access gating

LoomWeaver owns no authentication — login, session, tokens and the identity provider live in the product’s own stack. The platform only reacts to a session snapshot the distribution supplies via provideAuthSource (a reactive AuthSnapshot signal; roles/claims are opaque strings). On top of that, a contribution declares an access requirement, and the host reacts to the session by login state and roles:

  • Chrome items (rail, bar, views, view actions) are hidden or disabled.
  • Commands are blocked at the one execute seam — keybindings and the palette included.
  • Content routes show a neutral “sign-in required” placeholder in place (or redirect via provideUnauthorizedRedirect), and are offered in the New-Tab picker — and mountable for split/drag hosting — only once the session qualifies.

A plugin can also read the session imperatively through ctx.session (gated by the session capability), and the host push-adapts it into a sandboxed surface so an iframe plugin self-gates too. This is orthogonal to capabilities (what a plugin may do vs. what a user may see). Client-side gating is presentation, not security — the real boundary is server-side. The complete matrix of every gated surface is reference → access gating; the wiring, login UI and redirect with full examples are building a distribution → auth integration.

The three RPC boundaries (never conflated)

A plugin only ever sees the uniform ctx; a broker routes each call to the right boundary:

  1. Plugin ↔ Core — in-browser postMessage (Worker/iframe when sandboxed). This is the ctx proxy the plugin holds.
  2. Core ↔ TreadleMCP to a small user-installed companion agent for system-near local work (files, shell, OCR) that a browser cannot do. Runs on the user’s machine, under the user’s authority; capability-gated. Treadle is not built yet — there is no integration doc to follow; this boundary describes the design.
  3. Core ↔ product server — the product’s own HTTP API (its backend behind the settings-store / auth-source ports, or a weaver’s domain API). LoomWeaver ships no server of its own here.

The server/security seam is the product’s

LoomWeaver does not reinvent tenant identity, auth/session, secret storage, key material or CQRS — those live in the product’s own backend, whatever stack that is. Secrets are per-tenant and injected server-side; they never reach the browser. The platform ships no server for this — it defines the frontend ports and the default-deny broker, and the product supplies the implementation.

What’s built today

The platform is built and published as six versioned npm packages on one shared version line: @loom/shell (the host chrome), @loom/plugin-sdk (the contract), @loom/sandbox-kit (assets for sandboxed plugins) and the scaffolding trio @loom/cli, @loom/devkit and @loom/mcp. There is no LoomWeaver server package. The in-repo dogfood is the testbed weaver, a distribution built from source that exercises every contract. The live demo is a separate product that installs the published packages instead, which is how real products build: against the registry, in their own repos, with their own backend.


Next: Getting started — scaffold a running product in ~5 minutes, or set it up by hand to see every seam.