fix(provision): waiting feedback, back-out of hung RPCs, and device-list responsiveness#29456
Open
chrisnojima wants to merge 19 commits into
Open
fix(provision): waiting feedback, back-out of hung RPCs, and device-list responsiveness#29456chrisnojima wants to merge 19 commits into
chrisnojima wants to merge 19 commits into
Conversation
The screen declared fixed item heights (header 80) but the header text wraps taller on phones. With getFixedItemSize LegendList skips measuring, so containers overlap; the new-arch native container path applies no zIndex, letting the header container sit above device rows and swallow taps. iOS does not clip overflow so only iOS is affected. Switch to trueVariable so the list measures real heights.
When the transport drops, the service forgets every in-flight RPC but the GUI's sessions hung forever waiting on answers that would never come. A provision screen left up across a service restart became a zombie: its prompt response went to a dead session, so clicks silently did nothing. reset() had the same problem, clearing the session map without rejecting the orphaned promises. Cancel non-dangling sessions in both paths so listener promises reject and flows can react (provision now shows its error screen with retry). Session.cancel also releases the waiting count when the server owes a response and clears the start callback so a late response cannot fire it twice.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves the provisioning UX and engine resilience when backend RPCs are slow, hung, or disrupted (e.g., service restart), by adding explicit client-side cancellation hooks, a delayed waiting overlay, and pause/resume behavior for back-outs so the flow doesn’t appear dead or get stuck indefinitely.
Changes:
- Engine: cancel outstanding non-dangling sessions on disconnect/reset; add client-side cancel for listener RPCs via
onSessionCreated(cancel). - Provision UI/flow: add delayed full-screen waiting overlay; implement pause/resume-on-back-out; improve device list selection feedback and tap handling.
- Tests: add unit/flow tests covering session cancel, provision pause/resume, and waiting overlay behavior.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| shared/tsconfig.native.json | Includes new provision waiting overlay test in native TS config. |
| shared/signup/common.tsx | Adds optional waitingOverlay support to SignupScreen and mounts the overlay. |
| shared/provision/waiting-overlay.tsx | Implements delayed full-screen waiting overlay with cancel + back-out pause behavior. |
| shared/provision/waiting-overlay.test.tsx | Adds overlay timing/cancel/back-out tests. |
| shared/provision/username-or-email.tsx | Enables waiting overlay on this provision screen. |
| shared/provision/set-public-name.tsx | Enables waiting overlay on this provision screen. |
| shared/provision/select-other-device.tsx | Adds row-level spinner/disable feedback and switches list height handling to true-variable measurement. |
| shared/provision/select-other-device-connected.tsx | Tracks selected device name to drive row spinner and clears it when waiting ends. |
| shared/provision/password.tsx | Enables waiting overlay on this provision screen. |
| shared/provision/paper-key.tsx | Enables waiting overlay on this provision screen. |
| shared/provision/flow.tsx | Adds pause slot, attempt cancellation, and balances waiting increments per attempt. |
| shared/provision/flow.test.tsx | Adds pause/resume/cancel test coverage for the provision flow. |
| shared/provision/code-page/container.tsx | Mounts waiting overlay directly on the code page container. |
| shared/engine/session.tsx | Improves session cancel behavior (waiting release + double-callback guard). |
| shared/engine/session.test.tsx | Adds tests for cancel/waiting-release/double-callback protection. |
| shared/engine/listener.tsx | Adds onSessionCreated(cancel) plumbing for listener RPCs and captures session ID. |
| shared/engine/index.tsx | Cancels outstanding sessions on disconnect/reset and adds cancelSession. |
| shared/constants/rpc/rpc-gen.tsx | Adds onSessionCreated to generated listener arg types/plumbing. |
| shared/constants/rpc/rpc-chat-gen.tsx | Same onSessionCreated listener arg addition for chat RPC gen. |
| protocol/bin/generate-ts.ts | Updates TS generation template to emit onSessionCreated listener arg. |
Comment on lines
+380
to
+385
| } finally { | ||
| cancelAttempt = undefined | ||
| for (let i = 0; i < exchangedIncrements; ++i) { | ||
| useWaitingState.getState().dispatch.decrement(waitingKeyProvision) | ||
| } | ||
| } |
Comment on lines
+560
to
+563
| cancelAttempt = undefined | ||
| for (let i = 0; i < exchangedIncrements; ++i) { | ||
| useWaitingState.getState().dispatch.decrement(waitingKeyProvision) | ||
| } |
Comment on lines
45
to
46
| return new Promise((resolve, reject) => { | ||
| const {method, params, waitingKey} = p |
Comment on lines
+111
to
114
| const sessionID = getEngine()._rpcOutgoing({ | ||
| callback: (error?: RPCError, params?: unknown) => { | ||
| if (printOutstandingRPCs) { | ||
| clearInterval(outstandingIntervalID) |
Comment on lines
+160
to
163
| }) | ||
|
|
||
| // Idle handlers run when the user submits a step the RPC isn't currently waiting on: they went | ||
| // back to an earlier screen. Record the new answer and restart. |
Measure real text width (canvas, util/measure-text.desktop) instead of
guessing: keep "Hi {username}!" only when it fits 82px, else bare name
stepped down 12->10px. Await preloadFonts before first render so
measurements never run against fallback fonts.
Account picker was a dropdown (desktop) / native picker modal (mobile); now an always-visible list capped at 5.5 rows that shrinks on short screens (2-row floor, inner scroll only — logged-out screen never scrolls). Clicking a signed-in row logs in immediately with a per-row spinner, so the Log in button renders only when a password is needed. Mobile Create account moves to a small top-right button; desktop card height now grows past UserCard's fixed 430 to fit the list.
Clearing waitingKeyProvision in the cancel handlers zeroed the count and then the listener's final callback plus the per-attempt exchanged decrements pushed it negative, swallowing future waits. Cancel now relies on the natural bookkeeping: cancelSession synchronously rejects the listener's start callback, so every increment gets its matching decrement. The listener also tracks whether it believes the server is working and dedupes waiting dispatches through that flag, so a client-side cancel or engine reset arriving while a GUI prompt is up can't decrement below zero. DisplaySecretExchanged handlers are dispatched via setTimeout and could land after the attempt's finally already ran its decrements; an attemptEnded flag drops those so they can't leave a stuck count.
The team-page no-flash change made every no-name mobile Avatar render a blank circle, which regressed signup's empty avatar. Team callsites now pass isTeam explicitly, so the no-name branch can show the user placeholder for everyone else and keep the blank shape only while a team's meta is still loading.
…bled back Remove codePage's iOS unstable_headerLeftItems override (native back arrow alone; Cancel was redundant navigateUp). Patch @react-navigation/native-stack so non-empty unstable_headerLeftItems always hides the native back button, making doubled back/cancel impossible on any screen.
Screens pushed by a provision prompt mounted inside the engine's 500ms waiting-dispatch throttle window, so they rendered with waiting=true. List rows only re-render on data/extraData change, so the device row's onClick kept the stale waiting=true closure and every tap was silently ignored; the waiting overlay also blinked on each page entry. - engine: flush queued waiting changes immediately on decrement - select-other-device: read waiting at tap time; pass extraData so rows pick up spinner/disabled state
Mobile: the modal group's injected header Cancel is the only cancel; ConfirmButtons no longer renders its body cancel there. Desktop keeps the body cancel and no header Cancel. - deviceRevoke was the last confirm screen pushed instead of modal, hand-rolling a header Cancel that doubled the body one on desktop; move it to newModalRoutes (wide) and clearModals before navUpToScreen so post-revoke lands on the device list - drop teams emoji routes' headerLeft Cancels (modal group injects the identical one) - jest babel config stops inlining platform globals so tests can flip isMobile at runtime; Metro keeps the plugin, Vite never loads this config
Box2 without fullWidth/fullHeight gets align-self:center on desktop, which overrides the vertical stretch of an absolutely-positioned inset:0 child and collapses it to a content-height band. Add fullWidth/fullHeight to the provision waiting overlay and LoadingOverlay.
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.
Fixes for the provision flow feeling dead when the backend is slow, hung, or restarting.
Engine: cancel outstanding sessions on disconnect/reset. The 'Authorize this device' screen is backed by a live loginLogin RPC holding a chooseDevice prompt. When the transport drops (service restart, sleep, dev reload), the service forgets the RPC but the GUI session hung forever — row clicks answered a dead session and silently did nothing.
_onDisconnect()andreset()now cancel non-dangling sessions so listener promises reject and the provision flow shows its error screen with retry.Session.cancelalso releases the waiting count when the server owes a response and guards against a late response double-firing the callback. New unit tests inengine/session.test.tsx.Engine: client-side cancel for listener RPCs.
*RpcListenergains an optionalonSessionCreated(cancel)hook (codegen template updated) so callers can abort an in-flight listener session client-side.Provision: pause/resume on back-out. Backing out of a hung attempt (back button, gesture, or overlay Cancel) parks the flow: the in-flight attempt is cancelled, late prompts are rejected instead of yanking the user forward, and resubmitting any step restarts the login RPC, silently replaying previously recorded answers up to that step. Pause resets to idle handlers so resubmitting the step that was pending also resumes.
beforeRemovepause is gated to real POP/GO_BACK actions — router stack teardown on login success must not cancel the RPC as it resolves.DisplaySecretExchangedwaiting increments are now balanced per attempt (previously leaked on restart). Tests inprovision/flow.test.tsx(10 cases).Provision: waiting overlay. New
ProvisionWaitingOverlayon every provision screen (viaSignupScreen waitingOverlayprop; direct mount on code-page): after 300ms of RPC wait it dims the screen with a spinner and blocks input; after 10s it adds "Still trying to reach the server…" plus a Cancel button (pause + navigate up). Fast responses never flash. Recover-password reuses the device-list screen but opts out. Tests inprovision/waiting-overlay.test.tsx(6 cases).Provision: device list feedback. Tapped row shows an immediate spinner and rows disable while the selection is in flight; selection clears when waiting ends so a later step's waiting can't resurrect a stale spinner. Also: measure real item heights (
trueVariable) since the wrapping header made fixed heights overlap and eat taps on iOS.🤖 Generated with Claude Code