Status: engineering-layer positioning on frontend technology and deployment.
Audience: distribution authors choosing frontend stacks, integrators building on top of an evo distribution, anyone asking "what should the UI be?".
Related: BOUNDARY.md (the framework/distribution split; frontend is distribution-side), CLIENT_API.md (the protocol every frontend ultimately speaks), PLUGIN_AUTHORING.md (bridge plugins are plugins).
The framework has no opinion about frontend. This document is about making that explicit, cataloguing the deployment and technology shapes that distributions can reach for, and describing the bridge plugin pattern that turns the local Unix-socket protocol into any remote interface a frontend might want. The goal is technological versatility: the same steward should be able to back a Raspberry Pi kiosk, a web UI served locally, a mobile app over the network, a Home Assistant integration, or an automotive HMI - all at the same time, on the same device, if a distribution chooses.
A consumer of the steward is anything that opens its client socket, speaks the protocol in CLIENT_API.md, and uses the responses. A frontend is the specific kind of consumer that presents the device to a human. The framework treats frontends as consumers like any other - the same client socket, the same four operations, no privileged interface.
This document is organised around three orthogonal questions every distribution answers:
- Where does the frontend execute? (On the device, off the device, in a browser, in a terminal, on a phone.)
- What technology does it use? (Web framework, native toolkit, terminal UI library, integration platform.)
- How does it reach the steward? (Directly over the Unix socket, or through a bridge plugin exposing a remote interface.)
The answers are independent. A distribution may pick any combination of answers, and may even ship multiple combinations simultaneously - a local kiosk UI, a remote web UI via an HTTP bridge, and a Home Assistant integration via an MQTT bridge, all at once.
Three consequences of this flow from BOUNDARY.md and are worth stating plainly here:
- The steward has no frontend-specific code. No special-cased requests, no preferential path, no UI-shaped data types. The client socket is the whole interface.
- Changing frontend is free. A distribution can replace its entire frontend without touching any plugin or the steward. The only contract is the client socket.
- Multiple frontends compose without coordination. Frontends do not know about each other. They all address the steward, see consistent state, and react to the same happenings.
The framework's non-opinionated posture is deliberate: appliances span from headless to voice-controlled to automotive to Home Assistant rooms, and a one-framework-fits-all choice would exclude most of those deployments.
The combinations a distribution will land on vary widely. Quick examples, all valid:
| Deployment | Technology | Integration |
|---|---|---|
| On-device kiosk browser | React | Direct Unix socket via WebSocket bridge |
| Local HTTP server | Angular | Direct Unix socket via HTTP bridge |
| Mobile app | Flutter | HTTP bridge over LAN |
| Desktop companion | Tauri | WebSocket bridge over LAN |
| Terminal | Textual (Python) | Direct Unix socket |
| Home Assistant add-on | HA custom component (Python) | MQTT bridge |
| Automotive HMI | Qt/QML (C++) | Direct Unix socket on the head unit |
| Voice assistant | Rhasspy / Willow / Alexa skill | HTTP bridge |
| None (headless) | - | - |
None of these is "the right answer". Each is right for its distribution's product.
Where the frontend runs. The options below are not exhaustive; they are the shapes we have seen demand for.
A browser in full-screen mode, displayed on a connected screen (HDMI, DSI, touchscreen). Renders a web UI served from a local HTTP server also on the device. The kiosk plugin on the steward's side manages the browser process (see the kiosk rack's role in CONCEPT.md section 6).
Typical stack: Chromium in kiosk mode + a local HTTP server (part of the distribution) + a web UI in any framework.
The same local HTTP server, but the user opens it from another device on the network (phone, tablet, laptop). Network access is via the distribution's choice: some expose the HTTP port on the LAN directly; others put it behind a bridge plugin with authentication and TLS.
A native GUI application running directly on the device (no browser). Common in automotive, industrial, and embedded scenarios where a full browser is not wanted.
Typical stack: Qt/QML, GTK, Flutter, or an OEM's proprietary HMI framework.
A native or web-technology-based desktop app running on a separate computer (laptop, desktop), reaching the device over the network. Used for configuration tools, power-user interfaces, multi-device management.
Typical stack: Electron, Tauri, Qt, native Swift/Cocoa, WPF/.NET.
A native or cross-platform mobile app on iOS or Android, reaching the device over the network.
Typical stack: Flutter, React Native, Swift, Kotlin, native Jetpack Compose / SwiftUI.
A text-mode UI, either full-screen (TUI) or command-oriented (CLI). Appropriate for headless devices, SSH-administered appliances, and as a diagnostic companion alongside a GUI.
Typical stack: Textual (Python), Ratatui (Rust), Bubble Tea (Go), Ink (Node.js), or plain shell scripts for one-shot commands.
Microphone and speaker (voice) or camera and sensor (gesture) interfaces that translate human input into requests. Usually a separate process that listens for wake words, performs speech-to-text, maps intents to op = "request" calls, and uses text-to-speech for feedback.
Typical stack: Rhasspy, Willow, or a commercial assistant (Alexa, Google Assistant) via a skill or action.
The device appears inside an existing home automation system as an entity, a service, a thing, or a device. Users interact via the home automation system's existing UI rather than any UI the distribution ships itself.
| Target | Integration shape |
|---|---|
| Home Assistant | A custom component (Python) or a MQTT device following the HA MQTT discovery protocol. |
| openHAB | A binding (Java/Kotlin) or a MQTT thing. |
| Node-RED | Custom nodes (JavaScript) or MQTT/HTTP flows. |
| Apple Home / HomeKit | Via the HomeKit Accessory Protocol, usually through a bridge. |
| Google Home / Matter | Via Matter's device types and Google Home integrations. |
| Amazon Alexa | Via Smart Home Skills. |
These are not frontends in the narrow sense - they are bridges to other systems whose frontends render the device. A distribution that cares about broad home-automation compatibility typically ships an MQTT bridge plugin and lets each target system discover the device that way.
Automotive head units, industrial control panels, medical displays, smart appliances with their own OEM UI frameworks. The OEM already has a UI technology commitment; the steward is the backend service they integrate with.
The technology is whatever the OEM ships: Qt in Automotive Grade Linux; proprietary C++ frameworks in some automakers; AUTOSAR-compliant HMIs in industrial; native SDK-based UIs in appliances. The integration shape is whatever that ecosystem supports - direct Unix socket if the HMI is on the same device, a bridge plugin exposing HTTP or a proprietary protocol if not.
No UI at all. The device is controlled entirely by external systems (other appliances, automation, voice), accessed via bridge plugins, or observed through monitoring agents.
Valid and common. A distribution is not required to ship a frontend.
Multiple of the above simultaneously. A single device might run:
- A kiosk browser on its built-in display.
- An HTTP bridge exposing a REST interface for a mobile app.
- An MQTT bridge for Home Assistant discovery.
- A terminal CLI for diagnostics over SSH.
Each is an independent consumer. The steward does not coordinate between them; they all see consistent state because the steward is the single source of truth.
What the frontend is built in. The framework imposes nothing; this section names the common choices so a distribution knows the landscape.
For any UI rendered in a browser, whether kiosk, local HTTP, or remote. All of these are first-class; no preference:
| Framework | Notes |
|---|---|
| React | Ubiquitous; large ecosystem; good for teams already invested. |
| Angular | Strong for larger applications with explicit structure. |
| Vue | Approachable; popular in Europe and Asia. |
| Svelte | Compile-time framework; small runtime; fast. |
| Solid | React-like API with fine-grained reactivity. |
| Lit | Web Components standard; small footprint; framework-agnostic interop. |
| HTMX | Server-driven; minimal JavaScript; back-to-basics. |
| Vanilla JS/TS | No framework; appropriate for tiny UIs or embedded contexts. |
All of these consume the steward via either a WebSocket bridge (for subscription support) or an HTTP bridge (for request/response only). The protocol is the same; the framework is the distribution's choice.
For native applications targeting desktop, mobile, or multiple platforms:
| Toolkit | Platforms | Notes |
|---|---|---|
| Flutter | iOS, Android, desktop, web | Dart; strong mobile story; growing desktop. |
| React Native | iOS, Android, some desktop | JavaScript; large ecosystem. |
| Tauri | Desktop (Linux, macOS, Windows) | Rust backend + web frontend; small binaries. |
| Electron | Desktop | Node + Chromium; large binaries; well-understood. |
| Qt | Desktop, mobile, embedded | C++ or QML; widely used in automotive and industrial. |
| GTK | Linux desktop, mobile | C / Rust / Python bindings. |
For platform-specific apps where cross-platform is not a goal:
- iOS: Swift + SwiftUI or UIKit.
- Android: Kotlin + Jetpack Compose or traditional Views.
- macOS: Swift + SwiftUI or AppKit.
- Windows: WinUI, WPF, WinForms, or native Win32.
- Linux: Qt, GTK, or low-level X11/Wayland toolkits.
For CLI or TUI consumers:
| Library | Language | Shape |
|---|---|---|
| Textual | Python | Rich TUI framework; reactive; modern. |
| Ratatui | Rust | Low-level TUI; high performance. |
| Bubble Tea | Go | Elm-inspired; functional. |
| Ink | Node.js | React-for-terminals. |
| tview / termui | Go | Widgets-based; fast start. |
| ncurses | C / any binding | Traditional; portable. |
For one-shot commands, plain shell scripts plus the socat-based framing in CLIENT_API.md section 6.6 are enough.
Where the UI is the integration platform's UI, not ours:
| Platform | Integration language |
|---|---|
| Home Assistant | Python (custom components) or YAML + MQTT (device discovery). |
| openHAB | Java / Kotlin (bindings) or MQTT / HTTP things. |
| Node-RED | JavaScript (custom nodes) or HTTP / MQTT flows. |
| ioBroker | JavaScript (adapters). |
| Homebridge | JavaScript (plugins) bridging to HomeKit. |
| Domoticz | Python / LUA / HTTP. |
Each has its own contract for how external devices appear. The bridge plugin on the evo side speaks the platform's expected protocol (usually MQTT, sometimes HTTP); the platform-side integration is minimal.
Whatever the target ecosystem uses. Distributions shipping into automotive, industrial, or white-label appliance markets will meet frameworks not on this list. The pattern is the same: bridge plugin exposes whatever the target framework consumes; the target framework renders the UI.
How the frontend reaches the steward. Three shapes, with the choice determined by where the frontend runs.
Frontend runs on the same device. It opens the Unix socket directly and speaks the protocol.
Appropriate for: on-device kiosk, on-device native, on-device terminal, on-device voice. The frontend has filesystem access to the socket and is part of the distribution's trust boundary.
Latency is microseconds. No authentication needed - the distribution controls who has filesystem access.
A bridge plugin is a respondent that runs on the steward's side, terminates the local Unix socket, and exposes its own remote interface. It is a plugin like any other; PLUGIN_AUTHORING.md covers how to write one.
Shape:
flowchart LR
RC["Remote Consumer<br/>browser / mobile / HA"]
subgraph device ["ON-DEVICE"]
BP["<b>Bridge Plugin</b><br/>HTTP / WS / MQTT / gRPC"]
S["Steward"]
BP <-->|client socket| S
end
RC <==>|"remote protocol<br/>TLS · auth · rate limit"| BP
The bridge plugin:
- Listens on a network interface (TCP port, Unix socket at a different path, whatever).
- Speaks whatever remote protocol the distribution chose (HTTP, WebSocket, MQTT, gRPC, plain TCP framed, etc.).
- Authenticates remote clients (TLS certificates, tokens, basic auth, OAuth, whatever is appropriate).
- Translates between its remote protocol and the steward's client socket.
Because the bridge is a plugin, it is sandboxed to its declared trust class, it is a distribution-specific piece of code that does not live in evo-core, and swapping bridges (HTTP -> WebSocket, WebSocket -> gRPC) is a plugin change, not a steward change.
Distributions tend to ship one or more of:
| Bridge type | Protocol | Typical consumers |
|---|---|---|
| HTTP bridge | REST over HTTP/HTTPS | Web frontends, mobile apps, third-party scripts |
| WebSocket bridge | WebSocket over HTTP/HTTPS | Web frontends needing live updates, mobile apps |
| MQTT bridge | MQTT over TCP/TLS | Home automation systems, IoT platforms |
| gRPC bridge | gRPC over HTTP/2 | Microservice environments, polyglot ecosystems |
| ZeroMQ / NATS bridge | Message bus | Event-driven architectures |
| Proprietary protocol bridge | Whatever | Integration with OEM-specific backplanes |
A common shape for a consumer-friendly web UI is a combined HTTP + WebSocket bridge where:
GET /api/v1/custodiesmaps toop = "list_active_custodies".POST /api/v1/request/<shelf>/<type>maps toop = "request".GET /api/v1/wsupgrades to WebSocket and streams translated happenings.
The mapping is the bridge plugin's business. Different distributions will make different mapping choices; there is no one blessed REST-ification of the client protocol. Vendor-specific bridges ship in the evo-device-<vendor> repository.
The core protocol assumes local trust (CLIENT_API.md section 9). Bridges restore the security properties that the network environment needs:
- TLS for transport encryption.
- Authentication (tokens, certificates, OAuth, mutual TLS).
- Rate limiting against abuse.
- Input validation at the bridge boundary.
- Audit logging of remote requests.
None of this is in the steward. All of it is in the bridge plugin. A distribution serious about remote access ships a production-grade bridge; a distribution serving only local consumers can ship nothing at all.
Real distributions ship more than one frontend. Some common combinations:
The main frontend is a kiosk or web UI for end users. A terminal CLI sits alongside for operators, accessed over SSH. Both hit the client socket directly; they do not coordinate.
The main frontend renders the visible device state. A voice assistant (Rhasspy, Willow, Alexa) sits alongside and issues op = "request" calls on spoken commands. Voice feedback comes from TTS driven by the voice process, not by the primary UI.
The device has its own UI for direct users, and simultaneously appears inside Home Assistant or openHAB for integration into broader home automation. The MQTT bridge plugin exposes the device to the home automation system; the primary UI works independently.
On-device UI for local interaction; mobile app for remote control. The mobile app reaches the device via an HTTP or WebSocket bridge over the LAN. Both see consistent state through the steward.
Some distributions ship no on-device frontend at all. The device is operated entirely through bridges: HTTP for a web interface on another device, MQTT for home automation, voice via a separate assistant. The device itself has no screen.
The device is a component inside a larger product (an automotive infotainment system, an industrial control cabinet, a smart-home hub). The upstream product has its own HMI framework and operates the device through a proprietary bridge. The "primary UI" from the end user's perspective is the upstream product's UI, not ours.
The framework imposes one constraint on frontends: they must not address plugins directly. Every interaction with a plugin goes through the steward via the client socket. The bridge pattern preserves this: the bridge talks to the steward through its own client socket, not to plugins via shortcut paths.
In practice, this rules out:
- Opening sockets that specific plugins happen to expose.
- Running plugin binaries directly from the frontend.
- Reaching into plugin filesystem state.
- Calling OS commands that bypass the steward (e.g.,
systemctl restart <plugin>.service).
If a frontend needs something a plugin has but the steward does not expose, the fix is to widen the plugin's request interface (a new request_type), not to bypass the steward. This is the same discipline that keeps the plugin ecosystem coordination-free.
Security considerations for frontends are deployment-shape-specific:
| Deployment | Primary threat model | Mitigation |
|---|---|---|
| On-device direct | Local user has full access (often fine for appliances with known operators) | Filesystem permissions on the socket |
| On-device local HTTP (kiosk only) | None beyond local user | Bind to localhost only |
| On-device local HTTP (LAN exposed) | LAN-reachable attackers | Authentication at the HTTP layer; TLS; bridge plugin enforces |
| Remote browser over WAN | Internet-reachable attackers | TLS, strong authentication, rate limiting, probably a reverse proxy; bridge plugin enforces all of it |
| Home automation MQTT | Whatever MQTT broker's security posture is | Broker-level TLS + credentials; MQTT bridge enforces |
| Proprietary / embedded HMI | Depends on the upstream product's threat model | Defer to upstream; bridge speaks whatever they demand |
The steward itself has no authentication layer. Every security boundary is at a bridge plugin. This is a deliberate split: the steward is the trusted core; the bridge is the distribution-specific policy enforcement point. A distribution that gets its bridges right can safely expose the device anywhere; a distribution that skips the bridge layer must keep the steward strictly local.
This document names technologies, patterns, and combinations. It does not prescribe any of them. A distribution is sovereign over:
- Which frontend(s) to ship, if any.
- Which frameworks and libraries to use.
- Which deployment shape(s) to target.
- Which bridges to implement and how.
- What security posture to enforce.
- How to version and evolve all of the above.
The framework provides: a stable client protocol, a plugin SDK, a documented boundary, and reference examples. What you build on top of those is yours.
BOUNDARY.mdsection 6 - frontend is a distribution component.CLIENT_API.md- the protocol every frontend speaks, with language examples.PLUGIN_AUTHORING.md- how to write a bridge plugin (a bridge is just a respondent).PLUGIN_CONTRACT.md- the plugin wire protocol a bridge must terminate.CONCEPT.mdsection 6 - the kiosk rack's role for on-device surfaces.VENDOR_CONTRACT.md- trust classes for bridge plugins that cross security boundaries.DEVELOPING.mdsection 6 - talking to a running steward for frontend development.