Desktop — PC Client
demostatics-pc_application is the native desktop client, written in Rust as a Cargo workspace of 18 library crates, one binary and one reference server. It is the largest body of real code on the platform, and it is also the component that is furthest from the rest of it — it has never been connected to demostatics-web.
What it is
Section titled “What it is”The client subscribers run on Windows, macOS and Linux to consume real-time data. It is deliberately a thick client: the server supplies data, authentication and subscription entitlement, and the UI plus all statistical and mathematical analysis run locally on the user’s machine.
That split is stated in demostatics-pc_application/README.md and demostatics-pc_application/docs/ARCHITECTURE.md, and it is enforced by the crate graph. ds-analytics — the statistics engine — has exactly two dependencies, ds-core and serde. It cannot reach the network or the UI because those dependency edges do not exist.
Entitlement is checked client-side against a server-issued token (ds-auth plus identity.can(flag) in ds-core), but the authoritative gate is the server: the stream server filters topics and premium indicators before they reach the wire.
Workspace version is 0.1.0, edition 2021, minimum Rust 1.75, license LicenseRef-Proprietary. There has been no tagged release.
Why Rust
Section titled “Why Rust”docs/ARCHITECTURE.md records the decision as Rust over C++, high confidence. The reasoning, summarized:
- Raw performance is a wash. Both are native, no-GC, deterministic. Performance did not decide it.
- The tiebreaker was compile-time data-race freedom. The dominant engineering risk in an always-connected, long-running client is the concurrent zero-copy hot path. Rust’s
Send/Syncplus the borrow checker make data-race-free frame delivery a compile-time guarantee rather than a review discipline. - Async networking.
quinn,tonicandtokio-tungsteniteall run on one runtime. - Modularity becomes a type-system invariant. A Cargo workspace makes each crate its own compilation unit, so “leaf crates must not import IO or UI” is checked by the compiler, not by convention.
The document also names the case it would have lost: an already-Qt-native team, or a team valuing Qt’s mature Location and Charts widgets over compile-time concurrency safety.
The workspace enforces the discipline it claims. unsafe_code = "deny" is set at workspace level, with rust_2018_idioms and all of clippy at warn.
Threading model
Section titled “Threading model”Three physically separate domains connected by bounded channels, under one policy: the client may drop, never stall. A saturated channel discards frames rather than applying backpressure to the render loop.
┌────────────────────────────────────┐ │ 1. UI / render thread │ ~16.6 ms budget │ eframe + egui + egui_tiles │ reads ONE snapshot per frame │ │ no .await, no disk, │ │ no locks held across a frame └───────────────▲────────────────────┘ │ load_full() (lock-free read) ┌───────┴────────┐ │ arc-swap slot │ latest UiSnapshot only; older ones are dropped └───────▲────────┘ │ store(Arc::new(snapshot)) ┌───────────────┴────────────────────┐ │ 2. tokio IO domain │ owns sockets / TLS / QUIC / gRPC │ "data-plane" OS thread with │ decode -> StreamEngine::apply │ its own tokio runtime │ -> conflate queued burst -> publish │ │ mpsc(1024) inbound, mpsc(32) control out └───────────────┬────────────────────┘ │ ┌───────────────┴────────────────────┐ │ 3. rayon compute pool │ ds-compute::ComputeNode │ correlation jobs + metering │ drives the Compute pane └────────────────────────────────────┘Status: Partial. Domains 1 and 2 are Shipped and are the ones the running binary uses. bin/demostatics/src/gui/mod.rs spawns a named data-plane OS thread with its own tokio runtime, publishes an immutable UiSnapshot into an ArcSwap, and the egui thread does one load_full() per frame. Burst conflation is real: the loop drains everything already queued with try_recv before publishing, so the UI only ever sees the latest.
Domain 3 is where the code and the design document diverge. ARCHITECTURE.md describes the rayon pool as carrying decode, delta-merge, downsampling and statistics. In the code, rayon appears only in crates/ds-compute/src/lib.rs. Decode and delta-merge run on the data-plane thread; LTTB downsampling runs on the UI thread at repaint (ds_charts::lttb is called from gui/dock.rs and gui/dashboard.rs). The rayon pool exists and does real work, but not the work the diagram assigns it.
Crate map
Section titled “Crate map”Read from demostatics-pc_application/Cargo.toml, whose member comments are the canonical one-line descriptions, and confirmed against each crates/*/Cargo.toml. All 18 crates below exist on disk and are workspace members.
| Crate | What it does | Status |
|---|---|---|
ds-common | Foundation: errors, newtype IDs, Timestamp, tracing/telemetry init | Shipped |
ds-core | Pure domain, no IO/UI/async — taxonomy, entities, identity, risk, alerts, usage, time, units | Shipped |
ds-analytics | Client-side statistics engine: descriptive, streaming (Welford/EMA/rolling), regression, correlation, risk index | Shipped |
ds-charts | Framework-agnostic chart math: LTTB downsampling plus auto-ranging, zero GUI deps | Shipped |
ds-graph | Curve fitting (linear/poly/exp/log/power/auto) with R², asymptotics, nice grid ticks | Shipped |
ds-proto | Wire envelope and frame schemas (JSON dev format) | Shipped |
ds-transport | WebSocket client plus reconnect/backoff; QUIC and gRPC behind features | Shipped |
ds-stream | Real-time ingestion: snapshot/delta apply, schema_ver gate, sequence-gap detect, clock sync, subscription manager | Shipped |
ds-auth | Login providers (DevAuthProvider, HttpAuthProvider) and token stores (in-memory, OS keyring) | Shipped |
ds-store | Local offline cache — file-backed JSON doc store in the platform cache dir | Shipped |
ds-config | Config persistence to the platform config dir (atomic JSON) | Shipped |
ds-platform | OS abstraction: per-platform config/cache/data dirs plus a Notifier trait | Shipped |
ds-telemetry | Console plus daily-rotating file logs and a crash-report panic hook | Shipped |
ds-hw | Pure hardware profiler: PerfClass tiers and recommended worker counts | Shipped |
ds-sysinfo | Live CPU/RAM/disk/temperature/network telemetry for the specs bar | Shipped |
ds-ui | Shared design system (theme, widgets, hand-painted icons) plus the tier-gated FeatureModule mount seam | Shipped |
ds-ai | Assistant providers: LocalAssistant offline, CloudProvider server-proxied, LocalModelProvider OpenAI-compatible | Partial |
ds-compute | Distributed compute node: rayon worker pool running correlation jobs plus throughput metering | Partial |
bin/demostatics | The only shippable binary — CLI, headless runner, and the gui/ shell | Shipped |
examples/mock-stream-server | Local reference backend so the client runs with zero real credentials | Shipped |
ds-ai is Partial because LocalAssistant streams and reasons over the structured context but does no real model inference — its own crate documentation says so. CloudProvider has complete plumbing (SSE, tools, vision) but points at a backend endpoint that only the mock implements. ds-compute is Partial because the local rayon engine and metering work while backend job coordination does not exist.
Crates that are named but do not exist
Section titled “Crates that are named but do not exist”ARCHITECTURE.md documents these in its target tree marked ◦. Every one is absent from crates/ — verified directory by directory.
| Crate / tree | Intended role | Status |
|---|---|---|
ds-databus | Lock-free UI hand-off: SPSC/MPSC rings, seqlock, arc-swap | Planned |
ds-updater | Signed auto-update (stable/beta channels, delta, kill-switch) | Planned |
ds-map | Geospatial rendering: wgpu overlay compositor plus lyon tessellation | Planned |
ds-metering | Client-side usage counters feeding usage-based billing | Planned |
ds-sdk | Plugin host API (versioned IPC) for third-party plugins | Planned |
crates/features/ (11 crates) | Per-feature plugin crates behind the FeatureModule trait | Planned |
The crates/features/ tree — ds-feat-auth, ds-feat-dashboards, ds-feat-risk, ds-feat-map, ds-feat-listings, ds-feat-news, ds-feat-forums, ds-feat-docs, ds-feat-notifications, ds-feat-reports, ds-feat-settings — is entirely Planned. The FeatureModule seam those crates would plug into is real and load-bearing today: it lives in crates/ds-ui/src/module.rs and registry.rs, and every singleton pane in the binary already mounts through it. The plugin crates just have not been split out.
Several other top-level directories from the target tree are also absent: xtask/, assets/, config/, tests/, deploy/, third_party/, vendor/, examples/plugins/ and examples/api-partner/.
The GUI
Section titled “The GUI”An egui/eframe dockable workspace built on egui_tiles, behind the gui cargo feature. It is branded neutrally as “Console” — the Demostatics name is deliberately absent from the app chrome, which is recorded as a product decision in milestone 5 of ARCHITECTURE.md.
Every view is a pane you can split, tab, drag, resize, add or close. The pane set in bin/demostatics/src/gui/dock.rs is Overview, Chart, Risk, Compute, Forums, Assistant, Hardware, Reports, Analysis, Alerts and Settings.
Keyboard shortcuts, from gui/mod.rs and gui/palette.rs:
| Shortcut | Action |
|---|---|
Ctrl/Cmd+K | Command palette — fuzzy, keyboard-first launcher for every workspace action |
Ctrl/Cmd+, | Settings |
F11 | Fullscreen |
Layout tree, settings, alert rules and global zoom all persist through ds-config to the platform config directory. The latest data is kept in ds-store in the platform cache directory, on by default, so the app opens on last-known data before the stream connects.
Render quality tiers
Section titled “Render quality tiers”Graphics quality is one knob, profiled through ds-hw. ds_hw::classify(logical_cores, total_memory_bytes) returns a PerfClass, which gui/render.rs maps onto six user-facing tiers: Auto, Low, Balanced, High, Ultra, Custom. Each resolves to a RenderProfile of target FPS, per-series chart point cap and line width, applied to the repaint cadence and every chart pane. Custom exposes manual refresh-rate and chart-resolution sliders.
That RenderProfile is the documented seam a future raw-wgpu paint path would consume unchanged. The wgpu backend itself is Planned — the source comment in gui/render.rs states the eframe-to-wgpu backend swap is a deliberate, separately-validated step held back until it is proven on every CI platform under software rendering.
Build and run
Section titled “Build and run”The justfile at the repo root is canonical for dev commands. Everything below is the raw cargo form, with the just shortcut noted.
# build the whole workspace, no GUI/GL system libraries needed (just / just build)cargo build --workspace
# terminal A: local fake backend, WebSocket on 127.0.0.1:9001 (just serve)cargo run -p mock-stream-server
# terminal B: headless — log in, process 50 frames, print the (just run --frames 50)# locally computed analytics tablecargo run -p demostatics -- --frames 50
# one-shot end-to-end: start the mock, run 50 frames, stop itjust demo
# GUI: live dashboard window (needs a display)cargo run -p demostatics --features gui
# tests, incl. the WebSocket end-to-end roundtrip in ds-stream (just test)cargo test --workspace
# lints, denying warnings exactly as CI does (just lint)cargo clippy --workspace --all-targets -- -D warnings
# format (just fmt)cargo fmt --all
# criterion benches: wire decode/encode, delta-merge, curve fit, LTTBcargo bench --workspaceNote the -- separator on cargo run: arguments after -p demostatics belong to cargo unless you pass them through. cargo bench has no just recipe.
The client’s data-plane flag is --server (default ws://127.0.0.1:9001), not --url. The mock server’s listen flag is --addr (default 127.0.0.1:9001).
Linux prerequisites
Section titled “Linux prerequisites”The GUI needs the windowing and GL development packages. Windows and macOS need no extra system packages.
sudo apt-get install -y libxkbcommon-dev libwayland-dev libgl1-mesa-dev libx11-devAdd protobuf-compiler if you build anything with the grpc feature — tonic-build needs protoc.
Cargo features
Section titled “Cargo features”The binary bin/demostatics has exactly two features, both off by default. The rest are crate-level features that remote and gui compose.
| Feature | Where | Unlocks | Status |
|---|---|---|---|
gui | demostatics | eframe/egui/egui_plot/egui_tiles, arc-swap, and the ds-ui/ds-charts/ds-sysinfo/ds-ai/ds-config/ds-store/ds-hw/ds-platform/ds-graph/ds-compute crates, plus PNG capture | Shipped |
remote | demostatics | Real backend clients: turns on ds-auth/http, ds-auth/keyring, ds-transport/quic, ds-transport/grpc and ds-ai/http | Shipped against the mock |
http | ds-auth | HttpAuthProvider — POST /v1/auth/login and /v1/auth/refresh via reqwest/rustls | Shipped |
keyring | ds-auth | KeyringSecretStore — OS keychain token storage instead of memory | Shipped |
quic | ds-transport | quinn QUIC stream plane with WebPKI root validation, SHA-256 cert pinning and an explicit --insecure-tls opt-in | Shipped |
grpc | ds-transport | tonic unary query plane from schemas/proto/query.proto, Bearer-authenticated | Shipped |
http | ds-ai | CloudProvider (server-proxied SSE) and LocalModelProvider (OpenAI-compatible) | Partial |
notify | ds-platform | Real desktop notifications via notify-rust; the gui feature turns this on | Shipped |
The mock server has its own set: http-auth, quic, grpc, and all as a convenience alias for all three.
The full closed loop
Section titled “The full closed loop”mock-stream-server --features all doubles as a contract-faithful reference server, serving WebSocket, HTTP auth, QUIC, gRPC and the POST /v1/ai/chat assistant SSE stream. This is the complete HTTP-login-to-QUIC-stream path.
# terminal A — reference server, every planecargo run -p mock-stream-server --features all -- \ --http-addr 127.0.0.1:9010 --quic-addr '[::1]:9020' --grpc-addr 127.0.0.1:9030
# terminal B — remote client: HTTP login -> QUIC stream.# The reference server is self-signed, so --insecure-tls is required (dev only).cargo run -p demostatics --features remote -- \ --auth-url http://127.0.0.1:9010 \ --transport quic --server 'quic://[::1]:9020' --server-name localhost --insecure-tls \ --email pro@example.com --password demo --frames 40
# gRPC backfill has its own integration testcargo test -p mock-stream-server --features grpc --test grpc_backfillDelivery
Section titled “Delivery”Status: Shipped, unsigned. Both GitHub Actions workflows exist and are described accurately by ARCHITECTURE.md milestone 9.
.github/workflows/ci.yml runs on push to main, on every pull request, on a Monday cron, and on manual dispatch. It does format check, clippy with -D warnings, cargo test --workspace, and a cargo bench --workspace --no-run build check. A second job builds the gui, remote and reference-server feature combinations and runs the gRPC integration test. A third runs cargo-deny for advisories, licenses and bans.
One nuance worth knowing: the test matrix is not all three platforms on every run. Pull requests get Ubuntu only; pushes to main, the weekly cron and manual dispatches get Ubuntu, macOS and Windows. The workflow comment explains the reasoning — macOS bills at 10x, Windows at 2x, and the workspace has exactly one cfg(target_os) site.
.github/workflows/release.yml is tag-triggered on v*. It builds four targets — x86_64-unknown-linux-gnu, aarch64-apple-darwin, x86_64-apple-darwin, x86_64-pc-windows-msvc — packages each, and publishes them to a GitHub Release with a generated SHA256SUMS. rust-toolchain.toml lists six cross-targets; release builds four of them today.
| Packaging path | Artifact | Status |
|---|---|---|
scripts/package-linux.sh | tarball plus .sha256, with .desktop entry and bundled docs | Shipped — a built dist/demostatics-0.1.0-linux-x86_64.tar.gz is present in the repo |
scripts/package-macos.sh | .app bundle from packaging/macos/Info.plist, then dmg | Shipped as a script; not verified end to end |
| Windows zip | portable demostatics.exe archive | Shipped |
| Windows MSI | cargo-wix from wix/main.wxs, installs under Program Files\Demostatics | Partial — the release step is continue-on-error, so a WiX failure never blocks a release |
| Code signing and notarization | — | Not built. Needs certificates and repository secrets |
ds-updater signed auto-update | — | Planned. The crate does not exist |
Known gaps
Section titled “Known gaps”Signed-JWT verification is the headline one. It is listed as Remaining under both milestone 4 and milestone 8. Today ds-auth’s DevAuthProvider mints a token of the form demo.<base64url(claims)>, and the mock server’s auth.rs decodes it and checks expiry only — its own comment says “a real deployment verifies a signed JWT at this point”. HttpAuthProvider will consume a real JWT when a backend issues one; nothing issues one.
FlatBuffers is deferred on measured evidence, not neglected. The production hot path in ARCHITECTURE.md specifies zero-copy FlatBuffers frames. It is deliberately not built, and the document explains why with numbers from the criterion benches: JSON encode is roughly 2.6 µs and decode roughly 3.4 µs for a 64-indicator delta, against 18–37 µs for one chart’s LTTB downsample and 2–5 µs for a curve fit — both of which run every repaint. The wire format is not the bottleneck the render loop is. The decision is to revisit if payload sizes or rates grow by orders of magnitude. Treat this as a deliberate, defensible engineering choice.
The Forums pane makes no network calls at all. bin/demostatics/src/gui/forums.rs is honest about it in its first line — it is a local scratch space holding a Vec<String> in memory, with a “coming soon” note in the UI copy. Multi-user forums need the community server, which does not exist. The Forums pane on the desktop client is unrelated to the forum rules that govern the web-side community.
Schema coverage is thinner than the tree implies. ARCHITECTURE.md shows schemas/flatbuffers/*.fbs and schemas/json/. Neither directory exists. The only file under schemas/ is schemas/proto/query.proto. Both missing entries are correctly marked ◦ in the document; they are listed here because other pages have cited the schemas directory as if all three planes existed.
Benchmarks live per crate, not at the root. ARCHITECTURE.md marks a top-level benches/ as implemented. There is no top-level benches/ directory; there are four criterion benches inside crates — ds-proto/benches/wire.rs, ds-stream/benches/apply.rs, ds-charts/benches/lttb.rs, ds-graph/benches/fit.rs. The benches are real, the path in the tree is not.
The offline assistant does not run a model. LocalAssistant is deterministic — it streams and reasons over structured context but performs no inference. Real inference requires either --local-ai-url pointing at an OpenAI-compatible server or --ai-url pointing at a backend that runs the model. Neither exists in production.
Reading the honest ledger yourself
Section titled “Reading the honest ledger yourself”demostatics-pc_application/docs/ARCHITECTURE.md uses a two-symbol legend that is worth learning, because it is the most reliable status record anywhere in the five repositories:
✓— implemented today◦— planned: documented, not yet created
The legend is applied consistently down the whole target project tree and across the milestone list, and spot-checking it against the filesystem found only the small discrepancies noted above. Where this documentation site and that file disagree, open the file.
For how the desktop client’s contract differs from the one demostatics-web actually serves, see reconciliation and the stream contract. For the server it does not talk to, see Web. For what is scheduled next, see the roadmap.