Skip to content

fix: speed up pubky profile loading - #854

Merged
ovitrif merged 83 commits into
masterfrom
claude/flow-vibe-pubky-profile-load-lag
Oct 5, 2026
Merged

ovitrif merged 83 commits into
masterfrom
claude/flow-vibe-pubky-profile-load-lag

Conversation

@Jasonvdb

@Jasonvdb Jasonvdb commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Twin: synonymdev/bitkit-android#1399

Fixes the lag when loading pubky profiles and setting up a profile with contacts, which got much worse with the shared Pubky Ring keychain from #774.

Where the lag came from. All Paykit calls go through one lock, and each call kept it for its whole network round trip. That included simple public reads, like fetching a profile name or avatar, or checking how a contact can be paid. So lookups queued behind each other, behind sign-in, and behind background Paykit work. A pubky that was never published takes 2–7 s to fail, and everything else waited for it. On top of that:

  • The Pubky Ring choice screen and adopting a Ring identity added slow, repeated lookups to that queue. Adopting also waited about 4 s for an identity refresh while holding the lock, then fetched the profile again.
  • Before the import screen appears, every follow's profile is looked up. Those lookups ran one at a time, each retried once, with nothing moving on screen.
  • The Contacts screen waited for every contact's profile before showing anything.

How this fixes it. Public reads skip the lock and run side by side, up to six at once. Background reads (the contact list, private payment setup, payment checks) may use at most four of those six, so what you are looking at (your avatar, your profile, the Ring rows, the import screen) never waits behind them. Anything that touches your session, keys or saved Paykit data still goes through the lock. The choice screen shows its rows straight away with a spinner on each, and keeps found profiles for the session. Adopting reuses the profile its row already found, and sign-in no longer waits for the identity refresh. Contacts shows your saved contacts at once and fills in names and avatars as they load. Saving the imported contacts without new lookups came from #851, which this branch now includes from master.

Simulator, staging network, one take per build Before After
Published name visible 9.7 s 1.9 s
Tap a Ring row → Pay Contacts (identity with no follows) 9.6 s, nothing on screen moves 4.6 s, spinner on the row
Tap a Ring row → import screen (8 follows, 6 never published) 60.1 s, nothing on screen moves 8.8 s, spinner on the row

With QA's 61-follow Pubky Ring profile (simulator, staging network; 54 follows have profiles, 7 were never published). The before column is this PR before the Contacts and Delete fixes:

Before Now
Tap a Ring row → import screen 22–23 s 22 s
Continue on "Let your contacts pay you" 104–106 s 86 s
Contacts: profile lookups per visit 61 7 (only contacts with no profile)
Contacts: list updates per refresh 54 0
Fetches of each avatar that fails 5 per run 1
Delete Profile after 5 Contacts visits 645 s 100 s

