Back to list
Jul 31 2026

Development Update — July 31

A dense fifteen-PR day across four fronts. The skychat group feature — a long-standing community contribution — landed with its admission control, encryption hardening, and the last open “ask again” item finished. The dual-surface divergence campaign reached its conclusion: skychat, the logs content, and the logs window all now render from one implementation on every surface, with the wasm desktop’s WinBox windows hosting the exact same Angular components the native node page uses. A performance push cut wasm-visor time-to-first-transport by more than half and killed the native visor’s five-minute autoconnect cold start. And the routing examination began yielding concrete controls — a per-type transport-creation policy, a shared cross-network fallback primitive, and a fix for a browser proxy that was dying on a two-route mux.

Skywire: The Skychat Group Feature Lands

3595 refactor(skychat): share the group controller (move to pkg/skychat/group) + expose Leave/Delete/Invite in wasm is step one: the group Manager already was a shared controller compiling under js/wasm, it just lived in cmd/apps/skychat/group — so it moves next to the other shared cores, and three Manager methods the wasm binding was missing (Leave/Delete/Invite) are surfaced. 3631 [WIP] Improve group feature of Skychat is the community contribution (18 commits) it makes room for. 3644 feat(skychat): group admission control + encryption hardening (continues #3631) + “ask again” lands that work, replicated over the pkg/skychat/group relocation that had made the original PR conflict, with all of the original commits and their authorship preserved. It brings group admission control (join requests, approval queue, kick/ban/mute/read-only), rejection of stale roster/admin/mod gossip that could silently undo evictions, multi-admin invites so an offline founder no longer blocks joins, group-key rotation on eviction, sealed group keys at rest, PoW-priced and rate-limited joining, DH-ratcheted epoch keys for DMs, and encrypted governance gossip and file attachments. The new “ask again” item lets a deliberately re-requesting declined peer pay the same PoW and rate-limit gates for a fresh pending record, while a passive re-ask from a denied PK stays terminal — wired across all four surfaces plus a CLI command. 3640 fix: gofmt two skychat CLI files unbreaks the lint lane after the #3595 rebase left two CLI files unformatted from the import reorder.

Skywire: One Implementation on Every Surface

3641 feat(ui,wasmhv): skychat gets its own Chat tab; the wasm desktop’s chat window hosts the SAME Angular component closes the skychat half of the dual-surface divergence campaign. Skychat gets a top-level Chat tab (it had been buried as an Apps sub-tab), an ?embed=1 mode that renders a tab chrome-less, and an ?peer=<pk> deep-link — and browse.js’s 270-line bespoke DOM chat is replaced by a WinBox hosting an iframe of the exact Angular Chat component, ending the old window’s own divergent rendering rules. 3642 feat(wasmhv): the desktop’s visor-log window hosts the SAME Angular Logs tab does the same for logs: the ☰ logs window now iframes the node page’s Logs tab (level chips, module regex, live polling) instead of a bespoke console viewer — so with #3633 (unified content) and this (unified window), every flagged divergence resolves to one implementation. 3646 fix(wasmhv): duplicate Go/TinyGo variant selector inside the embedded chat iframe fixes the fallout: because the chat window now boots the boot-glue inside its iframe too, a second variant selector appeared inside the chat window; injection is now skipped when framed. 3643 feat(wasm/skychat): desktop notifications for the browser visor + skynet-default sends + async-PK windows adds three live-validated fixes — browser desktop notifications for inbound DMs when the tab is hidden or no chat window is open, a switch from a hardcoded 'dmsg' to the compose network selector’s default of skynet (measured faster, 7ms vs 178ms, because a routed transport survives dmsg session churn), and async self-PK resolution so a window opened during boot no longer shows “boot the visor first” on a booted visor.

Skywire: Faster First Transports

3647 perf(wasm): faster first transports — event-driven autoconnect start + fast-retry to target cuts the wasm-visor’s time to first transport from ~25s to ~12s and second transport from ~38s to ~16s. dmsg was already fast (~5s); the gap was the autoconnect loop, which waited a fixed 8s before its first pass even when dmsg was up in 4s, and slept the full 90s interval after a pass that landed too few transports — so it now starts as soon as dmsg has a session and fast-retries on 12s until the edge holds ≥2 transports. 3648 perf(visor): first public-autoconnect pass ~3s after boot, not after 5 minutes is the native analog: autoconnector.Run created a 300s ticker and selected on it before doing any work, so a ticker’s first-tick-after-the-interval semantics meant the first public-visor fetch happened a full five minutes after boot — invisible on a public hub but a five-minute cold start for a fresh client visor. A self-resetting timer now fires the first pass after a 3s initial delay, then resets to the steady-state interval. 3649 perf(autoconnect): dial public visors concurrently (native + wasm) then dials targets with a bounded 8-worker pool in both visors, so a single slow or dead peer no longer stalls the whole pass for its full per-target timeout — and fixes the root cause the previous dev worked around by adding a sync.RWMutex to the wasm WS dial table’s previously-unguarded maps, tested from 16 goroutines under -race.

Skywire: Routing Examination — Concrete Controls

3650 feat(transport): per-type transport-creation policy (disable direct p2p, keep dmsg) is the first change out of the routing examination. The audit found the “dmsg is a relay, exempt it” rule re-derived ad hoc in ~4 places with no shared predicate, and no way to express “allow dmsg, forbid direct p2p.” It adds IsRelay/IsDirect predicates as the single source of the split, two config knobs (no_direct_transports and an advanced transport_create_deny), and enforces them at the one creation chokepoint so every path — autoconnect, route-setup hook, tp add, --direct — is covered, gating only outbound creation. 3651 feat(apps): shared skynet→dmsg dial-fallback primitive; opt-in for proxy + vpn clients is the second: the audit found cross-network fallback implemented unevenly (skychat did it three ways; skysocks-client and vpn-client had none), so appnet.DialWithFallback consolidates the ordered-try pattern with dmsg as the natural last entry, and skysocks-client and vpn-client gain a default-off --dmsg-fallback flag — identical opt-in semantics for both, since the latency and metadata-downgrade trade-off is the same for a proxy stream and a tunnel. 3652 fix(wasm): dial skysocks over a single route, not a 2-route mux fixes the browser proxy, which dialed the exit with MuxRoutes = 2: the group reported “2/2 established” but the yamux session over it immediately read EOF, because a mux aux leg that black-holes beyond the first hop kept being selected and silently lost the SOCKS-handshake bytes. Mirroring the native visor’s single-route dial makes the browser browse clearnet through a mesh skysocks exit — verified live, HTTP 200 with the egress IP the exit’s. 3645 fix(transport): WalkTransports callback ran under tm.mx (silent wedge) + lock-stall watchdog fixes a native visor found wedged with the entire management surface frozen and no log line: WalkTransports ran the caller’s arbitrary callback inside the tm.mx critical section, so a panic (recovered upstream, hence no log) leaked the read lock and RWMutex writer-preference froze everyone behind it. It now snapshots under the lock and runs the callback unlocked, and a serveLockWatchdog probes the lock every 30s and dumps goroutines if it can’t acquire within 20s — turning the whole class of wedge from a silent hang into an actionable log.