112 lines
6.6 KiB
Markdown
112 lines
6.6 KiB
Markdown
---
|
||||
|
|
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.
|