Skip to content

Host UI and Surfaces

James Daley edited this page Jun 18, 2026 · 3 revisions

Host UI and Surfaces

Forsetti does not let modules mutate host UI directly. UI and app modules publish structured UIContributions, the runtime validates those contributions against capability and manifest declarations, and the host decides how approved contribution data is rendered.

Surface Composition

flowchart LR
    Active["Active UI/app module"] --> Contributions["UIContributions"]
    Contributions --> Validate["ModuleManager validation"]
    Validate --> Sanitize["Runtime sanitization"]
    Sanitize --> Surface["UISurfaceManager"]
    Surface --> Toolbar["toolbarItems"]
    Surface --> Slots["viewInjectionsBySlot"]
    Surface --> Overlay["overlaySchema"]
    Surface --> Theme["themeMask"]
    Toolbar --> Host["Host view"]
    Slots --> Host
    Overlay --> Router["ForsettiHostOverlayRouter"]
    Theme --> Host
Loading

The default runtime model applies contributions from the active UI/app module only. Switching UI modules deactivates the previous UI module and clears its contributions before publishing the new surface state.

ModuleManager currently sanitizes themeMask to nil before applying contributions to UISurfaceManager. Theme masks can still be declared and validated through capability and manifest requirements, but default shell theming remains host-controlled.

Contribution Types

Type Structure Required Capability Host Responsibility
Toolbar item ToolbarItemDescriptor toolbar_items Render title/icon and invoke ToolbarAction.
View injection ViewInjectionDescriptor view_injection Resolve viewID through ForsettiViewInjectionRegistry and place it in a known slot.
Overlay schema OverlaySchema routing_overlay Resolve pointer and route IDs through OverlayRouting.
Theme mask ThemeMask ui_theme_mask Current default runtime validates then strips before publication; custom hosts may add explicit support later.

UI Requirement Declarations

flowchart TD
    Manifest["runtimeRequirements.ui"] --> Theme["themeIDs"]
    Manifest --> Views["viewIDs"]
    Manifest --> Slots["slotIDs"]
    Manifest --> Toolbar["toolbarItemIDs"]
    Manifest --> Routes["routeIDs"]
    Manifest --> Pointers["pointerIDs"]
    Contributions["UIContributions"] --> Check["Validate every contributed ID"]
    Theme --> Check
    Views --> Check
    Slots --> Check
    Toolbar --> Check
    Routes --> Check
    Pointers --> Check
    Check -- pass --> Publish["Publish sanitized surface state"]
    Check -- fail --> Block["unsatisfiedRuntimeRequirement"]
Loading

Template 1.1 modules with UI contributions must declare the IDs they will publish. This lets reviewers and hosts understand a module's UI footprint before activation.

View Injection Workflow

sequenceDiagram
    participant Module as UI Module
    participant Manager as ModuleManager
    participant Surface as UISurfaceManager
    participant Registry as ViewInjectionRegistry
    participant Host as Host View

    Module-->>Manager: uiContributions.viewInjections
    Manager->>Manager: capability and declaration validation
    Manager->>Surface: apply(moduleID, sanitized contributions)
    Surface-->>Host: viewInjectionsBySlot
    Host->>Registry: resolve(viewID)
    Registry-->>Host: AnyView
    Host-->>Host: render slot by priority
Loading

View injections are sorted by slot, then by descending priority inside each slot. The host must register every resolvable viewID; a valid contribution can still render blank if the host does not provide the matching view builder.

Toolbar Actions

Action Runtime Behavior
navigate(pointerID:) Calls runtime.openPointer(pointerID) through the configured router.
openOverlay(routeID:) Calls runtime.openRoute(routeID) through the configured router.
publishEvent(type:payload:) Publishes a ForsettiEvent on the runtime event bus.
flowchart TD
    Click["User clicks toolbar item"] --> Action{"ToolbarAction"}
    Action -- navigate --> Pointer["openPointer(pointerID)"]
    Action -- openOverlay --> Route["openRoute(routeID)"]
    Action -- publishEvent --> Event["eventBus.publish"]
    Pointer --> Router["OverlayRouting"]
    Route --> Router
    Event --> Subscribers["Framework and module subscribers"]
Loading

Toolbar action IDs must be declared in runtimeRequirements.ui when they reference routes or pointers.

Overlay Routing

ForsettiHostOverlayRouter resolves active overlay pointers and routes from UISurfaceManager.overlaySchema.

flowchart TD
    Request["Pointer or route request"] --> Schema{"Active overlay schema?"}
    Schema -- no --> NoSchema["schemaUnavailable"]
    Schema -- yes --> Found{"Reference found?"}
    Found -- no --> Missing["missingPointer or missingRoute"]
    Found -- yes --> Destination{"Destination type"}
    Destination -- base --> Base{"Known base destination?"}
    Destination -- moduleOverlay --> Slot{"Known slot?"}
    Base -- yes --> BaseOK["pointerResolved or routeResolved"]
    Base -- no --> BadBase["invalidBaseDestination"]
    Slot -- yes --> SlotOK["routeResolved"]
    Slot -- no --> BadSlot["invalidSlot"]
Loading

Overlay schemas should reference stable host catalog IDs. The module declares route and pointer IDs; the host decides which base destinations and module overlay slots are allowed.

Host Shell Patterns

Context Recommended Host Behavior
Production Pattern A Hide shell controls and render the app module's primary view directly after successful boot and activation.
Production Pattern B Render the active UI/app module and keep service module controls out of end-user UI.
Developer Pattern C Show module lists, lock state, compatibility details, selected module, toolbar feedback, and errors.
Dashboard Pattern D Show intentional navigation among app modules and make the dashboard a product surface.

Surface Safety Rules

  • Treat UISurfaceManager state as presentation input, not domain truth.
  • Register every injectable viewID with the host's ForsettiViewInjectionRegistry.
  • Keep slot names stable and documented in the host app.
  • Keep base destination catalogs and module overlay slot catalogs host-owned.
  • Avoid embedding host-only assumptions inside reusable modules.
  • Prefer toolbar actions and overlay routes over direct host references.
  • Declare toolbar_items, view_injection, routing_overlay, and ui_theme_mask capabilities before contributing those surfaces.
  • Declare every contributed UI ID under runtimeRequirements.ui for manifest template 1.1.
  • Do not rely on themeMask publication in the default runtime path; apply host shell theming through host-owned code.

Clone this wiki locally