Files
archy/.planning/todos/pending/2026-08-02-companion-handover-clipboard-and-qr-web-side.md
T
archipelagoandClaude Opus 5 5a11fa7588 docs(todo): capture companion 0.5.27 handover — node/web-side clipboard + QR work
Companion build 0.5.27 (versionCode 47) shims navigator.clipboard natively, so
in-app copy/paste is fixed with zero web changes — but the contract must not be
clobbered (no unconditional re-define, no Object.freeze).

Still open web-side: main.ts's fake readText() makes SendBitcoinModal's Paste
button render and silently no-op in plain-HTTP browsers; 30 writeText call
sites across three inconsistent patterns, ~10 of which toast 'Copied!'
regardless of success; scanner prewarm/torch/constraints/no-reinit.

Also records three factual corrections to docs/qr-scanner-snappiness-handover.md
(ZXing not ML Kit; FORMAT_QR_CODE + KEEP_ONLY_LATEST already in place; do NOT
drop to 720p — 1080p is a deliberate 0.5.22 fix for dense bolt11 QRs).

Routed at Phase 11: the signed-PSBT paste affordance and the scanner items are
the same surface as WALLET-05.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 08:27:03 -04:00

6.6 KiB
Raw Blame History

created, title, area, severity, files
created title area severity files
2026-08-02T12:10:00.000Z Companion 0.5.27 handover — node/web-side clipboard + QR scanner work ui major
neode-ui/src/main.ts (:10-24 clipboard polyfill — the fake readText is the bug)
neode-ui/src/utils/clipboard.ts (to create — one util for 30 call sites)
neode-ui/src/views/web5/utils.ts (:39 safeClipboardWrite — best existing base)
neode-ui/src/components/WalletScanModal.vue (prewarm, torch, don't re-init between panes)
docs/qr-scanner-snappiness-handover.md (three factual corrections — see below)

Problem

Handover from the companion-app workstream (build 0.5.27, versionCode 47), 2026-08-02. All of it is node-repo / neode-ui/ side — none was implemented there, the companion was the only thing changed. Captured verbatim-in-substance so it is not lost.

1. Companion now shims navigator.clipboard — do not clobber it

0.5.27 adds a native ArchipelagoClipboard bridge, injected on every page load, pointing navigator.clipboard.readText/writeText at the Android clipboard. Fixes copy and paste in-app with zero web changes, in both the kiosk WebView and the in-app browser (BTCPay, LND).

Contract to preserve:

  • Shim runs at onPageStarted and onPageFinished; sets window.__archyClipboardPatched = true.
  • Defines navigator.clipboard as configurable if absent, then assigns readText/writeText onto whatever object is there.
  • Reads return via window.__archyClipboardResult(text).
  • Do not unconditionally re-define navigator.clipboard after page load. Do not Object.freeze it. Today's main.ts polyfill is safe only because it is guarded by if (!navigator.clipboard).

2. Web-side clipboard bugs — still open, affect plain browsers

Native is fixed; the same code is broken in any plain-HTTP browser (LAN/mesh — non-secure context, so navigator.clipboard is undefined).

  • neode-ui/src/main.ts:10-24 — the polyfill defines async readText() { return '' }. That makes SendBitcoinModal.vue:425's canReadClipboard true, so "Paste invoice" renders, fires, gets '', and silently does nothing. Fix: drop the fake readText (or define it only when a real source exists) so the button correctly hides.
  • 30 writeText call sites, three patterns:
    • ~8 duplicate their own execCommand fallback — Server.vue:762, Apps.vue:708, Credentials.vue:392, settings/AccountInfoSection.vue, settings/TwoFactorSection.vue.
    • ~10 are bare navigator.clipboard.writeText(x).catch(() => {})ReceiveBitcoinModal.vue:149, SendBitcoinModal.vue:326/:574, OnboardingSeedVerify.vue:201, OnboardingDid.vue:216/:223, settings/BackupSection.vue:270, PeerFiles.vue:1110/:1415 — these show "Copied!" whether or not anything reached the clipboard.
    • views/web5/utils.ts:39 safeClipboardWrite is the best existing base.
  • The execCommand fallbacks are fragile: no focus(), no readonly, no setSelectionRange, and the return value is never checked, so failure is invisible.

Suggested shape: one src/utils/clipboard.ts exporting copyText() / readText() / canPaste(), preferring native bridge → async Clipboard API → hardened execCommand, toasting "Copied" only on real success. Repoint all 30 sites at it.

Paste affordances that don't exist yet (bare textareas today): WalletScanModal.vue paste field, ecash token (views/web5/Web5SendReceiveModals.vue:160), signed PSBT, federation invite code (views/federation/JoinModal.vue:16).

3. QR scanner — web-side items still open

Native items are done in 0.5.27. Remaining on the web side:

  • Pre-warm the camera — start getUserMedia when the modal opens (action pane), not when the scan pane is reached; hide the preview until needed.
  • Torch toggleqr-scanner exposes hasFlash() / turnFlashOn().
  • Constraints{ focusMode: 'continuous', width: { ideal: 1280 } }.
  • Don't stop/start between panes — amount → scan currently re-inits the scanner; keep the paused stream alive for the modal's lifetime.
  • Already done upstream: 10 scans/sec where BarcodeDetector exists.
  • New optional hook: window.ArchipelagoQr?.prewarm?.() — safe to call repeatedly, safe when absent. The companion also self-prewarms on every node page load, so this is a small extra win.

4. Corrections to docs/qr-scanner-snappiness-handover.md (fix the doc)

That doc's native section assumed ML Kit and is wrong on three points — leaving it uncorrected invites someone to "optimise" the scanner backwards:

  • The native scanner uses ZXing (Apache-2.0, on-device, no telemetry), not ML Kit. ML Kit was rejected as a proprietary Google/Play-Services dependency, against project dependency policy. There is therefore no model cold-start to pay.
  • FORMAT_QR_CODE-only and STRATEGY_KEEP_ONLY_LATEST were already in place before this round.
  • Do not drop analysis resolution to 1280×720. 1920×1080 is a deliberate 0.5.22 fix: at 720p, dense bolt11 invoice QRs were undecodable on far-focusing lenses (e.g. Pixel 9a main) while sparse address QRs still read — that was the original "scanner doesn't pick up invoices" report.

What 0.5.27 changed natively, for the record: two-tier decode (cheap centre-70% pass ~18/s plus the thorough full-frame TRY_HARDER + inverted-retry pass ~5/s, replacing a single expensive pass capped ~7/s); camera/decoder prewarm; torch toggle in wallet-scan and pairing scanners; tap-to-focus with 4s suppression of periodic centre autofocus; zoom hunt alternating 1×/1.5× after ~3s with no decode; success haptic on first hit only (so animated QRs don't buzz per frame).

5. Not a web issue, noted for completeness

The companion regained "swipe away in recents = restart": the retained kiosk WebView (kept so remote ⇄ dashboard doesn't reload) is now released when the activity finishes, because the FIPS mesh service keeps the process alive and the static WebView was surviving the swipe. A Restart card was added to the hub menu as the manual path.

Solution

Route into Phase 11 (Wallet Experience & LND UI Parity) rather than a standalone pass — §2's signed-PSBT paste affordance and §3's scanner items are the same surface as WALLET-05 (the PSBT air-gap round trip), and WalletScanModal.vue is named in both. The clipboard utility (§2) is broader than Phase 11 and can land independently; the doc corrections (§4) are a five-minute fix that should not wait for a phase.

Supersedes any overlapping assumptions in docs/qr-scanner-snappiness-handover.md — that doc is now known-wrong on the three points in §4.