Description

  • Public profile, avatar, follows and payment-route reads no longer wait for the Paykit lock. Up to six run at once, at most four of them for background work, and a freed slot goes to what you are looking at before queued background reads
  • Sign-in starts the identity refresh in the background instead of waiting up to 5 s for it; auth approvals still wait for it, including a refresh that is already running
  • The Pubky Ring choice screen shows its rows at once, with a spinner on each row while its profile loads and on the row being adopted, and stays up until the adoption finishes
  • Ring row profiles are kept for the session; rows with no profile are looked up again when the app returns to the foreground
  • Adopting a Ring identity reuses the profile its row already found, and goes to Create Profile if that profile turns out to have been removed
  • The follows shown on the import screen are looked up side by side, with no retry, instead of one at a time
  • Contacts shows saved contacts at once, using an edited profile, a stored profile, a profile found earlier this session, or the saved name, then fills in profiles as they load. Opening a contact whose profile hasn't loaded yet looks it up at once, ahead of the background refresh, and editing or tagging it waits for that one lookup, so an edit can't save over a profile that is still arriving. If the lookup fails, the edit saves as it does on master
  • A contact import that finishes after you leave it no longer takes you to Pay Contacts, and an import that a sign-out overtakes stops saving
  • The Profile screen shows your cached name and avatar while it loads
  • Contact lookups retry only when adding a contact, and never after "not found"
  • Leaving a screen while it loads contacts keeps the contacts already loaded and shows no error, including while the load waits for the Paykit lock; leaving Settings > General still finishes turning on contact payments, unless you sign out of Pubky first
  • Turning contact payments on or off from Settings > General or Pay Contacts stops when you sign out of Pubky meanwhile: it writes no settings, publishes nothing after sign-out removed the endpoints, and leaves sign-out's cleanup marks in place
  • When you turn contact payments on or off while an earlier change is still running, the latest change wins. That includes the automatic enable from Settings > General. The earlier change stops quietly: it writes no settings and publishes or removes no endpoints after the newer one. Publications and removals check this under their locks, and the public marker is now removed under the endpoint lock too
  • Avatar cache writes that finish after sign-out are dropped. An avatar that failed because it is missing or too large is not fetched again until the avatar cache is cleared. Other failures are tried again after 60 s
  • Contacts does not look up again a profile it found in the last 10 minutes. It applies refreshed profiles at most once every 300 ms, and skips any that haven't changed
  • Opening Contacts no longer restarts private payment link setup for every contact. The app now announces a saved-contacts change only when contacts are added or removed, and Import announces it once. Before, each Contacts visit queued a walk over every contact, and Delete Profile waited behind all of them
  • Removing a contact stops its pending profile lookup, and deleting all contacts stops the refresh
  • A tag change queued while a contact's profile loads is dropped if you sign out or switch identity before it saves
  • Public reads are refused during a wallet wipe, and a read the wipe overtakes is dropped

Out of Scope

  • Bitkit/Services/PubkyService.swift: app launch still restores the Paykit session twice; removing that needs Paykit input
  • Bitkit/MainNavView.swift: opening Profile before initialization finishes still shows the full-screen loading view
  • Bitkit/AppScene.swift: the private link burst still holds the Paykit lock for each link attempt; changing its timing needs Paykit input
  • Continue on "Let your contacts pay you" and Delete Profile still wait for private payment link walks over every contact (about 90–120 s each with 61 contacts); feat: share paykit state across apps #856 rewrites that code
  • The import screen has no per-follow deadline on iOS (Android has one). The Paykit Swift bindings can't cancel a read once it has started, so a deadline couldn't free that read's slot
  • Contacts still looks up, on each visit, every contact without a profile (for example a never-published follow)
  • Bitkit/Views/Profile/CreateProfileView.swift: the form still waits for the existing-profile lookup
  • paykit-rs: a never-published pubky still takes 2–7 s to fail its lookup, because pkarr NotFound surfaces as a transport error. That is most of the remaining 8.8 s before the import screen
  • Contacts are still saved one at a time, because Paykit has no batch save
  • Bitkit/Views/Contacts/EditContactView.swift: editing a contact whose profile lookup failed still saves an empty bio, avatar and links over the published ones, as on master; saving only the changed fields needs a new override format

How to review

Production code is about a quarter of this PR; most of the rest is tests (about 2,470 lines, with near-duplicate cases merged into case tables). Suggested order, one area at a time:

  1. Paykit read path (the core fix): Bitkit/Services/PubkyService.swift, with withPublicRead, PaykitSdkReadLimiter and the background identity refresh. Tests: PaykitSdkReadLimiterTests, PaykitPublicReadLaneTests, PaykitSdkOperationLockTests, PaykitSdkClientConfigTests, PubkyIdentityRepublishTests
  2. Profile and Ring choice screen: PubkyProfileManager.swift, PubkyChoiceView.swift, PubkyChoiceRow.swift, ProfileView.swift, PubkyImage.swift. Tests: PubkyProfileManagerTests, PubkyChoiceViewTests, PubkyImageCacheTests
  3. Contacts: ContactsManager.swift (the list shown at once, the per-identity session profile cache with its freshness window, the batched background refresh, the saved-contacts announcement, queued tag changes bound to their session, and a screen's lookup taking over a contact's background lookup), then the views that call it: ContactDetailView.swift, EditContactView.swift, the import views and GeneralSettingsView.swift. Tests: ContactsManagerTests, ContactImportUITests
  4. Payment checks: PaykitPaymentRequestService.swift and PrivatePaykitService+Contacts.swift, which only choose a read lane
  5. Journeys: journeys/pubky-profile/

What to look for: reads no longer queue behind the Paykit lock, so a result can now arrive late, after a sign-out, an identity switch, a newer save or a wallet wipe. Most of the new code drops those late results (owner checks, generation counters, the wipe check). Each of those guards has a test that holds a fake Paykit call halfway, runs the competing action, then releases the call.

Design

N/A — no design available. The spinners reuse existing components.

Preview

Choosing a Pubky Ring identity

ios-before-after.mp4

Left: master (ab88d1c, plus a two-line compile fix it needed then). Right: this PR (ad6b5d5). Same simulator, same taps, fresh app data, staging network, one take each. On master the rows show bare keys for about 10 s, and after the tap the screen does not change for about 9 s. With this PR each row shows a spinner until its profile loads, and the tapped row spins while it is adopted.

Setting up a profile and importing contacts

ios-import-before-after.mp4

Left: master (e33dc08). Right: this PR (54e13d0). Same simulator, same taps, staging network, one take each, aligned on the Ring row tap. Each take chooses the Pubky Ring identity, taps Import All, then opens Contacts and Profile. The test identity has no real pubky.app follows, so both builds use the same debug-only hook that returns 8 follows: 2 published and 6 never published, all created for this test. On master the screen does not change for about 60 s after the tap. With this PR the tapped row spins and the import screen opens in about 9 s. Import All, Contacts and Profile are quick on both (thanks to #851), but master shows a "Failed to load contacts" error after you leave Contacts.

QA Notes

Journeys

  • new cached-profile-header.xml — Profile opened while loading shows the cached name and avatar without edit actions, then the full profile
  • new ring-choice-rows.xml — Pubky Ring rows show at once with a spinner each until their profile loads; adopting spins only the tapped row and disables the rest
  • new contact-import-after-leaving.xml — leaving the import screen right after Import All stays on Home, never opens Pay Contacts, and Contacts later lists every follow
  • new contacts-list-loading.xml — saved contacts show at once with their stored names, profiles fill in, and the full-screen spinner shows only until the first load

Manual Tests

  • With Pubky Ring holding a signed-up pubky and a never-published one, open the profile choice screen → both rows show at once with spinners, and the signed-up row resolves without waiting for the other — Pubky Ring not in Capabilities
  • Tap a Ring row → only that row spins, the other rows are disabled, and Create Profile or Pay Contacts opens — Pubky Ring not in Capabilities
  • Leave the choice screen and come back, then background and foreground the app → found rows are not looked up again, missing rows are — Pubky Ring not in Capabilities
  • Adopt a Ring identity that follows several pubkys, some never published → the tapped row spins and the import screen opens within seconds — Pubky Ring not in Capabilities

Automated Checks

  • added PaykitSdkReadLimiterTests.swift — the read limiter keeps its cap and FIFO order, keeps two slots for what you are looking at, gives a freed slot to an interactive read before queued bulk reads, and releases cancelled waiters
  • added PaykitPublicReadLaneTests.swift — background lookups leave room for interactive reads, receiver and payment-route reads don't hold the lock, and public reads are refused during a wallet wipe and dropped when a wipe overtakes them
  • updated PaykitSdkOperationLockTests.swift — unlocked work still goes through wipe admission
  • updated PaykitSdkClientConfigTests.swift — sign-in returns while the identity refresh is still running
  • updated PubkyIdentityRepublishTests.swift — approvals wait for a refresh sign-in started, up to the cap
  • updated PubkyProfileManagerTests.swift — Ring row cache, adopt reuse, stale profile loads after a write, cached header owner check, row lookup spinners, and lookups shared by two choice screens
  • updated PubkyChoiceViewTests.swift — when a row shows its lookup spinner
  • updated ContactsManagerTests.swift — tag changes queued during a contact's lookup keep the profile it finds and each other, a contacts load cancelled while its record read waits for the lock stays quiet, a screen's lookup taking over the contact's background lookup, retry policy per caller, import screen lookups on the interactive lane, the Contacts list publishing before lookups finish and reusing profiles found this session, an import that a sign-out overtakes, opening Pay Contacts only while the import is still pending, skipping lookups for profiles found in the last 10 minutes, batched refresh publishes that skip unchanged profiles, announcing saved-contact changes only when the keys change, stopping lookups for removed contacts, and dropping queued tag changes after a sign-out or identity switch
  • updated ContactImportUITests.swift — an import finishing after you leave the overview or the selection screen does not open Pay Contacts
  • updated ContactPaymentsServiceTests.swift:
    • A contact payments change stops when Pubky signs out during its contacts load or its publication, and writes nothing once the session changed.
    • A disable wins over an older enable still publishing.
    • An enable wins over an older disable still removing.
    • A newer enable supersedes an older one.
    • Endpoint removals refuse once their change is no longer current.
  • updated PaykitPaymentRequestServiceTests.swift — a contact's payment check uses the interactive lane
  • updated PubkyImageCacheTests.swift — avatar stores that started before a clear are dropped, and failed fetches are remembered until a clear (missing or too large) or for 60 s (other errors)

Profile, avatar and follows lookups are unauthenticated public reads, so they now run through a bounded, cancellable limiter (six at a time) instead of the Paykit SDK operation lock, taking the lock only when no SDK exists yet. Sign-in, sign-up and session import start the identity republish in the background rather than holding the lock while it waits on the DHT.
The choice screen now keeps found Ring profiles for the session and remembers misses until the app returns to the foreground, dedupes lookups in flight, and runs one cancellable load that stops when the screen goes away or an adopt starts. Adopting reuses the row's profile for that key instead of fetching it again, shows a spinner on the tapped row and keeps the other rows disabled. A profile load that started before a save, adopt or sign-out no longer overwrites the newer profile.
A missing profile is never retried, and bulk contact loads and follow discovery no longer retry a failed lookup, so each contact without a published profile costs one lookup instead of two. Adding a contact and importing contacts still retry a failed lookup once, and a lookup cancelled during that wait no longer starts the retry.
Adopting a Pubky Ring identity now stops only the other rows' lookups, so the tapped row's result can still land in time to be reused, and a failed adopt reloads the rows instead of leaving bare keys. Adoption looks the row profile up by the normalized pubky, and when it reuses one it refreshes the profile in the background without holding up navigation or deciding profile setup. The ring lookup tests now fail at a deadline instead of hanging when a request is never answered.
@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Medium risk] Optimizes profile loading with caching and concurrency limits.

The PR is not ready to merge because Ring adoption can bypass required profile setup and sign-out can leave an old avatar in memory.

Findings

  1. P1 Stale profile skips setup ▶
  2. P1 Disk read restores cleared avatar ▶
  3. P2 Old Ring profiles remain visible ▶

Summary

This PR moves public Pubky reads to a bounded concurrent lane, reuses Ring row profiles during adoption, and adds cached profile presentation and cancellation-aware loading.

  • Ring adoption can treat a removed remote profile as still present.
  • Ring row display and image-cache clearing have stale-cache paths that need attention.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Ring[Ring identities] --> Rows[Bounded public profile reads]
  Rows --> Cache[Session row-profile cache]
  Cache --> Adopt[Adopt selected identity]
  Adopt --> Route[Profile-setup routing]
  Adopt --> Refresh[Background profile refresh]
  Image[Avatar disk read] --> Memory[Image memory cache]
  SignOut[Sign-out clear] --> Memory
Loading

Reviews (1) · Last reviewed commit: "test: add pubky profile loading journeys"

Comment thread Bitkit/Managers/PubkyProfileManager.swift Outdated
Comment thread Bitkit/Views/Profile/PubkyChoiceView.swift Outdated
Comment thread Bitkit/Components/PubkyImage.swift
Resolve conflicts in ContactsManager and PubkyProfileManager, and drop
the duplicated sdkFactory declaration that master carries.
…ght row lookup

