fix: don't flash the empty-state screen while the agent is still scanning#213
Conversation
The scanning state rendered the full empty-state screen (search icon, headline, Add-Device CTA, pairing hints) with only the headline swapped — still a "no devices" flash for a user whose devices are about to appear. While the agent's first enumeration is in flight the device set is unknown, not empty: show the same quiet spinner frame as the pre-connection view (caption "Scanning for devices…"), and reserve the empty state for a completed scan that genuinely found nothing.
The steady 2 s poll is tuned for quiet background refresh, but at startup it left the window on its loading frame for up to a full period after the agent already knew the devices. Poll every 250 ms until a snapshot arrives with inventory_ready, then drop to the steady cadence; fall back to fast on a lost connection so an agent restart (binary-update exec, crash) re-converges just as quickly.
…first tarpc generates one request enum from the trait and bincode encodes the variant index, so inserting a method shifts every later variant and breaks even the version handshake across a skew. Document the append-only rule and why there is deliberately no minor version: GUI and agent ship in one bundle and the agent re-execs on binary replacement, so strict equality plus a clean refusal is the whole contract.
Greptile SummaryThis PR fixes the empty-state flash during startup by separating "scan in progress" (
Confidence Score: 4/5The UI and state-management changes are correct and safe to merge. The fast-polling logic in ipc_client.rs has a gap: a connection drop caught by the command arm doesn't revert the interval to fast cadence, so agent restarts that happen mid-command recover more slowly than intended. The cmd arm of the select! loop sets client = None on handle() error but does not reset steady or the interval, leaving recovery at the 2 s steady cadence instead of 250 ms. The poll arm correctly does this reset, and the UI and loading_body refactoring are clean, but the inconsistency in the command path contradicts the stated reconnect-speed guarantee. crates/openlogi-gui/src/ipc_client.rs — the cmd arm's error path is missing the steady/interval reset that the poll arm has Important Files Changed
Sequence DiagramsequenceDiagram
participant GUI as GUI (app.rs)
participant IPC as ipc_client.rs
participant Agent
Note over GUI: AppState.scanning = true
GUI->>GUI: connecting_view (loading_body)
loop Every 250 ms (STARTUP_POLL_PERIOD)
IPC->>Agent: ensure() + inventory() + status()
alt agent not up yet
Agent-->>IPC: Err (socket not bound)
IPC-->>IPC: Ok(None) — keep fast cadence
else "agent up, inventory_ready = false"
Agent-->>IPC: Ok(Some(false))
IPC-->>GUI: "PollUpdate (scanning=true)"
GUI->>GUI: device_scanning_state (loading_body)
else "agent up, inventory_ready = true"
Agent-->>IPC: Ok(Some(true))
IPC-->>IPC: "steady=true, switch to poll_period (2s)"
IPC-->>GUI: "PollUpdate (scanning=false)"
alt devices present
GUI->>GUI: device_gallery
else no devices
GUI->>GUI: device_empty_state
end
end
end
Note over IPC: On poll Err() (dropped connection)
IPC-->>IPC: "steady=false, revert to 250 ms"
|
A fresh tokio interval ticks immediately, so switching to the steady period fired a redundant poll back-to-back with the one that just confirmed readiness; start the steady interval one period out via interval_at.
…t handling Several poll-loop fixes from the post-merge review of #212/#213: - poll() now fetches status BEFORE inventory. The other way around, the agent's first enumeration could land between the two RPCs and pair an empty pre-enumeration inventory with ready=true — re-creating the 'No devices connected' flash the fast poll exists to prevent, stretched to a full steady period by the cadence latch. The inverse pairing is benign (devices render regardless of the scanning state). - Cadence policy extracted into a unit-tested Pacing type, and the command-branch disconnect now re-arms the fast cadence too — it previously only reset client, contradicting the 'back to fast whenever the connection is lost' contract, so a disconnect detected by an in-flight command re-converged at 2 s instead of 250 ms. - The fast phase is capped (15 s without readiness → steady): an agent that never becomes ready, or a protocol mismatch, no longer holds the loop (and its connect+warn round) at 4 Hz for the GUI's lifetime. - New GuiUpdate signals: Unreachable (no snapshot 15 s into an outage) and OutdatedGui (agent speaks a newer protocol) now reach AppState, wiring up the static frames from the AgentLink commit — no more eternal connecting spinner, and no frozen live-looking UI after an in-place app update. - A pairing session interrupted by an agent restart synthesizes a Failed event (the agent's terminal-event guarantee dies with the process), so the Add Device window no longer sits in 'Searching…' until manually cancelled. - Both intervals use MissedTickBehavior::Delay (a stalled poll no longer bursts its missed ticks), the spawn gate skips the tick that detected a drop (don't race the agent's self-exec for the singleton lock), identical snapshots skip the full-window refresh, and the module docs drop the stale 'launchd KeepAlive reconnects us' claim.
Resolve the IPC poll conflict against the startup-states rework and adapt the diagnostics adapter to master: - call store_agent_snapshot before the status snapshot moves into AgentLink::Ready; the inventory merge keeps the InventoryHealth gate (AprilNEA#213/AprilNEA#215/AprilNEA#220) - read accessibility from the retained AgentStatus; the AppState.accessibility_granted field no longer exists - map DeviceRoute::Unifying (AprilNEA#181) to a new ConnectionKind::UnifyingReceiver
Follow-up to #212 — the startup sequence still flashed the device empty-state screen for users with paired devices.
Problem
While the agent's first enumeration is in flight (
inventory_ready == false), the home body rendered the full empty-state screen — search icon, headline, Add-Device CTA, pairing hints — with only the headline swapped to "Scanning for devices…". For a user whose devices are about to appear that is still a "no devices" flash. And since the GUI polls on a 2 s timer, the gallery could lag a full period behind the agent actually knowing the devices.Fix
scanningis set, the home body shows the same quiet spinner frame as the pre-connection view (caption "Scanning for devices…", string already in all locales). The empty-state screen with its Add-Device CTA is reserved for a completed scan that genuinely found nothing. The two loading phases share one layout (loading_body), so startup renders as a single continuous frame whose caption changes, resolving into the gallery.inventory_readysnapshot arrives, then drops to the steady 2 s cadence; it falls back to fast when the connection is lost so an agent restart (binary-update exec, crash) re-converges just as quickly. A status+inventory round every 250 ms is noise for the agent.protocol_versionstays first, new methods append-only — and why the protocol deliberately has no minor version (single-bundle shipping + agent self-exec make strict equality the whole contract).Startup now (cold, devices paired)
connecting spinner (≈agent boot) → scanning spinner (≈one enumeration, sub-second with fast polling) → gallery. No empty-state flash at any point.
Verification
cargo test, full-workspace clippy pass; the scanning frame reuses the connecting-frame layout verified in #212.