docs: the phone app, before the first line of it - #107
Draft
pierre-rouanet wants to merge 2 commits into
Draft
pierre-rouanet wants to merge 2 commits into
pierre-rouanet wants to merge 2 commits into
Conversation
pierre-rouanet
marked this pull request as draft
August 24, 2026 13:23
`app-path-design.md` covers the robot half of the app path and says almost nothing about the client. This adds `docs/design/mobile-app.md` for the other half, and links the two. The protocol belongs in Rust: `tauri-plugin-blec` exposes the same handler to Rust as to JS, so the app can depend on `duck-ipc-proto` and `btd::framing` and drive the radio from the Rust side, which makes it unable to drift from the daemon. `duckctl` is the proof, and the caveat — depending on `btd` for one module pulls tokio, clap and tracing-subscriber with it. Bluetooth is the permanent channel, not a setup step. Reachy Mini's BLE code runs once and hands over to the LAN; ours is where the robot's interface lives, so the app needs a session layer that reconnects and re-authenticates silently. That layer has no counterpart to copy. A phone spike comes before any screen. §5.5 is the blocker and it is still a fact about CoreBluetooth on a laptop. Assisted-by: Claude:claude-opus-5
pierre-rouanet
force-pushed
the
docs-mobile-app-approach
branch
from
September 14, 2026 12:10
19f5065 to
9aec85a
Compare
… not have A short A/B on olducky, plus reading the client and the daemon Reachy Mini actually ships. iOS is not macOS. With pairing required, an iPhone puts up its pairing prompt, where CoreBluetooth on a laptop begins no SMP at all — so the hang §5.5 measured is not general to the stack. The read was not confirmed before the run was stopped, and `version read` is `debug!` against a unit set to `info`, so its absence proved nothing either. §5.5 records how far it got and what to set before resuming rather than implying more than was seen. And the reference implementation does not encrypt the link at all. Reachy Mini's characteristics carry no encryption flag; it registers a NoInputNoOutput agent and never requires a bond, which is why it works on iOS. It reached the same dead end `pairing.rs` documents — a headless robot cannot do MITM-protected pairing — and answered it by sealing the passphrase above the link layer instead. So §8.1 has three candidate fixes where it assumed one, two of which need no bond. The link stays open and `--require-pairing` stays off; the gate is unchanged. The caveat that is ours alone: the property comes from the ephemeral ECDH, not from the PIN, and their per-robot PIN trick is closed to us because our advertisement is not anonymous. Also in mobile-app.md: the vendored btleplug patch is a crash rather than a nicety and duckctl is exposed to it too, and their public no-PIN `PLAY_SOUND` is the shape §8.2's `identify` wants. Assisted-by: Claude:claude-opus-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
app-path-design.mdcovers the robot half of the app path and says almost nothing about the client. This addsdocs/design/mobile-app.mdfor the other half, and links the two.Rebased onto main and rewritten against what landed since August, then extended with what a short iPhone test and a read of the Reachy Mini source turned up.
The app stopped being four screens.
route.rsgained the account device flow, the Hub policy catalogue with an install, the skill table, the pad bindings,robot.do,system.logs, andupdate.rollback/update.select. The arguments on those arms rhyme — ten metres of radio range means whoever tapped it is looking at the robot, and the bond is PIN-checked — so BLE turned out to be the transport that best answers who is watching. The app is the robot's interface now, not a settings utility.BLE is now a choice, and still the right one.
mediadships, WebRTC is the default transport, the robot serves its own console. The original "there is no alternative" argument is dead; the §2.2 one is not — an app that needs the LAN cannot fix the setting keeping the robot off the LAN. The console needs an address, which means it needs a network, which means it needs whatever the app is for.The protocol belongs in Rust.
tauri-plugin-blecexposes the same handler to Rust as to JS, so the app can depend onduck-ipc-protoandbtd::framingand drive the radio from the Rust side — which makes it unable to drift from the daemon.duckctlis now the proof: its own crate, depending onbtdforframing, running on three desktop platforms becausebluersits behindcfg(target_os = "linux"). And the caveat — that dependency also pullstokio,clapandtracing-subscriberin for one module. Whereframinglives is a new open item, cheaper to settle before the second consumer than after.Bluetooth is the permanent channel, not a setup step. Reachy Mini's BLE code runs once and hands over to the LAN; ours is where the robot's interface lives. So the app needs a session layer that reconnects and re-authenticates silently, and that layer has no counterpart to copy.
What the iPhone run and the reference source changed
iOS is not macOS. An iPhone against olducky with
--require-pairingon puts up its pairing prompt, where CoreBluetooth on a laptop begins no SMP at all — so §5.5's hang is not general to the stack. The read was not confirmed before the run was stopped, andversion readisdebug!against a unit set toRUST_LOG=info, so its absence proved nothing either. §5.5 now records how far it got and what to set before resuming, rather than implying more than was seen.Reachy Mini does not encrypt the link at all. Its characteristics are
["write"]and["read", "notify"]— no encryption flag anywhere. It registers aNoInputNoOutputagent and never requires a bond, which is why it works on iOS. It reached the same dead endpairing.rsdocuments — a headless robot cannot do MITM-protected pairing — and answered it by sealing the passphrase above the link layer: ephemeral x25519, HKDF salted with the PIN, AES-GCM with the SSID as AAD.So §8.1 has three candidate fixes where it assumed one: fix the link layer, seal the call, or seal the session. Two of them need no bond, which is what takes encryption off the app's critical path — it still gates shipping, it no longer gates building. The link stays open and
--require-pairingstays off, deliberately and recorded.One caveat is ours alone: the property that matters comes from the ephemeral ECDH, not from the PIN, so the PIN-as-salt buys nothing at
000000. And their per-robot PIN — the serial's last five characters, printed on the robot — works because their advertisement is anonymous. Ours carries a serial-derived name, so that trick is closed to us by a choice made for a better reason.Two smaller things worth having written down: the vendored
btleplugpatch is a crash rather than a nicety (deviceplug/btleplug#397, macOS and iOS both, soduckctlis exposed too), and their public no-PINPLAY_SOUNDfrom the scan list is exactly the shape §8.2'sidentifyneeds.Three rows moved off the "what the robot owes the app" table — telling robots apart (built, with IPv4 in the advertisement), version skew (decided), and going back from a bad release (routed).
#286 fixes the
route.rscomment this turned up, which claimed an authenticated bond the just-works agent cannot produce.Nothing is built.
🤖 Generated with Claude Code