Appearance
Component packaging
UI components ship as part of a package, alongside the headless actor that backs them. This page covers the matrix.json declarations that mark a package as having a browser surface, the dist/ layout the gateway serves, and the vertical-slice rule that says where UI code is allowed to live.
The vertical-slice rule, in one paragraph
Every UI component belongs to the package that owns the backend it talks to. Not the shell. Not matrix-web. The vertical package.
matrix-web and matrix-edge are pure shells — navbar, status bar, auth gate, surface discovery, layout. They contain ZERO domain UI code. If a component displays inference providers, it lives in the inference package; if it edits a user profile, it lives in the user package. See WORKSTREAMS/matrix-web/VERTICAL-SLICE-RULE.md for the full rule.
This page documents the mechanics of shipping a component-bearing package; the policy about who owns what UI is in that workstream doc.
matrix.json — declaring a webapp
A package that ships a UI component bundle declares a webapp block in its matrix.json. The simplest case (from projects/matrix-3/packages/director/matrix.json):
json
{
"name": "@open-matrix/director",
"version": "0.1.28",
"description": "Director — Matrix actor hierarchy explorer",
"runtime": { "language": "typescript", "entry": "dist/index.html" },
"webapp": {
"distDir": "dist",
"entry": "index.html",
"appName": "director",
"displayName": "Director",
"icon": "🧭",
"navOrder": 20,
"description": "Matrix actor hierarchy explorer",
"shells": ["platform", "edge"]
},
"components": [],
"permissions": { "fsPolicy": "none" }
}The webapp block tells Host Service / system-gateway-http that this package serves static assets:
| Field | What it does |
|---|---|
distDir | Directory (relative to package root) that the gateway serves. |
entry | The HTML entry inside distDir. The gateway serves it for the package's route. |
appName | URL-safe slug; the package is reachable at /apps/<appName>/. |
displayName | Human-readable name shown in the shell's app launcher. |
icon | Emoji or short string shown in launcher. |
navOrder | Sort order in the launcher (lower first). |
description | Tooltip / launcher description. |
shells | Which shells should list this app. "platform" = matrix-web (account shell), "edge" = matrix-edge (Device shell). |
When you run matrix run @open-matrix/director --serve, Host Service:
- Allocates an HTTP port for the runtime.
- Registers the runtime with
system.runtimes. - Adds a route through
system-gateway-httpfor/apps/director/→ that port. - Serves the package's
dist/over that route.
Hybrid packages — components + actors
Hybrid packages declare both a webapp surface and a list of components (mounted actors). The chat package is the canonical example (projects/matrix-3/packages/chat/matrix.json):
json
{
"name": "@open-matrix/chat",
"class": "hybrid",
"namespace": "chat",
"root": {
"type": "MatrixChatApp",
"export": "MatrixChatApp",
"mount": "chat",
"surface": "browser"
},
"runtime": {
"language": "typescript",
"entry": "./dist/runtime/index.js",
"browserEntry": "./dist/browser/register-elements.js",
"bootstrapEntry": "./dist/browser/bootstrap.js",
"environments": ["browser", "headless-dom", "runtime"],
"executionClassCompatibility": [
"shared_host_service", "dedicated_process",
"container", "isolate", "remote_managed_service"
]
},
"webapp": {
"distDir": "./dist/browser",
"entry": "index.html",
"appName": "chat",
"shells": ["platform", "edge"]
},
"components": [
{ "type": "ChatConversationProxy", "mount": "conversation", "surface": "headless", "autoStart": true },
{ "type": "ChatComponentLibraryProxy", "mount": "component-library", "surface": "headless", "autoStart": true }
],
"permissions": { "fsPolicy": "none", "network": true, "subprocess": false, "env": false }
}The relevant fields for a component-bearing package:
runtime.browserEntry— the JS module that callscustomElements.define('chat-*', ...)for every UI component the package ships. Browsers load this through the gateway-served HTML's<script type="module">.runtime.bootstrapEntry— the bootstrap script the gateway-served HTML imports first. Sets up theMatrixDSLHost, callscreateBrowserNatsTransport, mounts the root component.components— headless actors auto-started by the runner when this package boots as a runtime. Each one becomes a mount under the package's namespace.consumes(chat extends this) — declares cross-package contracts the package needs satisfied.
Build outputs
A typical component-bearing package builds to:
packages/chat/
├── matrix.json
├── package.json
├── src/
│ ├── runtime/ ← headless actor implementations
│ ├── browser/ ← UI components (custom elements)
│ │ ├── register-elements.ts
│ │ └── bootstrap.ts
│ └── ...
├── vite.config.ts ← gateway-served browser bundle
├── vite.runtime.config.ts ← headless runtime bundle (Node)
└── dist/
├── runtime/index.js ← runtime.entry
└── browser/
├── index.html ← webapp.entry
├── register-elements.js ← runtime.browserEntry
├── bootstrap.js ← runtime.bootstrapEntry
└── assets/The exact build setup varies. chat, director, flowpad, inference-settings, and smithers all use Vite. Some packages have a single vite.config.ts; others split into vite.config.ts (browser) + vite.runtime.config.ts (Node). When in doubt, use the closest existing package as a template.
Permissions
Every package declares a permissions block. UI-bearing packages typically need network: true (so the bootstrap can open a NATS WebSocket connection) and fsPolicy: "none" (no filesystem access from a browser). Headless backend actors inside the same package may need different permissions; the SDK does not enforce this at runtime — package supervisors and the browser sandbox do.
Shipping convention
A workshop-grade Matrix package satisfies these checks:
matrix.jsondeclares awebappblock withappName,entry,shells.matrix.jsondeclarescomponentsfor any headless actors that should auto-start.- The browser entry registers every UI component via
customElements.define. - The bootstrap entry uses
createBrowserNatsTransport({ wsUrl: '/nats-ws', ... })(same-origin WS, never absolute). - Tests live alongside the package: unit tests in
tests/, integration tests inprojects/matrix-3/tests/. - The package builds idempotently from a fresh checkout:
pnpm install && pnpm --filter <name> build.
Skeleton walkthrough — a brand-new component package
bash
# 1. Create the package directory and matrix.json
mkdir -p projects/matrix-3/packages/my-widgets/{src,dist}
cat > projects/matrix-3/packages/my-widgets/matrix.json <<'EOF'
{
"name": "@open-matrix/my-widgets",
"version": "0.1.0",
"description": "Demonstration UI widgets",
"runtime": { "language": "typescript", "entry": "dist/index.html" },
"webapp": {
"distDir": "dist",
"entry": "index.html",
"appName": "my-widgets",
"displayName": "My Widgets",
"icon": "🧩",
"navOrder": 99,
"shells": ["platform"]
},
"components": [],
"permissions": { "fsPolicy": "none", "network": true }
}
EOF
# 2. Implement at least one component, register it, build via Vite,
# install into the local Host:
pnpm --filter @open-matrix/my-widgets build
matrix run @open-matrix/my-widgets --serve --home /tmp/matrix-homeThe full template lives in the existing minimal packages (director, flowpad, inference-settings).
See also
- Package manifest — the full
matrix.jsonreference. - App surfaces — the
webappblock,routePrefix, shells. - Browser transport — the bootstrap connection.
- Vertical Slice Rule.
Source:
projects/matrix-3/packages/director/matrix.json,projects/matrix-3/packages/chat/matrix.json,projects/matrix-3/packages/flowpad/matrix.json,projects/matrix-3/packages/inference-settings/matrix.json(real shipping examples).