fix(sync): put the notes-holding device in the approver role so the approval prompt appears#9
Merged
Conversation
…l prompt appears The server inferred 'new device needing a transfer' from cursor === 0, but the device that generates the sync code owns all the notes yet has cursor 0 (it pushes no ops; new devices get data via transfer). So the notes-holder was misclassified as a requester and skipped by the #8 replay, while the joiner was also flagged new — both sat on 'Choose a source device' and the approve/decline prompt appeared nowhere. Clients now send hasData (true when local notes exist) on join; the server only treats a device as new when !hasData && cursor === 0 && otherDevices exist. The notes-holder is now always the source/approver and reliably receives the approval prompt.
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
Even after #8, the device-transfer approve / decline prompt still appears on no device when adding a new device to a sync chain.
Root cause — role misclassification
The server decided whether a joining device "needs a transfer" with (server/src/index.ts):
cursoronly advances when a device pushes or acks server ops. But notes are not pushed as ops to new devices — a new device receives them through the peer transfer flow. So the device that generated the sync code and owns all the notes normally hascursor === 0(it hasn't pushed any ops yet).Consequently, as soon as a second device joins:
isNewDevice = true→ its UI shows "Choose a source device" and it asks another device for a transfer. It is also skipped by the pending-transfer replay added in fix(sync): show device-approval prompt after reconnect (replay pending transfers) #8 (which is gated on!isNewDevice).cursor === 0with others present → alsoisNewDevice = true.Both devices sit in the "pick a source" picker waiting for each other. The device that should be the approver is never placed in the approver role, so the approve/decline prompt is never produced — it appears nowhere. This is exactly the reported symptom.
The server can't inspect which device holds notes because the data is end-to-end encrypted. It needs the client to tell it.
Fix
The client now reports whether it already holds local notes via a
hasDataflag on thejoinmessage, and the server only treats a device as new when it has no data:Effect:
This breaks the both-devices-are-new deadlock.
Files changed
src/utils/sync.ts— addbuildJoinPayload()that includeshasData: (local notes > 0); use it at bothjoinsend sites (initial connect + token-refresh re-join).server/src/index.ts— readmsg.hasDataand factor it into theisNewDevicedecision; update protocol doc comment.Where the prompt appears (for the user)
On the device that already has your notes (the one you generated the sync code on / your existing device), the approve/decline prompt shows in two places:
Both devices must be open at the same time for the initial copy (the relay is ephemeral and only bridges two live devices).
Verification
join(hasData) → serverisNewDevice→ new device picker (needsTransfer) →request-transfer-from→ servertransfer-requested→ clientcase 'transfer-requested'setspendingTransfer→ global modal inApp.tsx+ card inSettingsPanel.tsx. The notes-holder is now correctly the approver.tsc/ build / lint — frontend deps aren't installed in the sandbox and package installs are blocked (registry 403); the server has no installed toolchain either. Changes are additive and use only existing in-scope symbols (callbacks.getNotes,msg.hasData).Manual test plan