Skip to content

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

Merged
ovitrif merged 5 commits into
masterfrom
fix/pubky-signin-link-kept
Oct 16, 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 #1542

Refs:

This PR keeps a pending Pubky sign-in link when another link opens Bitkit before the sign-in is approved or cancelled. The later link now opens after the sign-in instead of dropping it (device logs).

Description

  • Fixes a link opened on the PIN screen dropping a sign-in link that waits there. Deferred scans keep one slot, so the later link either replaced the sign-in link or, when it arrived first, made the waiting sign-in skip its own deferral. A sign-in link now keeps the slot, and the other link waits in one follow-up slot that runs once the sign-in sheet closes. A newer link replaces the one in that slot, a reopened duplicate keeps its QuickPay suppression and payment context, and a newer sign-in link still replaces an older one.
  • Fixes a link opened while the Authorize sheet is open replacing the sheet with Send. The link is deferred, also while the sheet is still being shown, and opens after Authorize or Cancel closes the sheet, the same way a link waits while a sign-in link restores the Pubky session.
  • Changes the earlier rule that the latest locked scan supersedes a sign-in link waiting for the Pubky identity, set with the startup link retention. Now both run, the sign-in first. @ben-kaufman, flagging this since you set that rule.

Out of Scope

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

Manual Tests

N/A

Automated Checks

  • added AppViewModelSendFlowTest.kt — a payment link waits until the open Authorize sheet closes; links opened on the PIN screen in either order open the sign-in first and the payment once; a newer payment replaces the queued one; a newer sign-in link wins over an older waiting one; a payment deferred by the lock and reopened over the Authorize sheet still skips QuickPay
  • updated AppViewModelSendFlowTest.kt — a payment scan made while locked runs after the sign-in link waiting for the Pubky identity, instead of replacing it

@ovitrif ovitrif added this to the 3.0.5 milestone Oct 16, 2026
@ovitrif ovitrif self-assigned this Oct 16, 2026
@ovitrif
ovitrif requested review from a team, ben-kaufman and pwltr and removed request for a team October 16, 2026 18:00
@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-16T18:06:08.063765Z aff356c 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.

@ovitrif ovitrif removed this from the 3.0.5 milestone Oct 16, 2026
@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches Bitkit’s incoming-link and scan flow.

Fix the stale payment replay before merging; a newer payment link can leave an older payment waiting to reopen.

Findings

  1. P1 Replaced payments open again ▶
  2. P2 PIN-screen paths lack tests ▶

Summary

This PR keeps a pending Pubky sign-in ahead of a payment link and delays payment links while Authorize is open.

  • A later link waits for Pubky sign-in to finish before opening.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Locked: sign-in plus payment A"] --> B["Unlock drains sign-in"]
    B --> C["Authorize opens; payment A remains"]
    C --> D["Payment B arrives"]
    D --> E["B enters deferredScan; A remains in scanAfterPubkyAuth"]
    E --> F["Close Authorize"]
    F --> G["Payment B opens"]
    G --> H["Close Send"]
    H --> I["Older payment A opens"]
Loading

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

Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Outdated
Comment thread app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt

@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: aff356cd97

ℹ️ 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 app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Outdated
Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Outdated
Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Outdated
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from 45abb40 (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 475f44b, answering the Greptile and Codex findings:

  • One follow-up slot holds through the whole sign-in: a later link replaces it, so an older payment never replays after a newer one.
  • A waiting sign-in link goes ahead only of a non-sign-in link, so the newest sign-in link opened on the PIN screen wins.
  • A link is also deferred while the Authorize sheet is still being shown.
  • AppViewModelSendFlowTest.kt adds the PIN-screen cases in both orders, the newer-payment case and the newer-sign-in case.

Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt

@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 a pending Pubky sign-in link when another link opens: the sign-in holds the deferred slot, a non-sign-in link waits in a single follow-up slot (scanAfterPubkyAuth), and links that arrive while the Authorize sheet is showing or transitioning in are deferred until it closes. Reviewed 475f44b, full tier, reasoned from the code and CI (not built here).

What I checked, and 5 candidates I ruled out

Read in full: launchScan, deferLockedScan, enqueueDeferredScan, flushDeferredScan, markDeferredScanUnlocked, invalidatePaymentRequestPresentation, showSheet/hideSheet, handlePubkyAuth, processDeeplink, PubkyAuthApprovalSheet dismiss paths, and the four new or changed tests in AppViewModelSendFlowTest.kt.
Diffed aff356c..475f44b against the five Greptile/Codex threads: older payment replay, newer sign-in on the PIN screen, the sheet-transition window and the PIN-screen test gap are all addressed by that commit and covered by the added tests.

Ruled out

  • Follow-up slot orphaned and replayed after an unrelated scan: it can only outlive its sign-in if the sign-in job is cancelled, and a sign-in started from flushDeferredScan is preserveUntilComplete, so later scans queue behind it instead of cancelling it. Every normal completion (sheet shown, identity toast, init timeout) goes through invokeOnCompletion to flushDeferredScan.
  • Stale transitionSheet deferring links forever: hideSheet nulls sheetTransitionJob so the completion handler skips the reset, but isPubkyAuthSheetShownOrPending also requires sheetTransitionJob?.isActive, so the stale value is never read.
  • QuickPay auto-paying a link deferred behind an open Authorize sheet: an unlocked link already QuickPays when handled immediately, and the existing deferral behind a running scan does not suppress it either. Links deferred across the lock still get suppressQuickPay through markDeferredScanUnlocked, which now covers both slots.
  • Duplicate check ignores the follow-up slot: a repeat of the same link only replaces the queued one with itself; the only difference is QuickPay on a link the user tapped again while unlocked, the same as a fresh tap.
  • Incoming Paykit request jumping the queue: hasDeferredScan counts both slots, so isPaymentRequestPresentationBlocked holds while a follow-up is waiting.

CI: lint, detekt, build-local and build (which runs testDevDebugUnitTest) green on 475f44b; the local e2e shards were still running at review time.

Merge confidence: 4/5, the queue paths of the 7,800-line AppViewModel.kt were read by function rather than whole, and the e2e shards had not finished.

@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 475f44b: no code findings. I reviewed it as a deep-link and auth change.

Both cases in #1545 are fixed:

  • Locked, sign-in link then a bitcoin: link. enqueueDeferredScan puts the payment in scanAfterPubkyAuth (AppViewModel.kt:3239), and the reverse order swaps them (:3244). Unlock marks both as suppressQuickPay, and flushDeferredScan runs the sign-in first. Every way out of the Authorize sheet (Authorize, Cancel, swipe, Back, close after success) ends in hideSheet() → flush → the payment, which goes through handleScan to review with no QuickPay. A failed sign-in also completes and flushes.
  • Unlocked with Authorize shown or in transition. A non-auth scan is deferred (:3062; isPubkyAuthSheetShownOrPending() covers the transition via transitionSheet, :6655-6656). A newer sign-in link still replaces the sheet, as the body states.

Checked and clean:

  • No new silent-drop path. Each slot keeps the newest link. Dismissing a payment request clears both slots (:1895-1900).
  • No queue growth: there are two fields, both replaced, and the invariant "non-auth deferredScan ⇒ scanAfterPubkyAuth == null" holds.
  • The kept link lives in memory only and needs the Authorize sheet, which re-checks the requester and asks for local auth.
  • transitionSheet is cleared with its job, and hideSheet() nulls the job, so a stale transition cannot keep links deferred.
  • The 5 new tests cover sheet-open deferral, both PIN orders, newer-payment replacement, newer-sign-in precedence, and a locked payment behind a sign-in awaiting identity.
  • Not performance-relevant (two nullable fields under the existing lock).

LOW, journeys: this changes the order users see sheets in, but #### Journeys adds none, and the body says the manual tests need capabilities the table lacks. journeys/README.md:138 lists am start -a android.intent.action.VIEW -d "<uri>". security/pin-lock-on-resume.xml drives the PIN pad, and pubky-auth/signin-cold-start.xml already does force-stop, sign-in link, unlock and PubkyAuthAuthorize. Extending signin-cold-start.xml with "open a bitcoin: link on the PIN screen → Authorize first, then Send", plus one case with Authorize already open, would make both repros a journey. Port it to iOS, or record it in the cross-platform table.

Device gate (partial): 475f44b — not run yet. Pending, with PIN on and a Bitkit-held identity:

  1. A sign-in link, then a bitcoin: link on the PIN screen, in both orders. Expect Authorize, then Send after close, with the Queuing 'deeplink' scan after a sign-in link log line and no Replacing deferred scan.
  2. A bitcoin: link while Authorize is open. Expect the sheet to stay and Send to open after Cancel.

The device result will follow.

@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 747075f: a link reopened while a sign-in is pending now merges with the queued duplicate, keeping its QuickPay suppression and payment context, as @ben-kaufman asked. AppViewModelSendFlowTest.kt adds the case.

@ovitrif
ovitrif requested a review from ben-kaufman October 16, 2026 18:20
@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

LOW, journeys: this changes the order users see sheets in, but #### Journeys adds none

@jvsena42 added in 685c9d4: signin-with-second-link.xml covers a bitcoin: link opened on the PIN screen in both orders and one opened over the open Authorize sheet, each expecting Authorize first and Send after it closes. The Journeys list now names it, and the manual tests are gone. The cross-platform table records it as not ported while I check iOS on a simulator; I will update that row with the result.

@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 45abb40: the iOS check reproduced the drop on the PIN screen, so the cross-platform row and the journey now point at synonymdev/bitkit-ios#985 instead of a pending check. Docs only.

@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 45abb40. My journeys LOW is addressed: journeys/pubky-auth/signin-with-second-link.xml is added and listed.

Delta since my code scan at 475f44b: 747075f moves the duplicate merge into mergeDuplicate and applies it to the follow-up slot too. A link re-delivered after crossing the lock now keeps suppressQuickPay, which can only become true through the merge, and keeps its contact payment context. Clean.

Device gate: 45abb40 — journeys: the PR's two repro cases pass on Android (CI APK, fresh profile, 200k funded, PIN on). The new journey file itself was not driven step by step. perf: not relevant. l10n: no UI text changed. No crash or ANR.

  • Sign-in link, then bitcoin: on the PIN pad. Authorize shows first and Send stays hidden. After Cancel, Send opens with 10 000 sats, and there is no Replacing deferred scan:
    18:53:11.246 INFO  [AppViewModel.kt:3258] Queuing 'deeplink' scan for deferred handling: 'redacted#11…'
    18:53:17.473 INFO  [AppViewModel.kt:3240] Queuing 'deeplink' scan after a sign-in link: 'redacted#e7…'
    18:53:24.332 DEBUG [AppViewModel.kt:3096] Starting scan from 'deeplink': 'redacted#11…'
    18:53:45.674 DEBUG [AppViewModel.kt:3096] Starting scan from 'deeplink': 'redacted#e7…'
    
  • Reverse order. It passes after a re-run: Queuing 'deeplink' sign-in link first (:3246), Authorize first, then Send. The first attempt kept the queue order right, but the restore stayed deferred with shared_state_busy for the whole 60 s wait, so no sheet opened and Send followed. That is #1541, which #1543 fixes. This branch does not include it, and the two were not run together.
  • Authorize open, then bitcoin:. The sheet stays, with SendNumberField absent 5 s later, and Send opens after Cancel.

⚠️ Not verified: QuickPay with a lightning: invoice (needs a usable channel), and the new journey file driven step by step.

CI: 15 checks green at 45abb40.

@ben-kaufman

Copy link
Copy Markdown
Contributor

@ovitrif I am fine with sign-in first, then opening the queued payment after sign-in is approved or cancelled. The queued payment should retain its normal confirmation and QuickPay safeguards, including any QuickPay suppression already applied.

@ovitrif

ovitrif commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

The queued payment should retain its normal confirmation and QuickPay safeguards, including any QuickPay suppression already applied.

@ben-kaufman it does: the queued payment goes through the normal scan path to confirmation once the sign-in sheet closes. Since 747075f a link queued across the lock keeps its QuickPay suppression, also when it is reopened and merged as a duplicate, and keeps its payment context. AppViewModelSendFlowTest.kt covers the reopened case going to confirmation, not QuickPay.

@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 45abb40 and the fixes for the existing link-ordering findings. No blocking findings. All 449 focused send-flow tests pass, including ordering, replacement, transition, and duplicate-context cases. Current hosted checks are green. Device journeys and combined integration with the other sign-in PR were not rerun as part of this review.

@ovitrif
ovitrif merged commit d4a8c69 into master Oct 16, 2026
21 checks passed
@ovitrif
ovitrif deleted the fix/pubky-signin-link-kept branch October 16, 2026 20:18
@ovitrif

ovitrif commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Device check on master

Ran the Pubky sign-in journeys on master at 722c824 (this PR, #1543 and #1552 together), on the CI regtest APK, an Android 16 emulator, a new wallet with a staging Pubky profile, Paykit on and the PIN on. All five cases pass.

  • signin-cold-start.xml — loading toast, Authorize sheet, PIN, PubkyAuthOK
  • signin-with-second-link.xml, sign-in link then bitcoin: on the PIN pad — Authorize first, Send with 10 000 sats after back (Queuing 'deeplink' scan after a sign-in link)
  • signin-with-second-link.xml, bitcoin: then sign-in link on the PIN pad — Authorize first, then Send (Queuing 'deeplink' sign-in link first)
  • signin-with-second-link.xml, bitcoin: over the open Authorize sheet — the sheet stays, Send opens after back
  • Deferred restore, 8 cold starts — every run opened Authorize and reached PubkyAuthOK, none showed "Couldn't Load Your Pubky Profile". Run 1 hit the deferral (Deferred session restoration ×5, then Continuing sign-in link with the stored Pubky key, Authorize about 3 s later, the session restored after approval); runs 2–8 restored without one.

Replacing deferred scan appears in no run. One journey step is stale: signin-with-second-link.xml checks the PIN pad by testTag PinPad, which android layout does not show; the pad's keys (N0–N9) do.

Cold startPIN pad: sign-in, then bitcoin:PIN pad: bitcoin:, then sign-in
case1-signin-cold-start-2x.mp4
case2-2x.mp4
case3-2x.mp4
bitcoin: over AuthorizeDeferred restore (run 1)
case4-2x.mp4
case5-run1-2x.mp4

Recorded at 2x.

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 link opened during a pending pubky sign-in drops the sign-in

4 participants