fix(sync): show device-approval prompt after reconnect (replay pending transfers)#8
Merged
Merged
Conversation
…es reconnect New-device join approvals were delivered fire-and-forget via sendToDevice and lost whenever the approver had no live WebSocket at that instant (e.g. a mobile PWA whose socket drops when backgrounded). The join handler never re-delivered them, so the approval prompt never appeared — 'not even when they return'. getPendingTransfers() existed for exactly this but was dead code. Wire it into the join handler: when an established device (re)connects, replay any still-pending transfer requests in its room whose requester is currently online, so it sees the approval prompt. New (needsTransfer) devices are skipped since they have no notes to send.
This was referenced Jun 25, 2026
Merged
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.
Problem
When a new device joins a sync chain, it asks an existing device to send it the notes (peer transfer with approval). The other devices were never showing the approval prompt — not while they were active, and not when they reopened the app later.
Root cause
The approval request was delivered fire-and-forget:
request-transfer-from→ the server creates apendingtransfer row and callssendToDevice(target, { type: 'transfer-requested' }).sendToDeviceonly delivers if the target has a live WebSocket at that exact moment; its boolean return is ignored and a missed message is lost.join/welcomehandler never replays pending transfer requests when a device (re)connects.On mobile PWAs the WebSocket drops whenever the app is backgrounded or the screen locks, so an "active" phone usually isn't holding a live socket — the request is dropped, and because nothing re-delivers it, reopening the app ("returning") shows nothing either.
db.tsalready hadgetPendingTransfers()written for this case, but it was dead code — never imported or called.Fix
Wire
getPendingTransfers()into thejoinhandler. When an established device (re)connects, the server replays any still-pendingtransfer requests in its room, so the approval prompt appears as soon as the device has a live connection.Guards:
status === 'pending'transfers (already-approved/in-flight ones are skipped).needsTransfer) devices are not asked to approve — they have no notes to send.The existing immediate
sendToDevicepath is unchanged; this purely adds durable re-delivery on connect. Replaying a request the approver already saw is harmless (sametransferId; the server still validatesstatus === 'pending'on approve).Files changed
server/src/index.ts— importgetPendingTransfers; replay pending transfer requests in thejoinhandler.Verification
db.ts(getPendingTransfersreturns rows withstatus IN ('pending','approved','in_progress');TransferRecord.statusincludes'pending') and the client handler (case 'transfer-requested'setspendingTransfer, rendered by the global modal inApp.tsxand the panel card inSettingsPanel.tsx).tsc/build/lint — the server's dependencies aren't installed in the sandbox and package installs are blocked (registry 403). The change is additive and uses only already-imported/in-scope symbols (getDevice,room,WebSocket).Manual test plan
pending).