Investigative experiment: browser as telephony handset, full-stack Rust business UIs.
The browser is not a GUI toolkit. It hosts a WASM process that owns UI, state, and client domain logic, speaking gRPC over WebSocket to a Rust server. Interactive surfaces are drawn with egui onto a single canvas — zero HTML/CSS/JS for the app chrome.
See macaron-PRD.md for the full vision.
Agents extending this stack should start with docs/guide.md (async UI bridge, Tokio rules, unary/stream/bidi recipes).
Three different internal-tool styles on one stack, chosen to stress distinct advantages:
| App | Kind | Stack advantage on display |
|---|---|---|
| Ledger | Classic RAD purchase-order entry | Multi-field forms + line grids in pure Rust; shared PurchaseOrder prost types end-to-end; unary gRPC, no JSON DTOs |
| Pulse | Live ops telemetry dashboard | Server-streaming metrics/events over one WS line; canvas gauges/sparklines without DOM thrash |
| Huddle | Multi-operator field dispatch | Bidirectional streaming (claim/presence/queue) — the reason the PRD rejects plain gRPC-Web |
A single WASM client is a hub that switches between the three. One server binary hosts all three gRPC services at /rpc and serves the static shell.
┌──────────── browser ────────────┐ WebSocket ┌──────── server ────────┐
│ HTML shell (canvas only) │◄──── slozhn ──────►│ axum static + /rpc │
│ WASM: egui + tonic clients │ gRPC semantics │ tonic Ledger/Pulse/ │
│ shared prost types │ unary+stream+bidi│ Huddle services │
└─────────────────────────────────┘ └────────────────────────┘
- UI:
egui/eframe(immediate-mode canvas) - RPC transport: slozhn — gRPC over WebSocket for native + wasm, full streaming, reconnect + session resume
- Schema:
proto/macaron/v1/*.proto→macaron-proto(tonic-prost-build) - Server:
macaron-server(axum + slozhn + in-memory domain stores) - Client:
macaron-client(native desktop and WASM via trunk)
- Rust stable (1.88+ recommended; tested on 1.97)
protoc(protobuf-compiler)trunk(cargo binstall trunkorcargo install trunk)wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown
cd crates/macaron-client
trunk build --release --dist ../../dist# default binds 0.0.0.0:8080 — override if busy
MACARON_ADDR=0.0.0.0:18787 cargo run -p macaron-serverOpen http://127.0.0.1:18787/ — the canvas hub loads. Demo token: macaron-demo.
# server must already be running
MACARON_RPC=ws://127.0.0.1:18787/rpc cargo run -p macaron-client --features nativecargo run -p macaron-server --bin smoke -- ws://127.0.0.1:18787/rpcExpected: ALL SMOKE CHECKS PASSED covering Ledger CRUD, Pulse streams, Huddle join/claim bidi.
macaron/
macaron-PRD.md
proto/macaron/v1/ # shared contracts
crates/
macaron-proto/ # generated prost + tonic stubs
macaron-server/ # axum + services + smoke bin
macaron-client/ # egui hub (native + wasm)
dist/ # trunk output (served by server)
When replicating or adopting this gRPC-over-WebSocket + WASM canvas pattern in other codebases, agents must implement the following server, client, and reverse proxy optimization standards:
Because the client-server bridge operates over WebSockets (slozhn protocol), the hosting reverse proxy (e.g., Caddy, Nginx) must support persistent connections and compression:
- WebSocket Handshake: The proxy must pass the HTTP/1.1
UpgradeandConnectionheaders to the backend upstream. - Over-the-Wire Compression: Since WASM files are large, the proxy must compress static assets. Brotli or Zstd compression is strongly recommended.
- Caddy Configuration Example:
macaron.dwp.solutions { encode zstd gzip reverse_proxy localhost:18787 }
- Caddy Configuration Example:
- Same-Origin Security / CORS: If the client is hosted on a different origin than the
/rpcendpoint, appropriate CORS headers and WebSocket security policies must be explicitly set on the proxy.
WASM execution is highly sensitive to binary size. Optimize the compilation footprint in the root Cargo.toml:
- Link-Time Optimization (LTO): Set
lto = trueandcodegen-units = 1in[profile.release]to enable global dead-code elimination. - Size Optimization Flags: Use
opt-level = "z"to minimize binary payload size. - Granular Feature Gates: Guard heavy OS-native runtime dependencies (like
tokio/rt-multi-thread,tokio/net) under feature flags so they are excluded from thewasm32-unknown-unknowncompilation graph.
- UI Render Loop: egui operates on a synchronous rendering thread. Never execute blocking operations in the UI render cycle.
- Mailbox / BUS Pattern: Offload network futures to background workers (
spawn_local_fut) and communicate events back to the UI loop via thread-safe Mutex queues (drain_bus). - Throttling: Implement queue throttling (e.g., limit to 50 updates per frame) when draining the message bus to prevent frame drops during network updates.
- Task Cleanup: Always wrap spawned client streams in
futures::future::Abortableto prevent background task leaks when routes transition.
Maintain the <script type="application/agent-hints+json"> tag in the bootstrap HTML shell. This allows inspecting agents to automatically discover DOM nodes, protocols, and troubleshooting details when debugging graphics contexts (such as WebGL 2 permissions).
- End-to-end typed data with zero JSON and zero DOM UI code for the interactive surface
- Bidirectional streaming works over the WebSocket transport (Huddle)
- WASM size acceptable for internal tools (current release build ~4.1 MB uncompressed wasm; well under multi-MB internal-tool budgets when gzipped)
- Developer iterates on forms and
.protocontracts in pure Rust
- Hub — read the three cards; note connection badge (ws URL).
- Ledger — Refresh seed POs, edit vendor/lines, Save (server validates), create a new order.
- Pulse — Start streams; watch per-service CPU/RPS sparklines and the ops event feed update live.
- Huddle — Join as an operator, Claim a critical job. Open a second tab/native client as another operator and watch the queue/presence update without polling.
Macaron v0 investigative prototype: vertical slice is running.
Following a comprehensive architectural evaluation of the async runtime (see tokio_evaluation.md), the following improvements have been applied:
- WASM Footprint Optimization: Heavy Tokio features (
rt,macros,time, andnet) are gated exclusively under thenativefeature flag, keeping the browser WASM package lightweight. - Client-Side Task Cancellation: Added stream-stopping capabilities to pulse.rs using
Abortablefutures, preventing task/network leaks when toggling streams.
Next PRD phases will measure payload/latency and compare developer productivity against React + REST.
Macaron by NDX Pty Ltd is licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
Copyright 2026 NDX Pty Ltd and contributors.
You are free to use, modify, and redistribute Macaron — including in commercial and internal products — under those terms. Contributions are welcome under the same license; see CONTRIBUTING.md.