-
Notifications
You must be signed in to change notification settings - Fork 0
Host UI and Surfaces
James Daley edited this page Apr 25, 2026
·
3 revisions
Forsetti does not let modules mutate host UI directly. UI modules publish structured contributions, and the host decides how those contributions are rendered.
flowchart LR
ModuleA["UI module A"] --> ContributionsA["UIContributions"]
ModuleB["UI module B"] --> ContributionsB["UIContributions"]
ContributionsA --> Surface["UISurfaceManager"]
ContributionsB --> Surface
Surface --> Toolbar["toolbarItems"]
Surface --> Slots["viewInjectionsBySlot"]
Surface --> Overlay["overlaySchema"]
Surface --> Theme["themeMask"]
Toolbar --> Host["Host view"]
Slots --> Host
Overlay --> Host
Theme --> Host
ModuleManager sanitizes UI contributions before applying them. themeMask is currently stripped because framework shell theming is reserved.
| Type | Structure | Host Responsibility |
|---|---|---|
| Toolbar item | ToolbarItemDescriptor |
Render title/icon and invoke ToolbarAction. |
| View injection | ViewInjectionDescriptor |
Resolve viewID through ForsettiViewInjectionRegistry and place it in a known slot. |
| Overlay schema | OverlaySchema |
Resolve pointer and route IDs through OverlayRouting. |
| Theme mask | ThemeMask |
Reserved by current runtime sanitization. |
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->>Surface: apply(moduleID, contributions)
Surface-->>Host: viewInjectionsBySlot
Host->>Registry: resolve(viewID)
Registry-->>Host: AnyView
Host-->>Host: render slot by priority
View injections are sorted by slot, then by descending priority inside each slot.
| 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/module subscribers"]
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"]
| Context | Recommended Host Behavior |
|---|---|
| Production Pattern A | Hide shell controls and render the app module's primary view directly. |
| Production Pattern B | Render the selected 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. |
- Treat
UISurfaceManagerstate as presentation input, not domain truth. - Register every injectable
viewIDwith the host'sForsettiViewInjectionRegistry. - Keep slot names stable and documented in the host app.
- Avoid embedding host-only assumptions inside reusable modules.
- Prefer toolbar actions and overlay routes over direct host references.