vike
Desktop app

Observe mode

vike-desktop --observe against a headless daemon — the one mode the shipped binary can compose, the two the library still declares and nothing reaches, the self-healing bridge, and the control channel that can place real orders.

vike-desktop --observe <ADDR> names the node to watch: the app connects to a headless vike-tradehub daemon's observe server instead of spawning a local trading core. The scan is in crates/vike-desktop/src/main.rs — the argument after --observe is the address — and everything it feeds is in crates/vike-app-core, where it is tested. This page is written from crates/vike-app-core/src/split_plane.rs, backend_conn.rs, observe_bridge.rs, tradehub_control.rs, and crates/vike-desktop/Cargo.toml.

One mode, and two the library still declares

split_plane.rs's app_mode maps the pair (fat build?, --observe given?) to what App::new composes, and it still declares three arms:

build--observemode
fatnoLocalCore — local trading core plus local venue feeds
fatyesObserveWithFeeds — a remote account plane beside local feeds
thinyesObserveOnly — the feed-less observer
thinnorefused before App::new

⚠ The shipped binary reaches exactly one of them. crates/vike-desktop/src/main.rs calls app_mode(false, …) — the first argument is a literal false, because the crate declares no features and the fat resolution that set it was deleted with the local core. So every launch composes ObserveOnly, and the two upper rows are library surface nothing in the release can select. They are listed because the enum is still there and a reader of the source will meet them.

The module doc divides the client's data into two planes with different owners. The account plane — orders, positions, equity, kline history — belongs to the backend and arrives as wire snapshots over the observe bridge. The market-data plane — DOM books, the trade tape, tick, volume and order-flow series — belongs to the venue and is subscribed directly and keylessly. The ObserveWithFeeds would run both in one process — a remote daemon's account plane beside local venue feeds, with no local core — and LOCAL_FEED_VENUES is the one written copy of that feed set (binance, bybit, okx, aster, hyperliquid, polymarket). The arm the release composes, ObserveOnly, mounts no feeds at all: the venue bridges are not in this binary's dependency graph.

crates/vike-desktop/Cargo.toml declares no [features] table. vike-mount and vike-run stand in it as a tombstone comment rather than as dependencies, so there is no venue→engine composition root to reach; vike-data is taken with default features, which is the HistStore trait and nothing behind it, so no DataFusion enters the tree. The manifest's own verification line is cargo tree -p vike-desktop -i datafusion, which must report the crate unreachable. The one venue crate that does link is vike-deribit, an unconditional edge through the options-chain provider.

It links the Studio and drives it remote-only: with a datahub address resolved — config.datahub_addr, else the connected backend's own advertisement — it opens a RemoteHistStore; with neither it shows a "Studio: no data store" label naming both fixes. CI's app-check job compiles the crate once, because there is one configuration to compile.

Which daemon a launch dials

backend_conn.rs's startup_backend is the ladder, and it has no "not connected" rung. A non-blank --observe flag is an override and becomes the synthetic cli_observe_record, whose observe key is the credential-store name VIKE_TRADEHUB_OBSERVE_KEY, whose control key is VIKE_TRADEHUB_CONTROL_KEY, and which is armed — so on the CLI path the process-level master gate is the only gate, exactly as before the registry existed. Otherwise the registry's active record is used as itself, because that record is the only place its key names live. Otherwise the local default DEFAULT_OBSERVE_ADDR. The doc records why the app no longer exits without a flag: a viewer that opens and says "not connected" in its status bar is strictly better than one that closed on a typo, since nothing in it can place an order. config.tradehub_addr is deliberately not a rung — that is the daemon's bind address, which a client cannot dial. Backends are added and picked in Connections.

The bridge

observe_bridge.rs is the reverse bridge: each pushed WireSnapshot is republished into the GUI's arc-swap cell as a CoreSnapshot, so every DOM, Trade and portfolio panel renders the remote daemon's live state unchanged. Charts render too — the daemon ships a bounded bar tail, about 300 closed bars plus the forming candle per mounted series, which wire_to_core rebuilds into the bar cache. spawn_bridge connects inside its own thread so window startup never blocks, and re-dials on a dropped link with a two-second backoff sliced into 50 ms checks so a stop lands promptly. The status line tracks each phase and ends OBSERVING <addr> (connected), led by the daemon's identity once a frame carries one — name [LIVE] in uppercase for a live daemon, name [paper] otherwise — and the OS window title reads Vike Trader — OBSERVING <addr>.

An unknown order status renders as Rejected

map_order_status decodes the wire's rendered status string and falls back, with a warning, to the terminal Rejected for anything it does not recognise — so an unknown status can never render as live or actionable. crates/vike-app-core/src/observe_bridge.rs's module doc records that before the exhaustive round-trip test existed, a newly added status variant did exactly that, silently.

build_portfolio maps the wire's cross-venue totals but sets capital_base to 0.0, meaning unknown, because the wire carries no seed and inventing one would let the drawdown curve read as a real capital base on the observe path.

The control channel

Observing is read-only by default. observe_bridge.rs's connect_control builds a second, dedicated connection under Scope::Control only when two things hold: VIKE_TRADEHUB_CONTROL=1 — the exact string "1", read off the real process environment by tradehub_control::control_enabled — and a non-blank control key. When it connects, the GUI's order buttons drive the remote core, and the function's doc says it outright: this can place and cancel real orders on the daemon. A missing key logs a warning and the observer stays read-only. For a registry record the per-backend control gate is an additional AND, so a record file flipping control = true is never sufficient on its own.

The status strip paints the armed segment amber

main.rs's status bar renders the control summary in a loud amber to flag that this observer can drive real orders, with a red dot when the control link is down, and a latched server error is click-to-dismiss. The trade panel routes to the snapshot's own market — under observe, the remote daemon's venue and symbol — and refuses with NoRoutableMarket before the first frame arrives rather than misrouting.

tradehub_control::wire_from_command lowers a live-core command into the wire vocabulary and is deliberately partial: submit, cancel, modify, mass-cancel, flatten, market-exit, trading-state and the strategy live-params re-tune have a wire form; brackets, batches, conditionals, combos, margin changes, reconcile plumbing and shutdown yield None and are logged and dropped. The handshake underneath — an HMAC-SHA256 nonce challenge bound to the scope, so an observe key can never forge a control handshake — is on The node protocol; the daemon side is vike-tradehub.

Switching at runtime

backend_conn.rs's switch_backend replaces the whole connection and performs the backend-session clear its doc calls safety-critical: the old bridge is stopped and joined, session-owned render state is reset and an empty snapshot published, so backend A's data never paints under backend B's connection, while the local feed plane survives the switch. switching_available answers false while a local core runs, so that path is byte-identical to before switching existed.

On this page