vike-desktop
The desktop app — a native egui multi-window shell that is a lossy observer of a trading core it does not run, in one build with no cargo features.
The desktop application: a native egui multi-window shell with live charts. Each window has a rich
title toolbar — chart-style menu, symbol dropdown, interval dropdown, LIVE badge, clone, minimize
and maximize icons (crates/vike-desktop/src/main.rs).
Market data flows feed threads → core ingest (lossless bar closes, conflated forming bars and
ticks) → the core-owned bar cache → an arc-swap CoreSnapshot, which this GUI converts to its
render model once per changed snapshot. The GUI is a lossy observer; it never touches the core's
state directly. That is the property every panel inherits, and it is why a slow frame can cost a
picture and never an order.
One build, no features
crates/vike-desktop/Cargo.toml declares no [features] table, so there is one program here
and cargo build -p vike-desktop is the whole of it. What that build does NOT contain is the
interesting half, and the manifest records each absence as a tombstone rather than a deletion:
- no trading core.
vike-mount(the venue→ExecutionClient/ReconClientcomposition root) andvike-run(build_node) stood in the manifest as optional dependencies turned on by afatfeature. Both are gone with it, so nomake_enginearm is compiled and this binary mounts no venue, signs no order and runs no reconciliation. - no DataFusion.
vike-datais taken with DEFAULT features — theHistStoretrait and nothing behind it. The manifest's own check iscargo tree -p vike-desktop -i datafusion, which must report the crate unreachable. - no collectors.
vike-backfillwas enabled by the samefatfeature; the Data Manager's bulk action now takesbackfill_route's WIRE arm against a datahub's store instead. - one venue crate, unconditionally.
vike-deribit, because the options-chain providervike-app-coremounts is Deribit's.
It still links the (DataFusion-free) Studio and drives it remote-only.
The ibkr and polymarket features went with fat
They gated live venue arms by flipping the matching vike-mount/vike-run feature. With no
vike-mount dependency there is no arm to gate, so a venue named at this binary is not demoted to
paper — it is not reachable from here at all. Those gates live in
vike-tradehub now, which is the one live root.
--observe
vike-desktop --observe <ADDR> is the read-only client mode: it connects to a headless
vike-tradehub daemon's observe server instead of
spawning a local trading core — which it could not do in any case. The argv scan lives in the
binary — argv is the binary's own fact — and feeds
vike_app_core::backend_conn::startup_backend, which is the ladder that decides which node is
actually dialled. A bare --observe is not an error: the app can do nothing else, so the word
alone means "the configured node" and the ladder falls through to the registry. Observe mode is made unmistakable in the OS window title, which reads
OBSERVING <addr> — and since the address always resolves, every run now carries that title
rather than the historic — live one.
It no longer refuses to start without an address
There is no local trading core, so App::new's local-core arm is compiled out — but the observe
address now always resolves (flag, else the active backend, else the local default), so the arm it
could have fallen into is unreachable by construction rather than by an early exit. The window
opens and the status bar says whether it connected, which is the thing a person can act on.
There is no local store engine either, so open_studio_store's no-address arm is an error
naming both fixes — set config.datahub_addr to a running
vike-datahub, or connect a backend whose daemon
advertises one — rather than a feature-shaped "rebuild fatter" shrug. See
observe mode for the mode itself.
Startup order
resolve_settings runs first — before the log subscriber, the window, any feed and any core.
Two reasons, in order of weight: the log directory (config.log_dir) and both log levels are
themselves settings, so a subscriber built before the load could only ever honour the environment;
and a stale risk-ceiling variable must stop the process before it does anything at all. It logs
nothing, which is why it can run that early; everything it would have said is emitted the instant
the subscriber exists, including the warnings vike_config deliberately returns as data — a clamp
nobody is told about is a limit the operator believes they set and does not have. A failure prints
to stderr and exits 2.
The binary performs the process's one std::env::vars() sweep and hands it to vike_boot::boot,
which owns the startup sequence: refuse removed environment variables, walk once for the settings
directory, load the credentials and refuse an arming credential file, then load the settings and
render the disclosure. The credential loader is passed as a function, so vike-boot owns when
the store is opened and this binary owns how.
A credential file may not arm real money
secrets.env is plaintext and parsed last-wins, so appending one line to it — with no read access
at all — would otherwise be enough to flip this app's venue mount onto a live venue. vike-boot
runs that refusal the moment the credentials are loaded, before the settings load and long before
the window, the feeds, the core and every mount (vike_config::arming). It does not change which
sources arm; it makes an arming credential file stop the process rather than run it.
Where the logic actually lives
vike-desktop is deliberately outside the derived CI roster (xtask/src/ci/tables.rs's
EXCLUDE_FROM_CI). CI's app-check job compiles it once — there is one feature resolution to
compile — runs
cargo clippy -p vike-desktop --all-targets -- -D warnings, and runs cargo nextest run -p vike-desktop —
but only two things execute in the crate: the pure-logic byte-layout pins in
crates/vike-desktop/src/chart_gpu.rs, and main.rs's font_binding_tests, which installs the fonts
and renders one real frame to assert every named family is bound to a usable face — it exists
because a Linux build that panicked on frame one shipped without it. Everything else, including
the whole of App::update, is type-checked and linted and then never run.
That is why the decisions moved down into vike-app-core, which rides the normal roster: the
per-frame CoreSnapshot fold (core_sync), the feed lifecycle, the startup layout, window spawn,
order dispatch, venue routing and the --observe reverse bridge (observe_bridge, whose
hand-maintained order-status mapping is now pinned by an exhaustive round-trip test — before the
move, a newly added variant silently rendered as terminal Rejected in the observer). The size of
the remaining unexecuted half is ratcheted by
crates/vike-ops/tests/ci_excluded_gui_shell_ratchet.rs, which fails a PR that grows it. Putting a
new branch in main.rs instead of in vike-app-core is choosing the unexecuted half.
Headless capture
The app has a QA capture mode driven by environment knobs the binary reads and injects as
vike_app_core::startup::StartupEnv. VIKE_SHOT being present marks a headless capture and
suppresses the saved-workspace restore, so a capture always renders the deterministic default
windows; VIKE_SHOT_WIN selects which single tool window opens instead of the default chart;
VIKE_STYLE forces every window to one ChartStyle and VIKE_SCALE forces every window's
price-scale mode, each ignoring an unparseable or out-of-range value. The recognised
VIKE_SHOT_WIN values are deliberately not written down in prose — the test
each_shot_win_value_opens_exactly_its_own_tool_window_and_no_feed iterates them, so they live in
something that goes red rather than in something that goes stale.
Related: the desktop app, the chart, connections, workspaces.