Adopting a Pubky Ring key writes its reference before sign-in finishes, so a row result landing mid-sign-in swapped the profile destination to an empty profile. The destination now keeps the choice screen while adoption is in progress, and returns to it when adoption fails. Adoption awaits the tapped row's lookup that is still in flight instead of fetching the same key again, and automatic session recovery while signed out no longer discards the choice rows and their lookups.
…tact lookups on tap

A disk hit promoted to the memory cache skipped the clear generation check, so a lookup that raced a sign-out could put the previous identity's avatar back in memory. The generation is now read once before the lookup and required for every memory store. The contact detail Retry button retries a transient failure once, and activation is covered returning while the identity republish is still running.
…r row profiles exactly

The refresh after adoption reuses a Ring row profile now treats a definitive
not-found for the adopted key as a removed profile: it clears the profile and
its cached metadata and marks profile setup pending, unless the identity
changed or a newer profile write landed. Transport failures keep the reused
profile. The choice screen's row profiles now equal the manager's cache
outside an adoption and only merge while one runs.
A Ring row now shows a spinner in its avatar slot until its profile
lookup finishes, whatever the outcome, so a row still looking up no
longer looks like one whose identity has no profile. The adopting row
keeps only its key-icon spinner, and stopped lookups drop their spinner
straight away.
…ng and share ring row lookups between choice screens
…bky-profile-load-lag

# Conflicts:
#	Bitkit/Views/Profile/CreateProfileView.swift
#	BitkitTests/PaykitSdkClientConfigTests.swift
Public reads now have a bulk lane: contact list and import profile lookups
take one of four bulk slots before one of the six read slots, so they can
never hold more than four. Avatars, the own profile, Ring rows and
single-contact lookups take only a read slot, so at least two stay free for
them while a large contact list loads.
Receiver path and marker reads are public reads, so contact receiver
discovery, private receiver path selection and payment request receiver
checks now run on the public read lane instead of holding the global SDK
lock for every network round trip. Private sync, import and payment request
checks use the bulk lane; adding or opening a single contact stays
interactive. Saved records are still read and written under the lock, and
selection still protects every saved path when no SDK can be built.
Import no longer looks every follow's profile up again, with a retry, before
saving it. It saves the profiles the import screens already show. A follow
whose lookup failed is still imported as a placeholder, on the wallet
receiver path and without receiver discovery, since private sync discovers
and merges a saved contact's receivers before it uses them. Only a failed
save skips a contact, and an import stops saving once the Pubky session is
reset by a sign-out or identity change.
Contacts used to show a spinner over an empty list until every contact's
profile lookup had finished, which takes seconds per contact with no
published profile. A load now publishes the saved records at once, using a
contact's profile override, its stored Paykit profile, a profile resolved
earlier this session for the same identity, or its saved label, then looks
the rest up in the background on the bulk read lane and updates rows as they
resolve. A failed lookup leaves its row as it is. Profiles resolved while
preparing an import, importing or adding a contact seed that session cache,
which a reset or another identity's load clears. Profile refreshes do not
count as a change to the saved contacts, so private Paykit sync does not
rerun for every row that fills in.
The import already runs on its own task, so leaving the import screens does
not stop it. Select stayed enabled while Import All ran, though, and each
screen only knew about its own import, so a second import of the same
follows could start alongside the first. The contacts manager now reports
whether an import is running, and both import screens disable Select,
Import All and Continue until it finishes.
Adds journeys for importing a Ring identity's follows and for Contacts
showing saved contacts while their profiles load, documents them in the
pubky-profile suite README, and extends the changelog fragment to cover the
faster import and contacts list.
Save on a contact's edit screen first waits for the lookup of a profile
the row is still waiting for, and then may upload a new avatar. It only
bound itself to the Contacts reset generation once those waits were
over, and never to the Pubky session. If the user left, signed out and
adopted another identity that had saved the same contact before the
lookup finished, the old edit filled its form from the new identity's
row, saved its label through the new session, wrote back the local
override sign-out had cleared, and showed a toast and navigated.

