Skip to content

fix(provision): waiting feedback, back-out of hung RPCs, and device-list responsiveness#29456

Open
chrisnojima wants to merge 19 commits into
masterfrom
nojima/HOTPOT-provision-fixes
Open

fix(provision): waiting feedback, back-out of hung RPCs, and device-list responsiveness#29456
chrisnojima wants to merge 19 commits into
masterfrom
nojima/HOTPOT-provision-fixes

Conversation

@chrisnojima

@chrisnojima chrisnojima commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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() and reset() now cancel non-dangling sessions so listener promises reject and the provision flow shows its error screen with retry. Session.cancel also releases the waiting count when the server owes a response and guards against a late response double-firing the callback. New unit tests in engine/session.test.tsx.

Engine: client-side cancel for listener RPCs. *RpcListener gains an optional onSessionCreated(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. beforeRemove pause is gated to real POP/GO_BACK actions — router stack teardown on login success must not cancel the RPC as it resolves. DisplaySecretExchanged waiting increments are now balanced per attempt (previously leaked on restart). Tests in provision/flow.test.tsx (10 cases).

Provision: waiting overlay. New ProvisionWaitingOverlay on every provision screen (via SignupScreen waitingOverlay prop; 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 in provision/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

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.
@chrisnojima chrisnojima changed the title fix(provision): device rows untappable on iOS in authorize list fix: provision device list unresponsive (engine session orphans + list heights) Jul 21, 2026
@chrisnojima chrisnojima changed the title fix: provision device list unresponsive (engine session orphans + list heights) fix(provision): waiting feedback, back-out of hung RPCs, and device-list responsiveness Jul 22, 2026
@chrisnojima
chrisnojima requested a review from Copilot July 22, 2026 02:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 thread shared/provision/flow.tsx
Comment on lines +380 to +385
} finally {
cancelAttempt = undefined
for (let i = 0; i < exchangedIncrements; ++i) {
useWaitingState.getState().dispatch.decrement(waitingKeyProvision)
}
}
Comment thread shared/provision/flow.tsx Outdated
Comment thread shared/provision/flow.tsx
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 thread shared/provision/flow.tsx
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants