fix(companion): first connect no longer stalls on unresolvable .fips dial hints — 0.5.12
Demo images / Build & push demo images (push) Successful in 2m59s

A pairing QR minted while the node UI was browsed over the mesh carried
npub….fips as fhost. Android's system DNS can't resolve .fips, so every
direct handshake dial failed and first connect crawled through public-
anchor discovery (2m35s observed) instead of one LAN hop.

- node UI: resolveServerUrl treats .fips names and IPv6 ULA literals as
  phone-unreachable and substitutes the node's LAN IP (same guard as
  tailnet/localhost)
- app: QR parser falls back to the url param's host when fhost is .fips
- app: peer store never keeps .fips dial addresses
- app: peer aliases slugged for the fips host map (spaces dropped
  "Framework PT"/"Mesh anchor" from name resolution entirely)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-07-24 14:40:44 +01:00
co-authored by Claude Fable 5
parent 385c950f00
commit 7e3d01f633
5 changed files with 44 additions and 11 deletions
Binary file not shown.
@@ -267,12 +267,20 @@ function isTailnetIp(host: string): boolean {
* - a tailnet 100.x address (operator browsing over Tailscale — a scanned
* QR carried one of these on 2026-07-22 and the companion sat there
* dialing an IP the phone had no route to)
* - a .fips name or mesh ULA (operator browsing over the mesh — a scanned
* QR carried npub….fips as fhost on 2026-07-24; Android's system DNS
* can't resolve .fips, so the phone's direct dial died and first
* connect crawled through anchor discovery instead of the LAN)
*/
async function resolveServerUrl(): Promise<string> {
if (IS_DEMO) return DEMO_SERVER_URL
const { hostname, origin } = window.location
const phoneUnreachable =
hostname === 'localhost' || hostname === '127.0.0.1' || isTailnetIp(hostname)
hostname === 'localhost' ||
hostname === '127.0.0.1' ||
isTailnetIp(hostname) ||
hostname.endsWith('.fips') ||
hostname.includes(':') // IPv6 literal — the node's mesh ULA
if (!phoneUnreachable) return origin
try {
const res = await rpcClient.call<{ mdns_hostname?: string; lan_ip?: string | null }>({