Files
archy/.planning/todos/pending/2026-08-02-companion-handover-clipboard-and-qr-web-side.md

112 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
created: 2026-08-02T12:10:00.000Z
title: Companion 0.5.27 handover — node/web-side clipboard + QR scanner work
area: ui
severity: major
files:
- 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 toggle** — `qr-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.