Appearance
Chat component
@open-matrix/chat-component is the embeddable variant of Chat. It registers the same browser elements as the Chat app plus a surface/ subpath that exports four conversation primitives for embedding in other apps.
Source tree (delta from chat)
The source layout is byte-identical to chat/src/ for the shared elements (browser/, components/, elements/, services/, app/services/, contracts/, runtime/, shims/, types/, utils/). The unique addition is:
chat-component/src/surface/
├── ConversationInput.ts
├── ConversationRenderer.ts
├── ConversationSessionList.ts
├── ConversationSurfaceActor.ts
└── register-surface.tsThese four classes plus the registration file are what makes chat-component an embeddable.
Exports
package.json:
json
"exports": {
".": { "import": "./dist/runtime/index.js" },
"./browser": { "import": "./dist/browser/register-elements.js" },
"./bootstrap": { "import": "./dist/browser/bootstrap.js" },
"./surface": {
"types": "./src/surface/register-surface.ts",
"import": "./src/surface/register-surface.ts"
},
"./dist-path": { "import": "./dist-path.js" }
}The ./surface export points at the TypeScript source rather than a built JS file. That's intentional: hosts (like Director) consume this as a Vite-resolved package specifier, and Vite is allowed to compile the TS source as part of the host's build graph. That keeps class identity unified — there's only one compiled copy of ConversationSurfaceActor, not two.
How Director consumes it
projects/matrix-3/packages/director/src/components/DirectorChatSurfaceHost.ts:
ts
await import('@open-matrix/chat-component/surface');Vite resolves @open-matrix/chat-component/surface to chat-component/src/surface/register-surface.ts, compiles it, and emits a lazy chunk in Director's dist. The chunk has a side-effect that registers the four <conversation-*> custom elements.
The Vite alias trick is what makes this work. If we depended on the built dist/browser/surface/...js instead, the browser would receive bare-specifier imports it cannot resolve. By pointing the export at TS source, the host's Vite owns the compilation.
This is the M4-003 / HARVEST-005 architecture. Genuine cross-package plugin loading (HARVEST-040) is a separate, bigger effort.
Surfaces declaration
chat-component/matrix.json:
json
"surfaces": {
"conversation": {
"tag": "conversation-surface",
"module": "./dist/browser/surface/register-surface.js",
"route": "/apps/chat-component/surface/register-surface.js",
"description": "Embeddable conversation surface plugin loaded by host shells (Director, etc.) via dynamic import"
}
}This is informational metadata for future host-side import-map work. Today, the module and route fields are not consumed by any code path; the actual loading uses the package specifier @open-matrix/chat-component/surface and Vite's resolver.
What chat-component does NOT include
- The full Chat app shell. Chat-component does not register
chat-appin a way that produces a usable user-facing app at/apps/chat-component/— itswebapp.appNameischat-component, notchat. If you want the Chat app, install@open-matrix/chat. - Slash commands. Slash commands belong to
MatrixChatApp(in the chat app); the surface uses a slimmer input. - Inline-component browser.
<mx-component-browser>is registered but not surfaced from the embeddable.
When to add a new surface to chat-component
If you want a new embeddable variant (say, a "session-only" surface or an "explorer-style" surface), follow the same pattern:
- Create
src/surface/<NewSurface>Actor.tsextendingMatrixActorHtmlElement. - Add it to
register-surface.ts(or create a parallelregister-<name>.ts). - Add a new entry under
matrix.jsonsurfaces. - Add an export path under
package.jsonexportspointing at the TS source.
The host (Director, smithers, etc.) then dynamic-imports it the same way DirectorChatSurfaceHost does.
See also
- Chat package
- Surface registration
- Director: plugin surfaces (where Director's host pattern lives)
Source:
projects/matrix-3/packages/chat-component/src/surface/,chat-component/package.json,chat-component/matrix.json.