Host-side dev server for whisker run.
Owns the long-running dev loop: file watch, cargo rebuild, install
to the device, subsecond patch construction, and WebSocket push.
whisker-cli's run subcommand is a thin wrapper that builds a
[Config] and calls [DevServer::run] — every piece of
UX-shaped logic lives here so future host shells (an editor
plugin, a notebook, a remote-controlled CI build) can reuse it.
Architecture
Constructed once via [Config], the dev server spins up six
cooperating pieces:
builder— translates [Config] into awhisker-buildinvocation (cargo + per-platform packaging) and runs it. HonoursRUSTC_WORKSPACE_WRAPPER+ linker shim env so the fat build doubles as a capture pass for Tier 1.installer— for the cold-rebuild path: shells out toadb install/simctl install + launch. Identity (bundle id, applicationId, scheme, …) comes in flat via [AndroidParams] / [IosParams]; the cli resolves these from the user'swhisker.rs::configure(&mut Config). This crate never depends onwhisker-config.watcher—notify-based, debounced, classifies events intoChangeKind::{RustCode, CargoToml, Other}.server—axumWebSocket endpoint atws://<bind>/whisker-dev. Devices dial in, send ahellocarrying theirsubsecond::aslr_reference(), then receive patch envelopes.hotpatch— Tier 1 implementation. Builds a thin.ofrom the changed user crate via captured rustc args, links it into a patch dylib with a stub-object of host-symbol jumps, ships the resultingsubsecond_types::JumpTableto connected clients.lib.rs::run— the orchestrator: file event →decide_action(Tier 1 patch vs Tier 2 rebuild) → builder/hotpatch/sender.
Layering
Stays manifest-agnostic on purpose. The cli does the
whisker.rs → Config translation; this crate accepts only
flat String / PathBuf fields. That keeps the dev-server
reusable from any host shell that can produce the same flat
Config (the cli is one; an editor plugin could be another).