Skip to content

feat(kernel-browser-runtime): add slot-based UIOrchestrator#810

Open
sirtimid wants to merge 3 commits intomainfrom
sirtimid/ui-vat-infrastructure
Open

feat(kernel-browser-runtime): add slot-based UIOrchestrator#810
sirtimid wants to merge 3 commits intomainfrom
sirtimid/ui-vat-infrastructure

Conversation

@sirtimid
Copy link
Contributor

@sirtimid sirtimid commented Feb 5, 2026

Summary

Add UIOrchestrator for managing visible UI vat iframes in named slots, enabling multiple caplet UIs to coexist in a structured layout.

  • Add UIOrchestrator class with slot-based iframe management
  • Add makeUIVatWorker factory for VatWorker interface
  • Add <div id="root"> mounting point to vat/iframe.html
  • Export SlotName type (currently 'main', extensible)

Usage

import { UIOrchestrator } from '@metamask/kernel-browser-runtime';

// 1. Create orchestrator with named slots
const orchestrator = UIOrchestrator.make({
  slots: { main: document.getElementById('caplet-container')! },
});

// 2. Launch a UI vat into a slot
const port = await orchestrator.launch({
  id: 'my-caplet-ui',
  uri: './vat/iframe.html',
  slot: 'main',
});

// 3. The iframe now has:
//    - lockdown() running (via endoify prelude)
//    - VatSupervisor connected via the returned port
//    - <div id="root"> for mounting Preact/React UI
//    - Console forwarding to parent

// 4. Use port for CapTP communication with the vat

// 5. Lifecycle management
orchestrator.hide('my-caplet-ui');   // Hide iframe
orchestrator.show('my-caplet-ui');   // Show iframe
orchestrator.terminate('my-caplet-ui'); // Remove iframe
orchestrator.terminateAll();         // Remove all iframes

Architecture

┌────────────────────────────────────────────────────────────────┐
│  Main UI Vat (first-party)                                     │
│  - UIVatController uses UIOrchestrator                         │
│  - Decides which caplet UIs go in which slots                  │
│  - Makes CapTP presences appear in iframes                     │
└────────────────────────────────────────────────────────────────┘
        │
        │ orchestrator.launch({ id, uri, slot: 'main' })
        ▼
┌───────────────┐  ┌───────────────┐  ┌───────────────┐
│ Caplet A UI   │  │ Caplet B UI   │  │ Caplet C UI   │
│ (vat iframe)  │  │ (vat iframe)  │  │ (vat iframe)  │
│ VatSupervisor │  │ VatSupervisor │  │ VatSupervisor │
│ + Preact UI   │  │ + Preact UI   │  │ + Preact UI   │
└───────────────┘  └───────────────┘  └───────────────┘

Test plan

  • Unit tests for UIOrchestrator (slot management, lifecycle, etc.)
  • Build passes
  • Lint passes

🤖 Generated with Claude Code


Note

Medium Risk
Adds new iframe creation and cross-window postMessage/MessageChannel plumbing, which can impact UI isolation and runtime stability if misused, but is largely additive with test coverage.

Overview
Adds a new UIOrchestrator API to launch and manage visible, sandboxed UI vat iframes in named slots, including lifecycle controls (launch, show/hide, terminate, terminateAll) and accessors for ports/iframes.

Introduces makeUIVatWorker to expose UI vats through the existing VatWorker interface, exports the new UI surface from the package index, and updates the vat iframe HTML to include a #root mount point; adds comprehensive unit tests for orchestrator behavior and ignores .playwright-mcp/ artifacts.

Written by Cursor Bugbot for commit eec21e3. This will update automatically on new commits. Configure here.

…ed orchestration

Add UIOrchestrator for managing visible UI vat iframes in named slots,
enabling multiple caplet UIs to coexist in a structured layout.

- Add UIOrchestrator class with slot-based iframe management
- Add makeUIVatWorker factory for VatWorker interface
- Add <div id="root"> mounting point to vat/iframe.html
- Export SlotName type (currently 'main', extensible)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sirtimid sirtimid requested a review from a team as a code owner February 5, 2026 16:43
@sirtimid sirtimid changed the title feat(kernel-browser-runtime): add UI vat infrastructure with slot-based orchestration feat(kernel-browser-runtime): add slot-based UIOrchestrator Feb 5, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.14%
🟰 ±0%
6171 / 7897
🔵 Statements 78.1%
🟰 ±0%
6269 / 8026
🔵 Functions 76.53%
⬇️ -0.04%
1579 / 2063
🔵 Branches 78.21%
⬇️ -0.17%
2244 / 2869
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/kernel-browser-runtime/src/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-browser-runtime/src/ui/UIOrchestrator.ts 85.26% 80.76% 83.33% 85.26% 156-158, 392-414, 436, 441
packages/kernel-browser-runtime/src/ui/index.ts 100% 100% 100% 100%
packages/kernel-browser-runtime/src/ui/makeUIVatWorker.ts 10% 0% 0% 10% 68-91
Generated in workflow #3570 for commit eec21e3 by the Vitest Coverage Report Action

- Create logger once at construction time in makeUIVatWorker instead of
  per method call
- Track in-progress launches to prevent concurrent launches with same ID
  from orphaning iframes
- Clean up iframe from DOM when connection establishment fails

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
title?: string,
visible = true,
): HTMLIFrameElement {
const iframe = document.createElement('iframe');
Copy link
Contributor Author

@sirtimid sirtimid Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use createWindow from @metamask/snaps-utils? But UIOrchestrator needs the iframe element itself for lifecycle management

…ion error handling

- Save original document.createElement before mocking to prevent infinite
  recursion when creating non-iframe elements in tests
- Move iframe creation inside try block so ID is removed from
  launchesInProgress if createIframe throws

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

for (const id of this.#uiVats.keys()) {
this.terminate(id);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map iteration during modification inconsistent with codebase pattern

Low Severity

The terminateAll method iterates directly over this.#uiVats.keys() while calling terminate() which deletes entries from the Map. While JavaScript handles this correctly for deletions, it's inconsistent with the established pattern in PlatformServicesServer.ts which uses Array.from(this.#vatWorkers.keys()) to snapshot the keys before iteration. Converting to an array first is safer and more maintainable.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant