From ed2c14c88a4a3019081c5d602220f8f7b8ef91a0 Mon Sep 17 00:00:00 2001 From: archipelago Date: Thu, 23 Jul 2026 16:46:51 -0400 Subject: [PATCH] =?UTF-8?q?fix(companion):=20pairing=20QR=20scannable=20ag?= =?UTF-8?q?ain=20=E2=80=94=20cap=20anchors=20at=202,=20EC=20level=20L,=207?= =?UTF-8?q?68px=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three npub-bearing anchors pushed the pairing URI to ~600 chars (QR ~v23 at EC-M) — phone cameras couldn't lock onto it off a screen. The phone only needs the node itself (LAN p2p) plus one public rendezvous (vps2 preferred), and screen scans don't need EC-M's damage tolerance — L drops a full version tier. Co-Authored-By: Claude Fable 5 --- .../src/components/CompanionIntroOverlay.vue | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/neode-ui/src/components/CompanionIntroOverlay.vue b/neode-ui/src/components/CompanionIntroOverlay.vue index d324d542..a8f4526f 100644 --- a/neode-ui/src/components/CompanionIntroOverlay.vue +++ b/neode-ui/src/components/CompanionIntroOverlay.vue @@ -357,10 +357,15 @@ async function buildPairingUrl(): Promise { const selfAnchor = info.tcp_port ? [{ npub: info.npub, addr: `${params.get('fhost')}:${info.tcp_port}`, transport: 'tcp' }] : [] - const anchors = [ - ...selfAnchor, - ...(info.anchors || []).filter((a) => a.npub !== info.npub), - ].slice(0, 4) + // HARD CAP at 2 anchors: every extra npub adds ~100 URL-encoded chars + // and pushed the QR past what phone cameras decode off a screen + // (3 anchors ≈ 600 chars ≈ QR v23 — observed unscannable 2026-07-23). + // Self + one public rendezvous is all the phone needs; prefer the + // Archipelago-operated vps2 anchor as the public one. + const others = (info.anchors || []).filter((a) => a.npub !== info.npub) + const publicAnchor = + others.find((a) => a.addr.startsWith('146.59.87.168')) ?? others[0] + const anchors = [...selfAnchor, ...(publicAnchor ? [publicAnchor] : [])].slice(0, 2) if (anchors.length) { params.set( 'fanchors', @@ -385,10 +390,13 @@ async function showPairScreen() { pairingUrl.value = await buildPairingUrl() // Large source + a real quiet zone; this QR is scanned by the companion // app's camera, so give it every advantage (see download QR note above). + // EC level L: the payload is long (token + npub + anchors) and screen + // scans don't suffer the damage EC-M protects against — L drops the + // module count a full version tier, which is what makes it scannable. pairQrDataUrl.value = await QRCode.toDataURL(pairingUrl.value, { - width: 512, + width: 768, margin: 3, - errorCorrectionLevel: 'M', + errorCorrectionLevel: 'L', color: { dark: '#111111', light: '#ffffff',