// under the neon

Architecture

A Tauri 2 shell, a Tokio async core, and a vanilla-JS dashboard — one process, no sidecars.

The shape

Two halves, one binary

A Rust backend owns the sockets and all state; a static HTML/CSS/JS frontend renders it. They talk over Tauri's IPC — commands one way, events the other. Nothing else is running.

Dashboard
HTML · CSS · vanilla JS
served from the binary
⇆
Tauri 2 IPC
25 commands →
9 event types ←
⇆
Rust / Tokio core
listeners, state,
rules, feeds, replay
⇆
Sockets
TCP + UDP
clients on the wire
Inbound path

What happens to a line on the wire

Every byte a client sends walks the same path, whichever transport or framing it arrived on.

accept / recv
TCP task or UDP loop
→
FrameCodec
split into a message
→
render + record
text/hex, metrics, stream
→
auth → command → rules
first match wins
→
deliver / broadcast
reply, or echo, or silence

Outbound bytes are re-framed by the same codec and pushed to a client's own unbounded channel (TCP) or sent to its address (UDP), then logged to the stream with per-client counters updated.

The code

Ten small Rust modules

Each does one thing; the pure ones (codec, payload, rules) carry the unit tests.

main.rs

windowed entry point → lib::run().

lib.rs

Tauri builder, the 25-command registry, and config load on startup.

state.rs

config types, metrics, client records, recorder/replay state — the one shared AppState.

server.rs

TCP accept loop, UDP loop + idle sweeper, per-client tasks, delivery, feeds and replay.

codec.rs

the framing codec — newline / CRLF / length-prefix / raw — as a Tokio Decoder/Encoder.

payload.rs

text-escape and hex parsing in; byte→text/hex rendering out, with binary detection.

rules.rs

the auto-responder — compile, match, and the template renderer.

feeds.rs

the timer-feed generator and the built-in geo world-map simulator.

persist.rs

load/save config.json in the app config directory.

commands.rs

the 25 IPC handlers the dashboard calls.

Why it holds up

Concurrency & design choices

A task per client

Each connection runs on its own Tokio task with a select! over "bytes to send", "bytes received" and "kick". A per-client cancellation token makes a kick or a shutdown close the socket immediately — no polling, no polite timeout.

Lock-light shared state

DashMap for the client roster and channels, atomics for the counters, and parking_lot mutexes only for the small ring buffers. The metrics snapshot is a cheap read.

Live config, no bounce

Saving rules or feeds recompiles them and restarts the feed tasks in place via a swappable token — the listener never drops. Only bind, transport and framing need a restart.

No runtime, no sidecar

Everything is in one Rust binary behind the webview. No .NET, no Node, no Python, no external service — the whole reason the download is ~6 MB and starts instantly.

Small modules, one shared state, a task per socket. Boring on purpose — so the neon can be the interesting part.