-
Notifications
You must be signed in to change notification settings - Fork 0
UI extensions render events, drive interactions, and contribute renderer-local regions. A UI extension may be a subscriber (reads events, renders), an interactor (answers typed request/response from core), a region contributor (customizes a target UI's sub-region), or a combination of those roles.
contractVersion: 1.0.0
Core owns the event loop. UI extensions receive events and may optionally respond to Interaction Protocol requests. Some UI extensions contribute components to another UI's renderer-local region ABI. This split lets stud-cli run with multiple viewers (TUI in a terminal, web UI in a browser, headless logger) while Interaction Protocol authority stays on the typed request/response rail. Multiple interactors may be active concurrently; the first response wins.
classDiagram
class UIContract {
kind : "UI"
contractVersion : SemVer
requiredCoreVersion : SemVerRange
configSchema : JSONSchema
loadedCardinality : unlimited
activeCardinality : unlimited
stateSlot : optional
discoveryRules : DiscoveryRules
reloadBehavior : between-turns
roles : ReadonlyArray~UIRole~
onEvent : SubscriberHandler?
onInteraction : InteractorHandler?
regions : RegionContribution[]?
}
roles is a non-empty array drawn from { 'subscriber', 'interactor', 'region' }. Multiple values may appear in the same roles array.
| Role | Reads events? | Answers Interaction Protocol? | Contributes renderer regions? | Handler / declaration required |
|---|---|---|---|---|
subscriber |
Yes | No | No | onEvent |
interactor |
No | Yes | No | onInteraction |
region |
No by default | No by default | Yes | regions[] |
An extension that wants only to answer prompts (e.g., a headless approval bridge) declares roles: ['interactor']. An extension that only renders its own surface (e.g., a dashboard) declares roles: ['subscriber']. A status-line customization for the bundled TUI declares roles: ['region'] and targets default-tui. The default TUI declares roles: ['subscriber', 'interactor'].
Load-time validation emits Validation/UIRoleHandlerMissing when a declared role has no matching handler or region declaration.
Every UI's configSchema must accept:
| Field | Meaning |
|---|---|
enabled |
Whether this UI participates in the session. |
targetUI |
Required for region contributors; the UI id whose renderer-local ABI this extension targets. |
Plus any UI-specific options (theme, pane layout, bindings). There is no priority field — every loaded UI whose roles array includes interactor is concurrently active and participates in race-to-answer fan-out (see Interaction Protocol § Multiple interactors).
A UI may expose its own renderer-local extension points for sub-regions such as a status line, transcript renderer, or input chrome. Region contributors are still kind: "UI" extensions, but the region ABI is UI-owned, not a new core authority surface.
Core only sees UI extensions through this contract:
- Event subscription still flows through
onEvent. - Authoritative answers still flow through
onInteraction. - Region contribution still flows through
roles: ['region'],targetUI, and the target UI's region declaration. - Commands still dispatch through the command bus.
- Region code does not receive direct tool-call authority, raw env access, or another extension's state slot.
The bundled Default TUI exposes Ink / React region plugins for startup, transcript, composer, statusLine, and dialogs. Another full UI may expose a different region ABI or no region ABI at all. Region plugins that target a specific UI must declare that target in their own manifest/config and degrade cleanly when the target UI is not loaded.
Renderer-local regions are allowed because they customize projection and input chrome; they must not become a hidden control plane. A region plugin that needs to perform a session action must call a command or answer an Interaction Protocol request through the owning UI's controlled callback.
| Phase | UI responsibilities |
|---|---|
init |
Validate config; allocate the terminal/window/transport. |
activate |
Subscribe to the event bus; if interactor, announce availability; if region contributor, register with the target UI's region registry. |
deactivate |
Drain pending interaction requests (error them if not answerable); unregister region contributions; unsubscribe. |
dispose |
Release the terminal/window/transport. Idempotent. |
A UI that cannot complete init fails validation with a clear diagnostic. Headless sessions may run without any UI — see Headless and Interactor.
Loaded: unlimited.
Active: unlimited for every UI role (Q-9 resolution).
| Role | Active cardinality | Notes |
|---|---|---|
subscriber |
unlimited | Every loaded subscriber receives every event. |
interactor |
unlimited | Core fans out to all active interactors; first-to-respond wins. |
region |
unlimited | Target UIs compose region contributions according to their own renderer-local ABI. |
Multiple interactors may be simultaneously active. When core issues an Interaction Protocol request:
- Core fans out the request to every active interactor concurrently.
- The first
acceptedorrejectedresponse wins. - Core immediately emits
InteractionAnsweredon the event bus, carrying thecorrelationIdand the winning response. - Interactors that have not yet responded should subscribe to
InteractionAnsweredand dismiss their own in-flight dialogs. - A response that arrives after the winner has already been recorded rejects with
Session/InteractionAlreadyAnswered.
See Cardinality and Activation and Interaction Protocol.
Optional. Typical uses:
- Saved pane sizes, selected theme, scroll positions.
- Last-seen event watermark so a reattach doesn't re-render the full history.
- Region-plugin preferences, such as selected status-line widgets or collapsed transcript sections.
Some of this may also live in config. Config for defaults; state slot for runtime user choices that persist across resumes. See Extension State.
UIs live in a ui/ folder under each configuration scope layer:
| Layer | Location |
|---|---|
| Bundled | Ships with the package. |
| Global | ~/.stud/ui/… |
| Project | <cwd>/.stud/ui/… |
UIs are an unordered category. Event delivery to subscribers is deterministic but the order across subscribers is not a concern of the contract — each subscriber sees the same event stream.
reloadBehavior: between-turns. Reloading a UI mid-turn would drop in-flight rendering and confuse the user. Reloading the interactor while an Interaction Protocol request is in flight is forbidden — core waits for the request to complete, then applies the reload.
A UI reads and writes only through the Host API:
-
host.events— subscriber role consumes the event stream. - Interaction Protocol — interactor role handles typed requests (Ask, Approve, Select, Auth.DeviceCode, Auth.Password, Confirm).
-
host.session.stateSlot(extId)— own slot for user preferences.
A UI never:
- Calls a tool directly.
- Writes to another extension's state slot.
- Reads secret values (the interactor is handed the question, not the env slot content).
Events are projection only. A UI that uses event timing to drive authoritative decisions (cancel a tool call based on an event arrival) is non-conformant. Authority flows through the Interaction Protocol, state machines, and commands. See Event Bus.
-
The interactor never receives raw secrets. An
Auth.Passwordrequest describes the purpose; the answer is captured and handed to the requesting extension without the UI logging it. - UI code runs in-process in v1 (see Extension Isolation). A third-party UI has the same access surface as any other extension and is gated by the same Project Trust prompt.
- Autocompletion in the input field is a disclosure channel. A UI that autocompletes from the env provider's known keys can accidentally leak names to screenshots or paste buffers. UIs should require an explicit action to disclose env values into the input. See LLM Context Isolation.
- Interactor departure matters. A UI that disappears mid-session (crash, disconnection) must release any in-flight interaction state cleanly. Other active interactors continue to receive fan-out; if none remain, headless behavior applies.
- Interaction Protocol
- Event Bus
- Headless and Interactor
- Tool Approvals
- Default TUI reference: Default-TUI
- UI contract as documented above.
roles: ReadonlyArray<UIRole>declares which ofsubscriber/interactor/regionthe extension performs. - Both cardinality axes are
unlimited— multiple subscribers, interactors, and region contributors may be active concurrently. - First-interactor-wins fan-out for Interaction Protocol requests;
InteractionAnsweredbroadcast; late responses reject withSession/InteractionAlreadyAnswered. - Load-time
Validation/UIRoleHandlerMissingwhen a declared role has no matching handler or region declaration. - No
validationSeverityfield — failed UIs are disabled. The "interactor required" check is enforced by the Headless / Interactor decision logic.
- Execution Model
- Message Loop
- Concurrency and Cancellation
- Error Model
- Event and Command Ordering
- Event Bus
- Command Model
- Interaction Protocol
- Hook Taxonomy
- Host API
- Extension Lifecycle
- Env Provider
- Prompt Registry
- Resource Registry
- Session Lifecycle
- Session Manifest
- Persistence and Recovery
- Stage Executions
- Subagent Sessions
- Contract Pattern
- Versioning and Compatibility
- Deprecation Policy
- Capability Negotiation
- Dependency Resolution
- Validation Pipeline
- Cardinality and Activation
- Extension State
- Conformance and Testing
- Providers
- Provider Params
- Tools
- Hooks
- UI
- Loggers
- State Machines
- SM Stage Lifecycle
- Stage Definitions
- Commands
- Session Store
- Context Providers
- Settings Shape
- Trust Model
- Project Trust
- Extension Isolation
- Extension Integrity
- LLM Context Isolation
- Secrets Hygiene
- Security Modes
- Tool Approvals
- MCP Trust
- Sandboxing
- Configuration Scopes
- Project Root
- Extension Discovery
- Extension Installation
- Extension Reloading
- Headless and Interactor
- Determinism and Ordering
- Launch Arguments
- Network Policy
- Platform Integration
Tools
UI
Session Stores
Loggers
Providers
Hooks
Context Providers
Commands
- First Run
- Default Chat
- Tool Call Cycle
- Hook Interception
- Guard Deny Reproposal
- State Machine Workflow
- SM Stage Retry
- Hot Model Switch
- Capability Mismatch Switch
- Session Resume
- Session Resume Drift
- Approval and Auth
- Interaction Timeout
- Headless Run
- Parallel Tool Approvals
- Subagent Delegation
- Scope Layering
- Project First-Run Trust
- Reload Mid-Turn
- Compaction Warning
- MCP Remote Tool Call
- MCP Prompt Consume
- MCP Resource Bind
- MCP Reconnect