Date: September 26, 2026. Issue: #9715, part of #9704. It follows #9712 (host serve) and #9713 (Computers screens).
This record covers the live Computers service, host presence telemetry, host-observed last-seen in NIP-HOST device listing, the invitation QR code, and lifecycle signals from iOS and Android. Evidence is split into synthetic Rust checks, simulator and emulator runs, and physical-device checks. No physical-device or production-relay check ran.
coder-computers:live::Live, aComputersServiceovercoder_host::client, the portablecoder-accessdevice client,coder-reachpresence, and onecoder-linkregistry. A background task drains connector reports and ticks supervisor timers; data freshness is reported separately after each catch-up (presence, activity summaries, device list, enrollment requests). A signedrevokedrefusal or a channel the host closed as revoked blocks the supervisor and persists as revoked.live::Storekeeps the saved grants;live::FileStoreandload_or_create_keykeep them owner-only for the terminal slice.ComputersService::applicationcarries foreground and background, andqrrenders invitations locally.coder-mobile: the normal app uses the live service instead ofUnavailable. Its grants live in a separate cache directory encrypted under the device key from the platform's protected store, so erasing the chats pairing does not erase them. Newlifecyclerequest, newcomputers_qrpacket field, and a test-launch-onlyloopback_testconfig flag. The synthetic service remains for tests.coder-host: presence telemetry (CPU count, load per CPU, available memory;--no-telemetrywithholds it), last-seen recording for direct channels, and a defaulthostfeature so clients build onlyclient.coder-access:DeviceEntry::last_seen, recorded on every admitted request and throughHost::touch. NIP-HOST documents the device-listing fields.coder-connect: aqrfeature andqr_modules_prefixedfor local QR rendering without the host store.- iOS and Android glue: invitation QR drawing from the packet modules,
polling while the Computers screens show, lifecycle signals from the scene
phase and
onResume/onPause, and the--loopback-testlaunch argument orloopback_testdebug extra. - Terminal slice:
cargo run -p coder-computers --example terminal -- --live DIRruns the live service and draws the QR code as half-block text.
crates/coder-mobile/src/computers_live_tests.rs drives the mobile
application state (coder_mobile::App, the same state the iOS and Android
hosts call through the C ABI and JNI) against the coder host serve command
path (coder_host::cli::run) on the synthetic NIP-42 relay fixture, with an
in-memory task owner. Each step asserts the screen text:
- The operator initializes the host and serves it;
invite --rights allequivalent creates an invitation. - The app pastes the invitation. The notice reads
Added Computer <key>.The saved record is encrypted; it contains neither the host key nor the grant schema in clear. - First run continues; the row reads
Online on this computer. Up to date. - Background and return: the supervisor probes and the row returns to online.
- The Access screen lists this device as
Active, joined by invitation, last seen just now; no row readslast seen: unknown. - Leaving out terminals and reviews, the app creates an invitation. Its
detail reads
Grants: View sessions and tasks, Run and steer tasks.The packet's QR modules equal the local renderer's. Another device redeems it and holds exactlyobserve, operate, issued by the app's key. - That device creates a task. The Activity screen shows
Task queued; the title the other device sent does not appear. - A third device with an all-rights grant revokes the app. The row reads
Revoked: this computer removed this device's access. Add it again with a new invitation.A new app instance over the same protected state starts revoked.
The run takes about 4 seconds. A diagnostic variant that waited 150 seconds before revoking, with the Computers screen polled every 5 seconds, also passed.
cargo test -p coder-computers(23 unit tests, including the live service's route classes, error mapping, and owner-only file store; the QR renderer; and lifecycle on the fixture).cargo test -p coder-mobile(31 tests, including the acceptance run and the normal app's live-service validation; the device-run fixture is ignored by default).cargo test -p coder-host(8 unit tests including telemetry, the end-to-end scenario now asserting telemetry and placement, and 3 serve tests).cargo test -p coder-access(14 + 2 CLI; the listing test asserts host-observed last-seen andtouchthrottling).cargo test -p coder-connect --lib qr.- Clippy with
-D warningsforcoder-access,coder-connect,coder-host,coder-computers, andcoder-mobile(all targets), pluscoder-hostandcoder-computerswith--no-default-features. cargo fmt --checkfor the same crates.
The workspace gate did not run.
Both runs used the ignored fixture serve_a_host_for_a_device_run, which
serves a host on the Mac, writes the invitation, and plays the other
devices: after the app enrolls it creates a task from a narrowed device,
and after a delay it revokes the app from an administrator device.
To reproduce, start the fixture, then pass invitation.txt to the platform
test: TEST_RUNNER_CODER_LIVE_INVITATION for xcodebuild test -only-testing:CoderUITests/ComputersLiveUITests, or the
coderLiveInvitation instrumentation argument for Android after
adb reverse tcp:PORT tcp:PORT for the ports in relay-port.txt and
host-port.txt:
CODER_COMPUTERS_FIXTURE_DIR=/private/tmp/computers-run \
cargo test -p coder-mobile --lib serve_a_host_for_a_device_run -- --ignored --nocaptureThe loopback_test flag admits only ws:// loopback relays and exists for
test launches: the iOS --loopback-test launch argument, which only a
developer or test launch passes, as with --synthetic, and the Android
loopback_test extra, which only debug builds honor.
- iOS Simulator (iPhone 17 Pro, iOS 26.5, debug Rust library):
ComputersLiveUITestslaunched with--synthetic --loopback-test(the synthetic world and reader, the live Computers service) and passed in about 101 seconds: pasted enrollment, online, last seen, a narrowed invitation with its QR code drawn natively,Task queuedactivity, and the revoked status with its reason.ComputersUITestsover the fixture passed in the same run. - Android emulator (API 35, arm64,
adb reversefor the relay and host ports):computersLiveEnrollInviteActivityAndRevocationpassed in about 96 seconds with the same steps; the QR view was asserted present, and the Access screenshot shows the listing above it.computersScreensShowStatusesAccessAndPastedInvitationpassed in the same run.
The first simulator run found a real defect: the iOS panel's 5-second
Timer.publish was recreated on every world redraw and never fired, so the
Computers screen stopped refreshing on its own. Polling now runs in a
.task tied to the view's identity.
Screenshots: online, last seen, invitation QR code, activity, revoked, Android access, and Android revoked.
None ran. Pending owner steps:
- Run
coder host serveon a computer with a production relay, create an invitation withcoder host invite, and scan its QR code with the iPhone and Android apps. - Confirm the row reaches Online over the relay or a LAN hint (start
the host with
--listenon a LAN address and--allow-nonloopback), then background the app for more than five minutes and confirm it reconnects. - On the Access screen, create an invitation and scan its QR code from a second device.
- Revoke a device from another device and confirm the revoked status.
- The live service does not start SSH hosts, read the owner directory, or approve reverse enrollment in a tested flow; approval and denial are implemented but only unit-checked through the controller.
- A host's label is
Computerand a key prefix. - A relay-route client detects revocation on its next catch-up or operation (every 30 seconds by default, sooner while a screen polls), not instantly.
- CPU use is the load average per CPU, not measured utilization.
- The
coderbinary crate, a direct consumer ofcoder-host, was checked (cargo check -p coder --bins --test host_serve --test host_cli) but its tests did not run; its host scenario shares the updatedscenario.rs.