ContactsManager.saveContactEdit now binds the edit to the Pubky session
and the reset generation when Save is tapped, using the same check as
queued tag changes. A stale edit stops after the lookup, after building
the edit (including the avatar upload) and around the save, without
saving, writing an override, changing a row or reporting an error, and
the screen shows no toast and does not navigate.
Adopting a remembered Ring row reuses its cached profile and refreshes
it in the background. A profile save only bumped the profile write
generation once its publication returned, so a refresh that found the
profile missing while the save was still publishing was accepted: it
cleared the profile and persisted profile setup as pending. The save
then committed the edited profile but left setup pending, so MainNav
could prompt Create Profile after a successful edit, also after a
relaunch.

A profile save now drops every profile read that started before it as
soon as its write starts, and a published profile clears pending setup.
A save that a sign-out or another identity overtakes no longer commits
its profile or touches setup state, so it cannot clear the next
identity's pending setup or show the earlier identity's profile.

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: 👍 Approve

Reaudit: diff 5 files.
No new findings; the rest is in the review.
Pair PR synonymdev/bitkit-android#1399: equivalent. This change binds an iOS contact save to its session and drops a missing-profile refresh that a profile save supersedes.

QA:
Tests wait for CI.


Reviewed by grok-4.7-xhigh via gh-pr-review-loop skill

A contact's tag change or edit checks its session right before it saves,
but the save then waits for the Paykit operation lock. A sign-out and
another identity's sign-in could take the lock first, so the save wrote
the old identity's label to the next identity's contact with the same
key. The local override and row were dropped, but the SDK write landed.
Edit Contact's avatar upload had the same window.

The contact save now carries the identity that was signed in when the
change was queued or Save was tapped, and PaykitSdkService checks it
against the SDK's signed-in identity in the same locked operation as
the write. A mismatch writes nothing and throws identityChanged, which
ContactsManager drops quietly, with no error and no toast. Edit
Contact's avatar upload passes the same identity to the upload's
existing check under the lock.
Edit Profile uploaded a new avatar and then saved the profile without
tying either to the session Save was tapped in. A save that a sign-out
and another identity's sign-in overtook changed nothing locally, but the
screen still showed the saved toast and navigated back, the upload could
land for the next identity, and the publication could overwrite the next
identity's profile while it waited for the Paykit lock.

PubkyProfileManager.saveProfile now takes the avatar image, binds the
edit to the current session before uploading, and returns whether it
saved. The upload and the profile publication both carry the session's
identity, which PaykitSdkService checks under the operation lock, so
neither writes once another identity is signed in. A stale or refused
save returns false without an error, leaves the profile and setup state
alone, and Edit Profile then shows no toast and stays put.
Binding profile and contact avatar uploads to an identity routed them
through the subscription icon upload's check, which throws the payment
request error requestUnavailable both for another identity and for no
live session. Edit Profile then showed "The payment request is no
longer available." when an upload failed for the signed-in identity.

Profile and contact avatars now use their own upload, which checks only
the identity under the Paykit lock: another identity throws
identityChanged, which Edit Profile and Edit Contact drop quietly, and
any other failure, such as no live session, is the SDK's own error, as
before. The payment request upload is unchanged.

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: 👍 Approve

