Skip to content

fix: keep a pending pubky sign-in link when another link opens - #987

Merged
ovitrif merged 5 commits into
masterfrom
fix/pubky-signin-link-kept
Oct 17, 2026
Merged

ovitrif merged 5 commits into
masterfrom
fix/pubky-signin-link-kept

Conversation

@ovitrif

@ovitrif ovitrif commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #985
Closes #992

Refs:

This PR keeps a pending Pubky sign-in link when another link opens Bitkit before the sign-in is handled. The later link now opens after the sign-in, as on Android, instead of replacing it.

Description

  • Fixes a link opened on the PIN screen dropping a pending sign-in link: AppViewModel kept one pending deep link and overwrote it. A sign-in link now keeps that slot, and another link waits in one follow-up slot that opens once the sign-in is handled and its Authorize sheet closes. A newer link replaces the one in that slot, and a newer sign-in link still replaces an older one.
  • Holds a link opened while the Authorize sheet is open, or while it replaces another sheet, until the sheet closes, instead of routing it over the sheet. SheetViewModel now records which sheet is being shown after a replacement, and MainNavView routes the held link once the Authorize sheet has closed and its 0.7 s replacement delay has passed.
  • Opens a payment link that crossed the PIN lock, on its own or held behind a sign-in link, for review instead of QuickPay, as Android does. Before this PR a lightning: link under the QuickPay threshold opened on the PIN screen was QuickPaid right after unlock, and a link held behind a sign-in would have been QuickPaid after the Authorize sheet closed. AppScene marks links that arrive while the PIN screen is up, and PaymentNavigationHelper.shouldUseQuickpay skips QuickPay while such a link routes.

Out of Scope

  • AppViewModel: two non-sign-in links opened while the app cannot route them still keep only the latest, as before.

Design

N/A — no UI changes.

Preview

N/A — no new UI; the sheets open in a different order.

QA Notes

Journeys

  • new signin-with-second-link.xml — a payment link opened on the PIN screen in either order, or over the open Authorize sheet, opens only after the sign-in sheet closes (port of the Android journey)

Manual Tests

N/A

Automated Checks

  • added PubkyAuthURLSchemeTests.swift — a pending sign-in link routes before a later link in either order; a link waits until the open Authorize sheet closes, and a newer one replaces it; a newer sign-in link replaces an older pending one; the pending Authorize sheet stays tracked when another sheet shows during its replacement; links that crossed the PIN lock route without QuickPay, unlocked links keep it

@ovitrif ovitrif self-assigned this Oct 16, 2026
@ovitrif
ovitrif requested review from a team, ben-kaufman and coreyphillips and removed request for a team October 16, 2026 19:11
@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-16T19:13:21.703387Z 13d02ef PR opened
ℹ️ 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13d02ef3cf

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Bitkit/MainNavView.swift Outdated
Comment thread journeys/pubky-auth/signin-with-second-link.xml
@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change is limited to Bitkit's incoming-link and approval-sheet flow.

Fix the approval replacement marker before merging so a receipt cannot release the held payment early.

Findings

  1. P1 Payment opens before approval ▶

Summary

Keeps a pending Pubky sign-in ahead of another link and resumes the held link when approval closes.

  • Bitkit routes a Pubky sign-in before the next waiting link.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Sign-in and another link arrive] --> B[Keep sign-in pending]
    B --> C[Hold latest other link]
    C --> D[Open approval or schedule replacement]
    D --> E[Approval closes]
    E --> F[Promote and route held link]
    D --> G[Receipt arrives during replacement delay]
    G --> H[Pending approval marker cleared]
    H --> I[Held link routes before approval]
Loading

Reviews (1) · Last reviewed commit: "fix: keep a pending pubky sign-in link w..." · Reviewed by Greptile

Comment thread Bitkit/ViewModels/SheetViewModel.swift Outdated
@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 1b4fad8, answering the Codex and Greptile findings:

  • A held link waits for the Authorize sheet to finish closing (the 0.7 s sheet replacement delay) before it routes, so the next sheet is not shown mid-dismissal.
  • The pending Authorize state now survives another sheet being shown during its replacement delay.
  • PubkyAuthURLSchemeTests.swift adds that case.
  • The journey keeps xcrun simctl openurl, the deep-link route the other iOS journeys use.

@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.

1 finding (1 P2). Keeps a pending Pubky sign-in link when another link arrives, holding the second link in a follow-up slot until the sign-in is handled and its Authorize sheet closes. Reviewed 13d02ef, full tier, reasoned from the code and CI (iOS does not build on this reviewer's Linux host, so no simulator run).

What I checked, and 4 candidates I ruled out

Read in full: the diff; AppViewModel.retainDeepLink / routePendingDeepLinkIfReady / promoteDeepLinkAfterPubkyAuthIfIdle; SheetViewModel.showSheet / hideSheet / hideSheetBeforePerforming; MainNavView deep-link onChange/task hooks and handleDeepLink; PubkyAuthApprovalSheet dismiss paths; AppScene onOpenURL / DeepLinkRouter delivery.
Call sites traced: retainDeepLink (3 in AppScene), isPubkyAuthApprovalShownOrPending, showSheet(.pubkyAuthApproval).
CI: validate and detect-changes green; Run Tests, Run Integration Tests and build-local still pending at review time.

Ruled out

  • Cold-start duplicate delivery parks the sign-in link in the follow-up slot: the duplicate has pending == url, so it falls through to the plain assignment, and lastRoutedDeepLink still swallows a re-delivery after routing.
  • Held link stranded when the sign-in link fails to parse or shows a toast instead of the sheet: routePendingDeepLinkIfReady promotes the follow-up after handler returns, and the pendingDeepLinkURL onChange routes it.
  • replacingSheetID left stale: it is cleared in both showSheet branches, and hideSheetBeforePerforming never sets it.
  • Newer sign-in link while the Authorize sheet is open: sign-in links bypass the hold, so a newer one still replaces the open sheet through showSheet, as before.
  • Cold reader: the finding stood, rated P2.

Merge confidence: 3/5, CI was still running when I reviewed, and the finding has not been checked on a device.

Comment thread Bitkit/MainNavView.swift
ben-kaufman
ben-kaufman previously approved these changes Oct 16, 2026

@ben-kaufman ben-kaufman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed 1b4fad8 and the existing sheet-dismissal and replacement fixes. No blocking code findings. The routing and regression tests cover sign-in priority, follow-up replacement, and the intervening-sheet case. SwiftFormat passes on all four changed Swift files. Hosted unit, integration, and build checks pass, while E2E is still queued. App and device tests were not rerun locally, so this approval does not mark the pending E2E gate complete.

@ovitrif
ovitrif enabled auto-merge October 16, 2026 20:20
jvsena42
jvsena42 previously approved these changes Oct 16, 2026

@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 1b4fad8, with two LOWs inline. This round was reviewed on Opus because Fable credits ran out.

Checked and clean against synonymdev/bitkit-android#1547:

  • Two slots, and both orders on the PIN screen work (retainDeepLink 243-253). A payment link waits behind an open or pending Authorize sheet. The newest link wins in each slot.
  • No silent drop beyond newest-wins. A held link is promoted after the handler returns, so a sign-in that ends in a toast still releases it. .task(id: canHandleDeepLinks) re-runs after a relock, and a node-gated link waits for the node.
  • Dismissal: Cancel, OK and swipe all clear activeSheetConfiguration. A newer sign-in over an open sheet sets replacingSheetID first. The presentationID pinning from #966 is untouched.
  • No queue growth (one URL per slot). A held contact link is re-checked against the session.

Device gate: 1b4fad8 — signin-with-second-link.xml: 6/6 cases pass on the iPhone 17 Pro simulator (PIN on, staging regtest):

Case Close Send after close Routes
sign-in then bitcoin: on PIN Cancel 0.75 s 1
bitcoin: then sign-in on PIN swipe 0.79 s 1
bitcoin: over open Authorize swipe 0.77 s 1
second sign-in over open Authorize, then bitcoin: Cancel (newer sheet shown first) 0.72 s 1
second sign-in and bitcoin: inside the 0.7 s replacement Cancel 0.73 s 1
bitcoin: over open Authorize Authorize → PIN → OK 0.74 s 1

Send always opened on Amount with 10 000 sats. No held link routed while an Authorize sheet was up.

perf: 0.72–0.79 s from close to Send. l10n: no UI text changed.

Each LOW can be fixed here or moved to a follow-up issue. Reply on the thread with which one.

Comment thread Bitkit/ViewModels/AppViewModel.swift
Comment thread journeys/pubky-auth/signin-with-second-link.xml
@jvsena42

jvsena42 commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

These are outside this PR's scope and need no change here.

1. QuickPay runs on any lightning: link that crossed the PIN lock. On master (bd09966), terminate the app, open a lightning: invoice under the QuickPay threshold, and enter the PIN: it is QuickPaid with no review (reproduced on the simulator, run M2). Android suppresses QuickPay for everything that crossed the lock (markDeferredScanUnlocked → suppressQuickPay). iOS has no equivalent. Suggested follow-up in bitkit-ios: port the lock-crossing suppression, which also covers the LOW inline.

2. Authorize sometimes hangs on "Authorizing…". Nothing is logged, the relay never receives the approval, and the sheet cannot be dismissed. It hung in 4 of 6 attempts on this head and 1 of 3 on master, alongside Deferred session restoration and SharedStateBusy warnings, so it is not from this PR. A held payment link stayed held while it hung, which is correct. Suggested follow-up in bitkit-ios, possibly tied to #984 / pubky/paykit-rs#197.

3. An incoming-transaction sheet replaces an open Authorize sheet and drops the sign-in request. While Authorize was open, a received on-chain transaction (an old channel sweep) called showSheet, and SheetViewModel replaced the sheet:

22:55:25 DEBUG: Hiding sheet pubkyAuthApproval reason: SheetViewModel.swift:64 showSheet(_:data:) - SheetViewModel

The sign-in request was lost with no user action, and the held payment link then opened Send. This sheet replacement predates this PR. Suggested follow-up in bitkit-ios: give pubkyAuthApproval priority over receivedTx, or queue receivedTx behind it.

@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

These are outside this PR's scope and need no change here.

@jvsena42 filed both: #992 (QuickPay on a link that crossed the PIN lock, 3.0.0, which also covers the inline LOW) and #993 (Authorize hanging on "Authorizing…" during a busy session restore, 3.0.5, linked to #984 and pubky/paykit-rs#197).

@ovitrif
ovitrif disabled auto-merge October 16, 2026 22:00
@ovitrif
ovitrif dismissed stale reviews from jvsena42 and ben-kaufman via 28be264 October 16, 2026 22:08
@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 28be264, which closes #992 here: a payment link that crossed the PIN lock, on its own or held behind a sign-in link, now opens for review instead of QuickPay, as on Android. AppScene marks links that arrive while the PIN screen is up, and shouldUseQuickpay skips QuickPay while such a link routes; links opened while unlocked keep QuickPay. PubkyAuthURLSchemeTests.swift covers both routes and the unlocked case. This answers the QuickPay LOW from @jvsena42.

jvsena42
jvsena42 previously approved these changes Oct 16, 2026

@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.

Re-approving at 28be264 ("open locked payment links for review instead of quickpay"), with one new LOW inline. This round was reviewed on Opus because Fable credits ran out.

  • My QuickPay LOW is fixed, and the fix goes further. retainDeepLink(url, whileLocked: settings.pinEnabled && !isPinVerified) marks every link that arrives while the PIN screen is up, matching the condition that shows AuthCheck (AppScene.swift:868). That also covers a lightning: link alone crossing the lock, the out-of-scope case from my earlier comment, now #992. A link delivered while unlocked over an open Authorize sheet is not marked, matching Android. A marked link still opens the Send review (Confirm, Amount or LNURL). QuickPaySpendStore.canApply is untouched.
  • My journey-wording LOW moved to #994.

Device gate: 28be264 — on the iPhone 17 Pro simulator, PIN on, QuickPay on ($5), a Lightning channel, and 1 811-sat invoices:

Case Result
lightning: then sign-in on the PIN screen, PIN, Cancel Authorize first, then Send Confirm (Swipe To Pay), not QuickPay
sign-in then lightning: on the PIN screen, Authorize → PIN → OK Send Confirm, not QuickPay
lightning: alone on the PIN screen, unlock Send Confirm, not QuickPay
unlocked with Authorize open, lightning: QuickPays after Cancel (spending 51 769 → 49 958)
after the previous row, Scan › Paste a fresh invoice QuickPays (53 580 → 51 769), so no stale suppression
signin-with-second-link.xml all three parts pass

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

The LOW can be fixed here or moved to a follow-up issue. Reply on the thread with which one.

Comment thread Bitkit/ViewModels/AppViewModel.swift Outdated
@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 4891907: the crossed-the-lock flag is now a count of locked routes in flight, so two overlapping routes cannot clear it for each other; this answers the LOW from @jvsena42 and adds his concurrency test to PubkyAuthURLSchemeTests.swift.

@ovitrif
ovitrif requested a review from jvsena42 October 16, 2026 23:11

@ben-kaufman ben-kaufman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed 4891907, including both commits since my previous approval. No new material findings. Locked payment links open for review instead of QuickPay, and the in-flight counter preserves suppression during concurrent routing. All 29 focused Pubky routing, payment navigation, and sheet tests pass locally. SwiftFormat and whitespace checks pass.

The full CI unit rerun and E2E check are still pending. I did not rerun the funded Pubky device journey locally. This approval does not mark those checks complete.

@ovitrif
ovitrif enabled auto-merge October 17, 2026 00:09
@ovitrif

ovitrif commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed ee3e45f: merged master (e96c4a7) into this branch, with no conflicts and no change to this PR's own diff. Run Tests failed three times on 4891907, each time on a different test this PR does not touch (SendConfirmationLifecycleTests.testPendingWalletSwitchResumesAfterTags, a BitkitUITests.testExample runner timeout, and BackupServiceTests.testAutomaticBackupDoesNotRepeatUnrelatedFailures). The last one is #988, whose fix is now on master, so this branch picks it up.

@ovitrif
ovitrif requested a review from ben-kaufman October 17, 2026 01:14
@ovitrif
ovitrif merged commit 2dbc5ba into master Oct 17, 2026
27 of 29 checks passed
@ovitrif
ovitrif deleted the fix/pubky-signin-link-kept branch October 17, 2026 05:28
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: a payment link that crossed the pin lock is quick-paid without review bug: a link opened on the pin screen drops a pending pubky sign-in link

4 participants