Repository navigation
Conversation
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
6 of 14 tasks
…removal until it's done
This was referenced Nov 7, 2026
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.
Summary
A restore with the recovery words now asks which of the group's devices the user still has, with none ticked. It signs out the rest on the first sync, and seals nothing before that. This follows flow-sync #96,
spec/protocol.mdRecovery.linking/Recovery.kt: the restore is split into a preview that writes nothing (roster, current key, device names through the history chain) and the commit.finishRestorereads every log without saving or writing, removes all current devices except the chosen ones, works that list out at removal time, and clears the saved choice once the roster reads back without them.GroupRecord.restoreKeep: the saved choice, in Room version 10 by AutoMigration. A crash or a lost connection resumes at the removal.Session.sealingKey(): the one place batches, acks, snapshots, store logins and live messages get their key. It refuses while the removal is pending.LiveChannel.sendreturns null during that time.engine/EpochKeys.kt: the history chain walk moved out ofSession, so the preview uses the same code.LinkedSetupwith one checkbox per device, a new error string and a nightly note.A read of a store now has a total limit, as on Desktop:
store/ReadBudget.ktfor pages and log objects, and the roster's own limit for roster pages. Past it, an ordinary sync goes on with what it read. A restored device doesn't remove: it stays pending, says the restore isn't finished, and the next sync goes on from what it holds, as flow-sync #96 asks in Recovery step 6. Android reads into its local store first, so the place is the saved server cursor and the held objects, and thecuts come out the same as one uninterrupted read. A log that stops at an object the store lacks, or at a bad copy, ends there and the removal goes ahead.If the removal has to replace the recovery key, because a device being removed set it, the new words go to
LinkedSync.replacedWordsand the key is marked unsaved before the roster is read again, so a failed read doesn't lose them. They stay in memory only: after the process ends, the saved flag still shows the warning, and Replace recovery words makes new ones.Related issue
None. Follows flow-sync #96, which should land first.
Change type
Validation
All Gradle commands ran with
--offline -Dorg.gradle.jvmargs="-Xmx4096m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8 -Djava.net.preferIPv6Addresses=true"../gradlew :app:assembleGithubDebug: not run locally, left to CI./gradlew :app:testGithubDebugUnitTest: on 802336f, 5,424 tests, 0 failed, 46 skipped. An earlier run of the same code had one failure,LanSyncTest.a_listener_takes_its_last_port_only_while_it_is_free, in code this PR doesn't touch, while other builds ran on this machine; it passed on the rerun. Not rerun in full on the tip, left to CI.:app:testGithubDebugUnitTest --tests 'io.github.aedev.flow.sync2.*' --tests 'io.github.aedev.flow.sync.*' --tests 'io.github.aedev.flow.ui.screens.sync.*'on the tip: 514 tests, 0 failed../gradlew ktlintCheck: passedNew tests:
RestoreChoiceTest(25) and two inLinkedSetupTest, one inServerStoreTest. Each of these changes made at least one test fail when tried: the sealing check off, the live check off, the removal dropped, the cut taken from saved positions, the choice ignored, the removal attempted offline, the sync not finishing the restore, the choice not saved, the saved choice cleared at the wrong time, the new words held only after the re-read, the key marked unsaved only after the re-read or never, and the sync dropping the words. So did these, on the read limit: the removal not held by a read that stopped on it, every sync failing on such a read, a missing object holding the removal too, no limit on pages, on file logs or on roster pages, a stopped read losing its place, a stop not reported on pages or on files, and cuts from saved positions.Test device and Android version: none. Nothing ran on a device or an emulator, so the new screen has only been seen by Compose tests.
Risk and compatibility
Room
LinkedDatabase9 to 10, one nullable column. Other devices read these entries as an ordinary restore, recovery change and removal, so they need no update. On WebDAV a removal asks for a new login, as any removal does.A store that keeps a read at its limit keeps a restore pending, sealing nothing, until that stops; each sync still ends. On this PR alone, an object over the size limit fails the whole sync, so nothing is removed. #1830 makes such an object stop only its sender's log, and from then on a restore cuts there like a gap and goes ahead. The two tests in
RestoreChoiceTestthat assert today's behaviour change with whichever of the two lands second.