Reaudit: diff 9 files.
No new findings; the rest is in the review.
Pair PR synonymdev/bitkit-android#1399: equivalent. An iOS contact or profile write is refused once another identity is signed in under the Paykit lock. A profile or contact avatar upload that fails while that identity is still signed in reports the SDK's own error.

QA:
Tests wait for CI.


Reviewed by grok-4.7-xhigh via gh-pr-review-loop skill

@Jasonvdb

Jasonvdb commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

@ovi-reviewer test

1 similar comment
@Jasonvdb

Jasonvdb commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

@ovi-reviewer test

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA review

Scope: Full PR diff and affected callers/tests reviewed at 6d29518 against merge base f751f50, with targeted source comparison of corresponding Android flows at b46f007.

1 actionable finding — resolve or provide an evidence-backed rebuttal.

Validation: current-head unit tests and integration tests succeeded in CI. The public-read SDK contract was checked at Paykit 24162eb. Local host Swift checks passed: 13 extracted lock/limiter tests, two head/base ordering tests and contact-form checks, using dependency stubs. The ordering tests confirmed the reported toggle race; full app suites were not run locally.

iOS import preparation still has no per-follow deadline. Remaining large-contact private-link delays are tracked separately in #861 and #856.

Findings

  • [MEDIUM] Invalidate an older enable when a newer toggle turns sharing off — inline at Bitkit/Views/Settings/GeneralSettingsView.swift:155.

Comment thread Bitkit/Views/Settings/GeneralSettingsView.swift
General Settings' first automatic enable runs in its own task, so a
disable from a General Settings opened again could start while that
enable still prepared endpoints, and the enable then published the
marker and endpoints after the disable had removed them.

Each change now takes the next change number before it suspends and
stops quietly once a newer one starts, as it already did for a session
change. Its endpoint publications and removals check this under their
locks, and a public removal now also removes the marker under the
endpoint lock, so an older change never writes or removes endpoints
after a newer one.
@ovitrif

ovitrif commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator

@ovi-reviewer test

Hehe it's offline for maintenance ^^

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

Went over both fix PRs also in dedicated sessions with AI, to look into the other perf issues and what still needs fixes over the Paykit lib update PRs.

Recommended approach was to merge this and its Android sibling, then have Ben's PRs fix the merge conflicts.

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA review

Scope: Full PR review at 3ced8b1, covering the complete diff from merge base f751f50, affected callers and tests. Shared-flow comparison used Android b46f007; that comparison covered the changed contracts only.

2 actionable findings — resolve or provide an evidence-backed rebuttal.

Posting these in case you find this useful and want to handle here, otherwise let's merge as per @ovitrif's recommendation

Findings

  • [MEDIUM] Invalidate stale profile reads before uploading an edited avatar — inline at Bitkit/Managers/PubkyProfileManager.swift:814.
  • [MEDIUM] Discard interactive profile results after deleting a contact — inline at Bitkit/Managers/ContactsManager.swift:493.

Comment thread Bitkit/Managers/PubkyProfileManager.swift
Comment thread Bitkit/Managers/ContactsManager.swift
@ovitrif
ovitrif enabled auto-merge October 4, 2026 09:31
@ovi-reviewer

ovi-reviewer Bot commented Oct 5, 2026

Copy link
Copy Markdown

@Jasonvdb build error

@Jasonvdb

Jasonvdb commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Could you share the build log or the error?

CI builds 3ced8b1 without errors:

  • build-local built the e2e app, and e2e passed on it (run 37114271554).
  • Run Tests compiled and ran the unit tests.
  • Run Integration Tests compiled. Only testchannelPurchaseFlow fails, because a staging Blocktank order never reaches paid.

master hasn't moved since f751f50, so a build merged with master is the same code. My guess is the build environment after maintenance, but I can't tell without the error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants