Skip to content

fix(remote-connect): show the Relay invitation on the first click - #3837

Merged
bobleer merged 1 commit into
openBitFun:mainfrom
bobleer:bob/fix-relay-invitation-first-click
Nov 9, 2026
Merged

bobleer merged 1 commit into
openBitFun:mainfrom
bobleer:bob/fix-relay-invitation-first-click

Conversation

@bobleer

@bobleer bobleer commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

After an app restart, the first click on "show connection code" for the official Relay left the dialog showing "Connected" with a spinning button and no QR code. Closing the dialog or switching views and clicking again worked.

The trigger is the /relay?v=<version> endpoint from #3808, through a string comparison rather than an HTTP redirect. Restoring a session normalizes the stored endpoint to its base (.../relay), while prepare_relay returns .../relay?v=1.0.9, so remote_connect_start always saw a different Relay and ran a full account login. That login emitted account://login-state, and the dialog's listener cleared pendingOwnerRef for the start still in flight. handleConnect then discarded the invitation and skipped setLoading(false). The login had already stored the versioned URL, so the next click matched and worked.

Changes:

  • Endpoint identity: remote_connect_start and RemoteConnectService::start compare endpoints by base. The invitation link is built from the prepared endpoint, so it keeps ?v=.
  • Login events: the dialog only invalidates an invitation that existed before the event. A start in flight issues its result for the new account context.
  • Loading state: it now follows the operation generation, so a released owner cannot leave the button spinning.
  • Copy: the invitation button now reads "Hide invitation" (zh-CN 收起邀请, zh-TW 收起邀請), since it collapses the QR code and leaves an established connection running.

Type and Areas

Type: regression fix / UI copy

Areas: desktop/Tauri, Rust core (remote connect), web UI, locales

Motivation / Impact

The official Relay QR code appears on the first click. Showing it no longer signs the desktop in again after each restart, which also rotated the account token and dropped device routing, disconnecting attached controllers.

Remote scenarios:

  • Remote control (official and LAN Relay): this is the changed path. Covered by local fixtures only (mocked dialog boundary, service-level tests, and the loopback dual-Relay test).
  • Peer Device Mode: the account and device protocol is unchanged, and it benefits from routing no longer being dropped. Not exercised live.
  • Remote workspace / Detached Dispatch: not affected.

Upgrade compatibility: no persisted shapes change. A session stored on the legacy /v/1.0.2 alias still migrates through one login, as before, and the dialog now handles that login without losing the invitation.

Verification

Run on the previous main base; this branch adds no conflicting changes from upstream (the files touched here did not change).

  • pnpm --dir src/web-ui run test:run src/app/components/RemoteConnectDialog/RemoteConnectDialog.status.test.tsx src/app/components/RemoteConnectDialog/RemoteConnectDialog.contract.test.ts src/infrastructure/remote-connect: 61 passed. The new login-event case failed before the fix with the reported state (connected, still connecting, no invitation).
  • cargo test --locked -p openbitfun-core --no-default-features --features remote-connect --lib service::remote_connect::host_lifecycle_tests: 9 passed.
  • cargo test --locked -p openbitfun-desktop --lib api::remote_connect_api: 21 passed, 1 ignored (performance experiment).
  • pnpm run type-check:web and pnpm run i18n:audit: passed.

Not verified: the native app against the production Relay.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable.

The first "show connection code" after an app restart left the dialog
spinning with no QR code until it was closed and reopened.

Since the official endpoint moved to `/relay?v=<version>`, a restored
session holds the endpoint base while the prepared endpoint also carries
the version, so the exact string comparison always reported a different
Relay. That forced a full account login, which rotated the token, dropped
device routing, and emitted `account://login-state`. The dialog's listener
then released the in-flight start, so its invitation was discarded and the
loading state was never reset.

- Compare endpoints by base when deciding whether to sign in, and when
  checking the prepared endpoint before issuing an invitation. The
  invitation link is built from the prepared endpoint, so it keeps `?v=`.
- A login event now only invalidates an invitation that existed before the
  event; a start in flight issues its result for the new account context.
- Tie the loading state to the operation so it cannot stick.
- Relabel the invitation button "Hide invitation": it collapses the QR
  code and leaves an established connection running.
@bobleer
bobleer merged commit 071bfd4 into openBitFun:main Nov 9, 2026
12 checks passed
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.

1 participant