fix(companion): pairing QR scannable again — cap anchors at 2, EC level L, 768px source

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 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-23 16:46:51 -04:00
parent 4199c43cc9
commit ed2c14c88a

View File

@ -357,10 +357,15 @@ async function buildPairingUrl(): Promise<string> {
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',