Appearance
Runtime entrypoints
A package can have up to three different entry files, depending on what it ships. This page covers what each one is, who loads it, and what the contract is for what it must export.
The three entries
json
"runtime": {
"language": "typescript",
"entry": "./dist/runtime/index.js",
"browserEntry": "./dist/browser/register-elements.js",
"bootstrapEntry": "./dist/browser/bootstrap.js"
}| Entry | Loaded by | Required when |
|---|---|---|
entry | The runner (Host Service) when the package is brought up as a runtime. | Package has headless components OR is a webapp that needs entry: "dist/index.html" sentinel. |
browserEntry | The browser, via the gateway-served HTML's <script type="module">. Registers custom elements. | Package ships UI components. |
bootstrapEntry | The browser, via the same HTML, BEFORE browserEntry. Sets up transport, host element, root mount. | Package ships UI components. |
A purely-headless package (e.g., system-auth) declares only entry. A purely-UI package (e.g., director) declares entry: "dist/index.html" and no separate browser/bootstrap entries because its bootstrap is bundled into the same HTML by Vite. A hybrid (e.g., chat) declares all three.
runtime.entry — what the runner imports
The runner calls await import(packagePath + entry), then iterates manifest.components[] and looks up module[component.export] for each entry. Each export must be a MatrixActor subclass (or a class registered as a custom element).
ts
// dist/runtime/index.ts
export { ChatConversationProxy } from './chat-conversation-proxy';
export { ChatComponentLibraryProxy } from './chat-component-library-proxy';
export { ChatIdentityProxy } from './chat-identity-proxy';
// ... etc, one per components[] entryIf components[] is empty (e.g., a webapp-only package), the runner simply imports the module for any side effects and continues. The sentinel "entry": "dist/index.html" (used by director, flowpad, inference-settings) is the convention for "no JS entry" — the runner sees no JS to import, only the gateway serves the HTML.
Note: The runner does not call any factory function exported by
entry. The protocol is "import, then look up the named exports listed incomponents[]." If you need package-wide initialization, do it in module top-level code (it runs once per import) — but prefer per-actoronBootstrap().
runtime.bootstrapEntry — browser bootstrap
Loaded first by the served HTML. The bootstrap is responsible for:
- Constructing a transport using
createBrowserNatsTransport({ wsUrl: '/nats-ws', ... }). - Resolving the authority root (typically from a
/api/whoamiendpoint or a meta tag). - Constructing a
MatrixRuntime({ transport, domStrategy: new BrowserDomStrategy() }). - Inserting a
<matrix-dsl-host>element into the DOM and giving it the runtime + the root context. - Mounting the root component (
manifest.root.export) under the host.
Sketch (every package writes its own variant, but the shape is similar):
ts
// src/browser/bootstrap.ts
import { MatrixRuntime, BrowserDomStrategy, createBrowserNatsTransport } from '@open-matrix/core';
import { ChatApp } from './chat-app';
async function main() {
const whoami = await fetch('/api/whoami').then(r => r.json());
const transport = await createBrowserNatsTransport({
wsUrl: '/nats-ws',
root: whoami.authorityRoot,
jwtProvider: () => fetch('/api/nats-jwt').then(r => r.json()),
});
const runtime = new MatrixRuntime({
transport,
domStrategy: new BrowserDomStrategy(),
});
customElements.define('chat-app', ChatApp);
// ... other UI components
const host = document.querySelector('matrix-dsl-host')!;
// wire host with runtime; mount <chat-app> as root
}
main();This is illustrative — package bootstraps in the tree (chat, director, flowpad) are the canonical references. The exact API of <matrix-dsl-host> and the bootstrap sequence is in @open-matrix/core/browser/MatrixDSLHost.
runtime.browserEntry — element registration
browserEntry is the module that calls customElements.define for every UI component the package ships:
ts
// src/browser/register-elements.ts
import { ChatConversationView } from './components/chat-conversation-view';
import { ChatInput } from './components/chat-input';
import { ChatMessageList } from './components/chat-message-list';
customElements.define('chat-conversation-view', ChatConversationView);
customElements.define('chat-input', ChatInput);
customElements.define('chat-message-list', ChatMessageList);In simple packages, registration happens inline in bootstrapEntry, and browserEntry is omitted. In larger packages, splitting them keeps bootstrap focused on transport/runtime setup and keeps registration where the components live.
The HTML loads them in order:
html
<script type="module" src="./bootstrap.js"></script>
<script type="module" src="./register-elements.js"></script>Module dependency order matters: bootstrap first because it sets up the runtime and host element; then registrations so elements can find their host on construction.
Vite configuration
Most packages use Vite for the browser bundle. A typical vite.config.ts produces:
dist/browser/
├── index.html
├── bootstrap.js
├── register-elements.js
└── assets/<hashed>.jsFor Node-side runtime.entry, packages typically use plain tsc (compiling src/runtime/ to dist/runtime/). Some packages use a vite.runtime.config.ts that produces an ESM bundle for the runtime side too — useful when the runtime depends on third-party CJS that needs to be bundled.
The exact build script lives in each package's package.json. There is no single canonical layout — pick the closest existing package as a template (e.g., copy from flowpad or director for webapp-only, from chat for hybrid).
What the runner is allowed to assume
- The
entrymodule loads cleanly under Node ESM with no top-levelawaitblocking startup more than a few seconds. - Every export named in
components[]is a class (constructor function), not a factory. - The class is a valid
MatrixActorsubclass (passesinstanceofchecks at runtime). - The class has whatever static fields its declared
surface,accepts,emitsimply.
If any of these break, matrix run reports the specific failure and the supervisor refuses to start the runtime.
See also
- Package manifest — every other field around
runtime. - Component packaging — UI-side build details.
- Browser transport — what
bootstrapEntryactually wires up.
Source:
projects/matrix-3/packages/chat/matrix.json:14-26(all three entries present),projects/matrix-3/packages/director/matrix.json:5(entry: "dist/index.html"sentinel pattern).