Repository navigation
fix: preserve tags when restoring a profile - #1884
Conversation
[Medium impact] The follow-up appears safe to merge; no new blocking issue was found. SummaryPreserves stored tags when a restore form opened without being able to read them.
No new actionable issues were found. Checks in this review were based on source inspection; tests were not run. Reviews (2) · Last reviewed commit: "fix: keep stored tags after a partial pr..." · Reviewed by Greptile |
|
Triage: milestone 3.2.0. This PR fixes the pubky.app profile restore on master and closes no issue, so it carries the milestone itself. Cherry-picking it into 3.1.1 stays your call. |
talosmachina
left a comment
There was a problem hiding this comment.
No findings. Carries the restore read's unread-tags flag in RestoreBase and hands it to writeProfile, so a restore save keeps the stored Paykit tags after a later loadProfile reset tagsUnavailable. Reviewed 147b2fb, light tier, static (the Bitkit Android branch is not built on this reviewer's host; CI covers the build).
What I checked, and 3 candidates I ruled out
Read in full: fetchRemoteProfile, publishIdentityProfile, writeProfile, writeProfileOnce, storedCopy, markTagsRead, loadOwnProfile in PubkyRepo.kt
Call sites traced: fetchRemoteProfile and createIdentity (only CreateProfileViewModel), every restoreBase write
CI: detect-changes green; build-and-test, lint and ui-tests still running at review time
Ruled out
- The bug before the fix:
copyUnread = tagsUnread || tagsUnavailablewithtagsUnreadalways false on this path;loadOwnProfilesetstagsUnavailable = falseon a successful read, sostoredCopyreturnedStoredCopy(null, null)andkeptTagsbecame the form's empty list. - Paykit-only restore (
revision = null): that branch runs only afterpaykitRead.getOrThrow()succeeded, so the defaulttagsUnread = falseis correct there. - Stale flag across restores:
restoreBaseis cleared before each read and after each save, so the flag cannot outlive the form that read it.
Merge confidence: 4/5, CI was still running when this was posted; the new PubkyRepoTest case covers the change.
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
jvsena42
left a comment
There was a problem hiding this comment.
No findings.
- The form-open
tagsUnreadflag (PubkyRepo.kt:141-146, set at:962frompaykitRead.isFailure) reacheswriteProfile(:1075-1079). The save then re-reads the Paykit copy and keeps stored tags plus the form's new ones (keptTags,:1345). - Tags added in the form survive. Deleted tags can't come back, because the form never showed the stored ones.
- The base is only used for the same
publicKey(:1071) and lives in memory, so a wipe and restore of another wallet ignores it. - A failed copy write leaves the stored tags alone. A failed publish keeps the base for the retry.
PubkyRepoTest.kt:1152drives fail-read →loadProfile→createIdentity, which is the real sequence.
Note on changelog.d/next/1884.fixed.md: release-3.1.1 already carries this fix (9eb6a8896) next to #1801. If 3.1.1 ships as cut, the 3.2.0 notes would announce a fix for something 3.1.1 never had. Your call whether to drop it.
Device gate: no device check needed: no UI change, and the state needs a Paykit read to fail at form open and succeed on a background refresh, which needs fault injection. The unit test is the proof. perf: one extra Paykit read at save, only when the form opened unread. CI: 16 pass, 1 pending.
Reviewer model: Claude Fable 5.2 (claude-fable-5-2), Claude Opus 5.5 (claude-opus-5-5)
Reasoning effort: not exposed
|
Following up on the approval notes.
Kotlin compilation, Detekt, Android Lint, and all 5,402 local unit tests passed. GitHub CI is running for the follow-up. |
|
@greptileai Please review the current head |
talosmachina
left a comment
There was a problem hiding this comment.
Re-review of 7ec6742 after 147b2fb. 0 earlier findings, 0 new. The follow-up installs the load's unread-tags flag together with the profile under one lock, carries the save's own unread state into installSavedProfile so a save whose copy could not be reread leaves the flag set, has tag edits in ProfileViewModel save against one profileEditBase() snapshot, and makes createIdentity report a failed tag write while keeping the identity. Full tier on the diff since 147b2fb, static: the Bitkit Android branch is not built on this reviewer's host, CI covers the build.
What I checked, and 5 candidates I ruled out
Read in full: loadProfile, fetchRemoteProfile, createIdentity, publishIdentityProfile, finishIdentityCreation, saveProfile, writeProfileOnce, unreadTagsAfterCopy, writeProfileCopy, storedCopy, loadOwnProfile, setProfile, installSavedProfile in PubkyRepo.kt; the save path of CreateProfileViewModel; updateTags in ProfileViewModel
Call sites traced: createIdentity (only CreateProfileViewModel), profileEditBase() (EditProfileViewModel, ProfileViewModel), every tagsUnavailable and tagsReadEpoch write
CI: detect-changes and e2e-branch/determine green; build-and-test, lint, ui-tests and build-local still pending at review time
Ruled out
- A load that is stale by the time it lands rewrites the flag: the flag, epoch, revision and profile are set only inside
synchronized(profileStateLock)afterisCurrentLoad(), andinstallSavedProfilegoes throughsetProfile, which movesprofileWriteGeneration, so a load in flight across a save is skipped. - The fire-and-forget copy write (
tags == baseTags) clears the flag even though it fails: it yieldsfalseonly when the save reread the copy and no load movedtagsReadEpochsince, the same condition as the removedmarkTagsRead. - A load during a save that set unread is overwritten by the save: only when the save wrote the copy itself (
wroteTags, epoch passed as null), and then the copy holds exactly the installed tags, so clearing is correct. createIdentitynow returning a failure for a created identity: its only caller treatsPubkyProfileTagsNotSavedErroras success plus the existing tag warning, andloadContactsis launched before the return.- A restore that keeps a stale unread flag from the previous identity:
setProfile(null)now resetstagsUnavailable, andfetchRemoteProfileno longer writes the global flag at all; the restore's state lives only inrestoreBase.
Merge confidence: 4/5, CI was still running when this was posted; the four new PubkyRepoTest cases and the CreateProfileViewModelTest case cover the change.
Reviewer model: Claude Opus 5.5 (claude-opus-5-5)
Reasoning effort: Not exposed (the session does not report it)
jvsena42
left a comment
There was a problem hiding this comment.
Re-approving at 7ec67425e. This also closes the gap I'd seen after a save-time read failure. With the copy unreadable at both form open and save, unreadTagsAfterCopy returns true (:1391). installSavedProfile re-arms tagsUnavailable with the profile under the lock (:2637). The next edit's profileEditBase() then rereads and merges instead of writing [].
Checked:
- Locking: the four
profileStateLockblocks hold only atomics, volatile writes,isCurrentand the StateFlow set, so nothing suspends under the monitor. The only nesting is the reentrantinstallSavedProfile→setProfile. - Stale loads: a load in flight is dropped by the
profileWriteGenerationbump. A save'sfalseapplies only with an unchangedtagsReadEpoch, or when the save wrote the tags itself. - Form open and sign-out:
fetchRemoteProfileno longer touches the global flag, so opening Restore can't change a signed-in session's flag.setProfile(null)resets it on sign-out and identity change.
Device gate: no device check needed: the new toast path needs fault injection on the Paykit write, and CreateProfileViewModelTest covers it. perf: not relevant.
Twin: synonymdev/bitkit-ios#1303
Description
The restore form can open when the pubky.app profile is available and its Paykit copy cannot be read. A refresh can recover the stored tags while the open form still holds an empty list. Preserve the restore snapshot’s unread-tags flag through publication so the save keeps those stored tags. 3.1.1 release PR
The follow-up applies the unread state with the profile that was actually accepted and uses that same snapshot for tag edits. If the profile saves and its tag write fails, identity setup completes and displays the existing tag-save warning.
QA Notes
Setup
Run
./gradlew :app:testDevDebugUnitTest --tests '*PubkyRepoTest'. The regression uses a stubbed profile read and publisher with no live backend.Automated Checks
PubkyRepoTest.ktto verify that a failed initial read followed by a successful refresh and a restore save preserves the stored tags in the published Paykit copy.Evidence
Debug Kotlin compilation, Detekt, Android Lint, and all 5,397 local unit tests passed on
147b2fb67. The signed follow-up7ec67425epassed debug Kotlin compilation, Detekt, Android Lint, and all 5,402 local unit tests.Models used
GPT-6(reasoning:Not exposed)GPT-6(reasoning:Not exposed),gpt-6.1-sol(reasoning:high)gpt-6.1-sol(reasoning:high)The session exposes the model family. It does not expose the variant or reasoning setting.