Skip to content

fix: keep the pubky sign-in sheet open when a link cold-starts bitkit - #966

Merged
ovitrif merged 5 commits into
masterfrom
fix/pubky-signin-sheet-cold-start
Oct 16, 2026
Merged

ovitrif merged 5 commits into
masterfrom
fix/pubky-signin-sheet-cold-start

Conversation

@ovitrif

@ovitrif ovitrif commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #957
Refs:

Description

  • Fixes the Pubky sign-in sheet disappearing after a link cold-starts Bitkit. On a cold start the same link arrives twice: through onOpenURL and through the scene delegate's replay. The startup gate can route the first delivery before the second arrives, so the link was handled twice and the second handling replaced the open sheet.
  • Adds a guard in AppViewModel.retainDeepLink: a repeat of the last routed link within 3 seconds is ignored. Both delivery paths stay, since the scene replay was added on purpose with the Pubky auth links (feat: open pubky auth links #722). Opening the same link again later still works.
  • Hides the sign-in sheet on dismiss only for the presentation being dismissed: SheetViewModel keeps shown presentations in order and each dismiss consumes the oldest (markSheetShown, hideDismissedSheet). The old check compared only the sheet ID, so a replaced sign-in sheet's late dismiss hid the new sign-in sheet.
  • Scoped to 3.0.5, based on master.

Out of Scope

  • Bitkit/MainNavView.swift: other sheets still dismiss by sheet ID. Only the sign-in sheet can be replaced by a sheet with the same ID through a repeated link.

Design

N/A — no UI changes.

Preview

Five cold starts from the Shop staging sign-in link, no PIN (2x): each opens one Authorize sheet that is still open 18 s later; the app log shows the second delivery arriving 0.17–0.21 s after routing and being ignored each time.

cold-2x.mp4

With PIN on: Authorize opens the PIN check over the sheet; backing out of it leaves the sheet open (2x).

pin-2x.mp4

QA Notes

Journeys

  • updated open-signin-link.xml — the sheet is still open 5 s after a cold start

Manual Tests

  • on an iPhone with only Bitkit installed, force-quit Bitkit and choose "Authorize with Bitkit" on pubky.app, 3 times → one Authorize sheet each time, open until Authorize or Cancel — the race was reported on a physical device, whose launch timing the simulator journey does not reproduce
  • with PIN on, cold-start Bitkit with a sign-in link, unlock, tap Authorize, then back out of the PIN check → the Authorize sheet stays open — the journey has no PIN step

Automated Checks

  • added PubkyAuthURLSchemeTests.swift — a second delivery of a routed link routes once; the same link later routes again
  • added SheetViewModelTests.swift — a replaced sheet's dismiss keeps the newer sheet with the same ID, whether the newer sheet appears before or after that dismiss
  • ran PubkyContactLinkTests.swift, SceneDelegateTests.swift

On a cold start the same link reaches the app through onOpenURL and the scene delegate replay, and the startup gate can route the first delivery before the second arrives. Ignore a repeat of the last routed link for a few seconds, and hide the pubky approval sheet only for its own presentation so a replaced sheet cannot hide its successor.
@ovitrif ovitrif self-assigned this Oct 16, 2026
The sheet content's onDisappear can fire when the auth check covers it. Hide on the sheet's onDismiss instead, for the presentation that last appeared.
@ovitrif
ovitrif marked this pull request as ready for review October 16, 2026 13:24
@ovitrif
ovitrif requested review from a team, ben-kaufman and pwltr and removed request for a team October 16, 2026 13:24
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T13:27:20.101207Z 3e1d444 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes reach deep-link routing and Pubky sign-in sheet presentation.

The PR appears safe to merge, with a non-blocking concern about how the view chooses the dismissal ID.

Findings

  1. P2 Dismissal reads a shared ID ▶

Summary

Keeps the Pubky sign-in sheet open when a cold-start link arrives twice.

  • Repeated cold-start links now route only once within three seconds.
  • A late Pubky sheet dismissal now leaves its replacement open.
  • Existing app checks use expanded lines without changing behavior.

Author ovitrif explicitly intends the three-second suppression and leaves other sheet types unchanged.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[URL delivery] --> B[retainDeepLink]
  B --> C{Same routed URL within 3 seconds?}
  C -->|Yes| D[Ignore repeat]
  C -->|No| E[Wait for startup gates]
  E --> F[Route URL and record time]
  F --> G[Show Pubky sign-in sheet]
  G --> H[Dismiss using presentationID]
Loading

Reviews (1) · Last reviewed commit: "fix: keep the sign-in sheet open while i..." · Reviewed by Greptile

Comment thread Bitkit/MainNavView.swift Outdated
A replacement sheet can appear before the replaced sheet's dismiss runs, so a shared last-shown ID could let that dismiss hide the replacement. Track shown presentations in order and let each dismiss consume the oldest.
@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 1dae42f: each sign-in sheet dismiss now consumes the oldest shown presentation instead of a shared last-shown ID, so the old sheet's dismiss can't hide its replacement even when the replacement appears first. Answers the review thread on MainNavView.swift; new test covers both callback orders.

@talosmachina talosmachina left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No findings. Keeps the Pubky sign-in sheet open when a link cold-starts Bitkit by ignoring a duplicate delivery of an already routed link and by tying the approval sheet's dismiss to its own presentation. Reviewed 1dae42f, full tier (sign-in), reasoned from the code and CI; iOS does not build on this reviewer's host.

What I checked, and 3 candidates I ruled out

Read in full: SheetViewModel.swift, AppViewModel.swift (retainDeepLink, routePendingDeepLinkIfReady), the MainNavView.swift and PubkyAuthApprovalSheet.swift hunks, both test files
Call sites traced: retainDeepLink from AppScene (onOpenURL, the launch and handoff paths), hideDismissedSheet, markSheetShown

Ruled out

  • A second, different link swallowed: the duplicate guard compares the full URL and a time window, so a new sign-in link inside the window still routes.
  • A replaced sheet hiding its successor: dismissals pop presentations in the order they appeared and only hide when that presentation is still active, which the two new SheetViewModelTests cases cover in both callback orders.
  • Dismiss before appear: hideDismissedSheet returns early when nothing was marked shown, so a dismiss with no recorded presentation is a no-op.

CI: unit and integration tests and build-local still running at review time.

Merge confidence: 4/5, the tests cover the change but CI had not finished at review time.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code scan at 1dae42f: no findings. I reviewed it as an auth and deep-link change.

Checked:

  • retainDeepLink (AppViewModel.swift:233-241) ignores only an identical URL within 3 s of routing it (lastRoutedDeepLink, set at :257). A sign-in link with a different secret still routes. MainNavView exists only after PIN verification (AppScene.swift:864-872), so a link that arrives before unlock stays pending as before.
  • Dismissal is pinned to the sheet that was shown. SheetConfiguration.presentationID is a UUID per show. markSheetShown dedupes, so the PIN fullScreenCover re-appearing does not mark it twice. hideDismissedSheet hides only the active presentation, and the interactive dismiss still nils activeSheetConfiguration. A stale queue entry can at most make the fallback a no-op; it never hides another sheet. showSheet nils before presenting, so there is no in-place swap with the same ID.
  • Approval uses the displayed sheet's config.request (PubkyAuthApprovalSheet.swift:683-690). No TOCTOU is introduced.
  • PubkyAuthURLSchemeTests covers the duplicate ignored at +0.35 s and the same link routing after the window. SheetViewModelTests covers both callback orders and the re-appear after PIN.
  • Not performance-relevant (a date comparison and a small array). No UI text changed.

Device gate (partial): 1dae42f — not run yet. Pending: journeys/pubky-auth/open-signin-link.xml on the simulator (cold start, sheet still open after 5 s, Cancel), plus the same link re-opened within 3 s (ignored) and after 3 s (sheet shows). The device result will follow.

@jvsena42

Copy link
Copy Markdown
Member

Outside this PR's scope, no change needed here.

The cold-start sign-in journey points the wrong way across repos. The description of journeys/pubky-auth/open-signin-link.xml says "Android needs no twin". That line predates this PR. synonymdev/bitkit-android#1514 now adds pubky-auth/signin-cold-start.xml, which walks the same flow over the raw pubkyauth scheme, and its README marks it "not ported" for iOS. I left that half inline on #1514. When #1514's journey is ported back, update this description and the iOS ## Not ported / "Porting from Android" notes to name the Android file. Repo: bitkit-ios, alongside the #1514 port.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving at 1dae42f. The code scan from my earlier review stands, with no findings.

Device gate: 1dae42f — journeys/pubky-auth/open-signin-link.xml: 12/12 actions pass on the iPhone 17 Pro simulator (iOS 26.5), against a fresh profile created through Bitkit on staging.

  • Cold start: the Authorize sheet appeared 4 s after openurl and was still up at +17 s. The second delivery 0.12 s later was dropped:
    14:58:48.275 Received deeplink
    14:58:48.391 Ignoring duplicate delivery of a routed deep link
    
    Two more cold starts also kept the sheet.
  • Relay origin https://httprelay.pubky.app, no Paykit access row. Cancel closes the sheet, and a duplicate secret shows the invalid-request toast.
  • The same link re-opened within 3 s, while the sheet is open or after Cancel, is ignored, with one sheet at most. Re-opened after 5 s it shows the sheet each time.

perf: not relevant; l10n: no UI text changed.

⚠️ Not verified: the PIN-on cold start and timing on a physical device (the PR's manual tests). The approval rests on the simulator run and the code scan for those.

@ovitrif
ovitrif enabled auto-merge October 16, 2026 15:21
@ovitrif
ovitrif merged commit afdc534 into master Oct 16, 2026
18 of 19 checks passed
@ovitrif
ovitrif deleted the fix/pubky-signin-sheet-cold-start branch October 16, 2026 15:56
@ovitrif ovitrif modified the milestone: 3.0.0 Oct 16, 2026
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.

bug: pubky sign-in sheet can disappear when a link cold-starts bitkit

3 participants