Skip to content

Repository files navigation

Macaron by NDX Pty Ltd

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).

Showcase apps

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.

Architecture

┌──────────── 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/*.protomacaron-proto (tonic-prost-build)
  • Server: macaron-server (axum + slozhn + in-memory domain stores)
  • Client: macaron-client (native desktop and WASM via trunk)

Quick start

Prerequisites

  • Rust stable (1.88+ recommended; tested on 1.97)
  • protoc (protobuf-compiler)
  • trunk (cargo binstall trunk or cargo install trunk)
  • wasm32-unknown-unknown target: rustup target add wasm32-unknown-unknown

Build WASM client

cd crates/macaron-client
trunk build --release --dist ../../dist

Run server (serves dist/ + gRPC/WS)

# default binds 0.0.0.0:8080 — override if busy
MACARON_ADDR=0.0.0.0:18787 cargo run -p macaron-server

Open http://127.0.0.1:18787/ — the canvas hub loads. Demo token: macaron-demo.

Native client (optional)

# server must already be running
MACARON_RPC=ws://127.0.0.1:18787/rpc cargo run -p macaron-client --features native

Headless smoke test (all three services)

cargo run -p macaron-server --bin smoke -- ws://127.0.0.1:18787/rpc

Expected: ALL SMOKE CHECKS PASSED covering Ledger CRUD, Pulse streams, Huddle join/claim bidi.

Workspace layout

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)

Adopting the Macaron Architecture (for Agents)

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:

1. Reverse Proxy & Routing Requirements

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 Upgrade and Connection headers 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
      }
  • Same-Origin Security / CORS: If the client is hosted on a different origin than the /rpc endpoint, appropriate CORS headers and WebSocket security policies must be explicitly set on the proxy.

2. Client & Code Size Optimizations

WASM execution is highly sensitive to binary size. Optimize the compilation footprint in the root Cargo.toml:

  • Link-Time Optimization (LTO): Set lto = true and codegen-units = 1 in [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 the wasm32-unknown-unknown compilation graph.

3. Asynchronous UI Guidelines

  • 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::Abortable to prevent background task leaks when routes transition.

4. Self-Documentation for Agents

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).

What success looks like (from the PRD)

  • 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 .proto contracts in pure Rust

Demo walkthrough

  1. Hub — read the three cards; note connection badge (ws URL).
  2. Ledger — Refresh seed POs, edit vendor/lines, Save (server validates), create a new order.
  3. Pulse — Start streams; watch per-service CPU/RPS sparklines and the ops event feed update live.
  4. 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.

Status

Macaron v0 investigative prototype: vertical slice is running.

Tokio Optimization & Task Cancellation

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, and net) are gated exclusively under the native feature flag, keeping the browser WASM package lightweight.
  • Client-Side Task Cancellation: Added stream-stopping capabilities to pulse.rs using Abortable futures, preventing task/network leaks when toggling streams.

Next PRD phases will measure payload/latency and compare developer productivity against React + REST.

License

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.

About

An investigative experiment into network distributed widgetry.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages