Skip to content

hotfix(qr-scanner): stop clipboard paste-prompt racing the camera (re-target of #2520 to main) - #2531

Merged
abalinda merged 4 commits into
mainfrom
hotfix/qr-scanner-clipboard-preread
Jul 27, 2026
Merged

hotfix(qr-scanner): stop clipboard paste-prompt racing the camera (re-target of #2520 to main)#2531
abalinda merged 4 commits into
mainfrom
hotfix/qr-scanner-clipboard-preread

Conversation

@abalinda

Copy link
Copy Markdown
Contributor

Summary

Re-target of #2520 to main — that PR was merged into dev by mistake; the fix needs to ship to prod as a hotfix. This branch is the same 4 commits cherry-picked onto origin/main (authorship preserved), verified byte-identical to the reviewed/merged #2520 content (git diff origin/dev on all touched files is empty).

Full context, adversarial-review record, corrected Sentry attribution, follow-ups, and the iOS device checklist live in #2520 — read that PR for the story. Short version: the QR scanner's gesture-less clipboard pre-read raised the iOS "Allow Paste" prompt at open and blocked the camera (TASK-20906); this revert + platform-gated rework kills the prompt at open on every surface (Android keeps its chip; iOS goes prompt-free hasStrings + read-on-tap; web/PWA has no pre-read).

Notes for this re-target

QA

Same gates as #2520, re-run on this branch against main: typecheck 0 errors, full jest suite green (7 QRScanner tests incl. the platform-split pin), content parity with the reviewed diff proven by empty git diff origin/dev on touched files.

abalinda and others added 4 commits July 27, 2026 18:24
…prompt

Clipboard.read() on scanner open fires before getUserMedia (camera start
deliberately sleeps 200ms on iOS), so the iOS 'Allow Paste' alert lands
first and blocks the camera permission dialog — getUserMedia never
settles and the 5s watchdog reports a false NotAllowedError ('Camera
start timed out'). Camera only opened on the second attempt, once iOS
remembered the paste grant for the session.

Reverts the pre-read shortcut from 32454bb. The explicit 'Click to
paste' button keeps the paste path on a real user gesture, which is
where the OS prompt belongs.
…-read

Reintroduces the clipboard shortcut removed in #2520 with a platform split
that can't race the camera permission dialog:

- Android native: unchanged from pre-revert — read at open, preview the
  copied EVM address in a one-tap chip (only side effect is the system
  paste toast).
- iOS native: a new app-local ClipboardDetect plugin exposes
  UIPasteboard.hasStrings — metadata only, so no iOS 16+ 'Allow Paste'
  alert. When true, a generic 'Use copied address' chip appears; the real
  Clipboard.read() runs on the chip tap, a user gesture, with the camera
  already live. Older binaries (OTA'd JS) resolve hasStrings to false and
  simply show no chip.
- Web/PWA: no pre-read; the explicit 'Click to paste' stays the fallback.

Needs an iOS binary release to activate the chip (new native plugin,
registered via AppViewController per Capacitor's app-local plugin flow).
… pin the platform split with tests

Review findings on the chip rework: a payment-routing failure inside
handleUsePasteChip landed in the clipboard catch and toasted 'Could not
access clipboard' (hiding the chip), and an empty clipboard on the chip
path used different copy than the 'Click to paste' path for the same
condition. Split the onScan await out of the clipboard try and reuse
handlePaste's empty-clipboard mapping.

New component test pins the load-bearing claim of the platform split:
Clipboard.read() never fires at scanner open off Android-native (the
PEANUT-UI-PYW paste-prompt race), iOS reads only on chip tap, Android
keeps the read-at-open preview.
…and guarded-scan helpers

Review follow-up (kush): the onScan-inside-clipboard-try misreport fixed on
the chip path still lived in handlePaste, the empty/error toast mapping was
duplicated across both handlers, and the Android chip called onScan with no
guard at all (unhandled rejection, no user feedback). One readClipboardText
helper + one scanValue wrapper now serve all three tap paths; tests pin the
two newly guarded ones.
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 27, 2026 4:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78b6d3e1-4357-42c7-ac33-191b8643b6e8

📥 Commits

Reviewing files that changed from the base of the PR and between 85a581f and 549d904.

⛔ Files ignored due to path filters (4)
  • ios/App/App.xcodeproj/project.pbxproj is excluded by !ios/**
  • ios/App/App/AppViewController.swift is excluded by !ios/**
  • ios/App/App/Base.lproj/Main.storyboard is excluded by !ios/**
  • ios/App/App/ClipboardDetectPlugin.swift is excluded by !ios/**
📒 Files selected for processing (4)
  • src/components/Global/QRScanner/__tests__/index.test.tsx
  • src/components/Global/QRScanner/index.tsx
  • src/utils/__tests__/clipboard-detect.test.ts
  • src/utils/clipboard-detect.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/qr-scanner-clipboard-preread

Comment @coderabbitai help to get the list of available commands.

@abalinda
abalinda marked this pull request as ready for review July 27, 2026 16:26
@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6274.15 → 6279.78 (+5.63)
Findings: 0 net (+4 new, -4 resolved)

🆕 New findings (4)

  • high complexity — src/components/Global/QRScanner/index.tsx — CC 37, MI 62.92, SLOC 147
  • medium high-mdd — src/components/Global/QRScanner/index.tsx:171 — QRScanner: MDD 27.4 (uses across many lines from declarations)
  • low high-dlt — src/components/Global/QRScanner/index.tsx:171 — QRScanner: DLT 21 (calls 21 distinct functions — high context load)
  • low missing-return-type — src/components/Global/QRScanner/index.tsx:171 — QRScanner: exported fn missing return type annotation

✅ Resolved (4)

  • src/components/Global/QRScanner/index.tsx — CC 26, MI 64.45, SLOC 104
  • src/components/Global/QRScanner/index.tsx:157 — QRScanner: DLT 16 (calls 16 distinct functions — high context load)
  • src/components/Global/QRScanner/index.tsx:157 — QRScanner: MDD 15.1 (uses across many lines from declarations)
  • src/components/Global/QRScanner/index.tsx:157 — QRScanner: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/utils/clipboard-detect.ts 0.0 4.2 +4.2
src/components/Global/QRScanner/index.tsx 7.4 8.8 +1.3

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2166 ran, 0 failed, 0 skipped, 30.9s

📊 Coverage (unit)

metric %
statements 61.1%
branches 44.5%
functions 50.4%
lines 61.5%
⏱ 10 slowest test cases
time test
2.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.0s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.2s src/utils/__tests__/demo-balance.test.ts › keeps a spent-down balance across a cold start within the TTL
0.1s src/utils/__tests__/demo-balance.test.ts › debits and floors at zero
0.1s src/utils/__tests__/demo-balance.test.ts › resetDemoBalance refills and restarts the TTL window
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@abalinda
abalinda merged commit ad971d7 into main Jul 27, 2026
24 of 26 checks passed
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.

3 participants