fix(sync): classify transfer-need by explicit join intent + auto-request source so approval prompt appears#11
Merged
Conversation
…st source After #10 the new device connected but immediately showed 'Connected' and never asked to sync, so the old device got no approval prompt. Cause: 'needsTransfer' was inferred from hasData (note count), and a fresh device usually has a seed note -> it was wrongly classified as already-established. Replace the guess with explicit, persisted per-chain intent: a device is 'initialized' for a room when it generated the code or finished receiving a transfer (new notes-sync-initialized-room key). The client now declares needsTransfer on join; the server trusts it (isNewDevice = needsTransfer && others>0). On completing a transfer the device marks itself initialized, so it stops asking and reconnects as a full member (fixes the connecting-again loop). Also auto-request a transfer from an online device on welcome/device-joined, so the approve/deny prompt reaches the existing device without manually picking a source; the picker stays as an override.
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.
Symptom (after #10)
The new device now connects, but immediately shows "Connected" + the device list and never asks to sync, so the old device gets no approve/deny prompt. On refresh the new device briefly shows "Connecting…" again.
Root cause — "needs a transfer?" was inferred, not declared
Whether a joining device should pull the existing notes was being guessed from proxies that don't actually mean "this device is a member of the chain":
hasData(does the device hold any local notes). But a fresh device usually does have a note (a seed/empty note, or something typed before joining), sohasDatawastrue→ the server marked it already-established → it never requested a transfer → the old device never got a prompt. That's the "immediately Connected" behavior.cursor === 0proxy was wrong in the opposite direction (the origin has cursor 0).The only reliable signal is explicit intent: did this device generate the code (it's the origin/source) or enter a code to join (it must bootstrap from an existing device)? That's a per-chain fact the device knows and must remember.
Fix — persisted per-chain intent + auto-request
notes-sync-initialized-roomlocalStorage key records the chain (roomId) this device has bootstrapped. A device is "initialized" when it generated the code or finished receiving a transfer.joinnow sendsneedsTransfer = !initializedForThisRoominstead ofhasData.enableSync(..., { initialized: true })on generate;{ initialized: false }on entering a code.isNewDevice = clientNeedsTransfer && otherDevices.length > 0(replacing thehasData/cursorguess). This drives the welcomeneedsTransfer, the catch-up-ops gate, and the fix(sync): show device-approval prompt after reconnect (replay pending transfers) #8 approval-replay gate.welcomeand when a device comes online), so the approve/deny prompt appears on the existing device without the user manually picking a source. The "Choose a source device" picker remains as an override for multi-device setups. Self-guarded so it never fires while a transfer is pending/in progress.Files changed
src/utils/sync.ts— per-chain initialized flag + helpers;buildJoinPayloadsendsneedsTransfer; welcome uses client-authoritativeneedsTransfer;maybeAutoRequestTransfer()on welcome + device-joined; mark initialized on transfer completion;enableSyncintent param; clear flag on disable.src/components/SettingsPanel.tsx— pass{ initialized: true }on generate,{ initialized: false }on join.server/src/index.ts— classify role frommsg.needsTransfer; protocol doc comment.Where the prompt appears
On the device that generated the code / holds the notes: a full-screen "Allow sync to ?" modal (Deny/Approve) and a "Transfer Request" card under Settings → Sync. With auto-request, you no longer need to tap a source on the new device — just keep both apps open.
Verification
enableSync({initialized:false})→ joinneedsTransfer:true→ serverisNewDevice:true→ welcome → auto-request → old device shows approval → approve → chunks → new device marks initialized → subsequent joins areneedsTransfer:false("Connected").tsc/build/lint — sandbox has no installed toolchain for either workspace and installs are blocked (registry 403). Changes use only existing in-scope symbols;requestTransferFromDeviceis a hoisted function declaration referenced bymaybeAutoRequestTransfer.Manual test plan