fix(qr-scanner): stop clipboard paste-prompt racing the camera; platform-gate the copied-address chip - #2520
Conversation
…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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughQRScanner removes clipboard-based EVM address detection and the conditional detected-address action. ScanRegionOverlay now accepts only the paste handler and displays the standard paste interaction. ChangesQR scanner behavior
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code-analysis diffPainscore total: 6271.93 → 6277.56 (+5.63) 🆕 New findings (4)
✅ Resolved (4)
📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@abalinda this is me who worked on the clipboard feature. During testing I didn't experience the issue you describe. Let me rework the feature. On Android it works, I'll do some tweaks on iOS |
…-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).
@innolope-dev hi! overcommunicating here, you will own this PR and work on it/open a new one that will fix both PWA and native apps? |
|
Pushed 9c78dc2 on top of the revert (folding in what was briefly #2521): reintroduces the clipboard shortcut with a design that can't race the camera prompt — Android keeps the pre-read + address-preview chip exactly as before this PR; iOS gets a prompt-free |
…chip) Port of #2520 + follow-up onto mobile-release, adapted to the localized scanner. Android native keeps the pre-read + copied-address preview chip (paste toast only, nothing blocking). iOS native replaces the un-gestured Clipboard.read() — whose 'Allow Paste' alert raced and blocked the camera permission dialog (PEANUT-UI-PYW) — with a prompt-free UIPasteboard.hasStrings check via a new app-local ClipboardDetect plugin; the localized 'Use copied address' chip reads the clipboard only on tap. Web/PWA gets no pre-read. Older iOS binaries resolve hasStrings false and show no chip. New iOS plugin needs the next binary release to activate the chip.
|
@abalinda I added an update on top of your commit |
… 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.
|
reviewed Pushed
Before the next iOS binary release (CI can't compile Swift — your
One paper trail note: the Sentry step-change on PEANUT-UI-PYW predates your feature reaching prod web (details in the PR body) — the spike is the pre-existing 5s camera watchdog racing real permission prompts, which is now a named follow-up. Your instinct that the feature "worked in testing" was right for the surfaces you tested; the prompt collision was iOS-at-open specifically. |
… pin the platform split with tests Port of 3af6b6d (review fixes on #2520) onto the localized scanner: split the onScan await out of the clipboard try so a payment-routing failure toasts the localized qrProcessingFailed copy instead of 'Could not access clipboard', map an empty clipboard on the chip path to the same clipboardEmpty copy as 'Click to paste', and add the component test pinning that Clipboard.read() never fires at scanner open off Android-native (iOS reads only on chip tap).
Review notes — 1 should-fix + 2 minorOverall solid: platform split is justified, tests pin the right invariant, risks disclosed honestly. Two changes worth folding in before merge (they're really one small change), plus two nits. 1. Should-fix — the misreport bug you just fixed on the chip path still lives in Commit // handlePaste
try {
const { value } = await Clipboard.read()
const text = (value ?? '').trim()
if (text) {
await onScan(text) // <-- onScan is INSIDE the clipboard try
} ...
} catch (err) {
toast.error('Could not access clipboard') // any onScan/routing failure misreports here
}Root-cause fix: pull 2. DRY — clipboard error-mapping duplicated The 3. Minor — onUseDetected={() => onScan(detectedAddress!)} // Android path — no try/catchEvery other tap path now wraps 4. Minor UX (fine to ship) — Bug risk on the whole is low — |
…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.
|
@kushagrasarathe all three folded into
Two new tests pin 1 and 3. On 4 — agreed it's inherent to a content-blind |
kushagrasarathe
left a comment
There was a problem hiding this comment.
Approving. All three review items from the earlier pass are closed in a7a466b86:
handlePasteonScan-misreport fixed (onScan now outside the clipboard try, viascanValue)- error-mapping DRY'd into one
readClipboardTexthelper - Android detected-address chip now guarded (no more unhandled rejection)
Traced the new three-state return contract (null = read failed + already toasted / '' = empty, not toasted / text) — both callers handle it correctly, no double-toast, paste-button raw-text behavior preserved. Two new tests pin the newly guarded paths.
One non-code gate remains before the next iOS binary release (not blocking this merge to dev): the manual Xcode/device pass for the hand-edited pbxproj + AppViewController storyboard swap + new Swift plugin — CI can't compile Swift. Already documented in the PR body.
…ard-preread hotfix(qr-scanner): stop clipboard paste-prompt racing the camera (re-target of #2520 to main)
Summary
The bug (TASK-20906): since 32454bb (2026-07-02) the QR scanner read the clipboard the moment it opened — no user gesture — to offer a copied EVM address as a one-tap target. On iOS that raises the system "Allow Paste" prompt at open: it lands before
getUserMedia(camera start deliberately sleeps 200ms on iOS), so users got a paste prompt instead of a camera, and the camera only appeared on the second attempt. On the iOS web/PWA path the blocked permission dialog also let the 5s watchdog fire a falseNotAllowedError("Camera start timed out").This PR (three commits):
cfd9797cb— byte-exact revert of the pre-read (@abalinda). Kills the prompt-at-open on every surface; the explicit "Click to paste" button (gesture-driven) is untouched.9c78dc2bd— platform-gated reintroduction of the shortcut (@innolope-dev, folded in from feat(qr-scanner): prompt-free iOS clipboard chip; restore Android pre-read #2521): Android native keeps the read-at-open + address-preview chip exactly as before (only side effect is the system paste toast). iOS native probesUIPasteboard.hasStringsvia a new app-localClipboardDetectplugin — metadata-only, provably prompt-free — and shows a generic "Use copied address" chip whose realClipboard.read()runs on tap (user gesture, camera already live). Web/PWA has no pre-read at all. Older iOS binaries running OTA'd JS resolvehasStrings→ false: no chip, no error.3af6b6dc3— review fixes:onScanfailures on the chip path no longer misreport as "Could not access clipboard"; empty-clipboard copy unified with "Click to paste"; new component test pinning the platform split (read never fires at open off Android-native).Fail-safe property: if the native plugin is missing, broken, or unregistered,
hasStringsrejects →false→ no chip, no error, camera unaffected. The Swift code gates only whether the feature appears, never whether the bugfix works.Impact
Follow-ups (separate PRs, not bundled)
useQRScanner.ts:270-278racesscanner.start()against 5s while the user may still be answering the real permission prompt; already fixed on the native path only (useQRScanner.ts:260-265). Dominant cause of PEANUT-UI-PYW.useClipboardSuggestiongesture-gating — same gesture-lessClipboard.read()pattern (1s after mount) on claim/withdraw/request screens; still fires iOS paste prompts there.Risks / release notes
AppViewController(a bad customClass would crash at launch), chip appears with no Allow Paste alert at open, tap-allow pays / tap-deny leaves the camera live, andnpx cap sync iosis run before archiving (the committedCapApp-SPM/Package.swiftis stale — release CI syncs automatically, a raw local Xcode build does not).QA
pnpm typecheck0 errors · 2151 tests green (5 new: platform-split pin + error-attribution + copy) ·npm run buildgreen (Node 22) · pbxproj passes plist lint.capacitorDidLoadbefore any JS, including Capgo-swapped bundles, loads).Screenshots:⚠️ NONE — the fix is OS permission-prompt behavior (not capturable headless); visual delta is the removed/regated chip, fully visible in the diff. Device screenshots belong to the pre-binary-release checklist above.