Commit Graph
100 Commits
Author SHA1 Message Date
archipelagoandClaude Fable 5 e074a117d2 feat(pine): node-status endpoint + Claude voice brain + mesh announcements + wake-word groundwork
- GET /api/pine/status: public tier (version/uptime/bitcoin/mesh facts) for
  the Pine page; bearer-token tier (Lightning balances, latest mesh message)
  for the seeded Home Assistant sensors. Token minted 0600 at
  secrets/pine-status-token; nginx location ships via the bootstrap
  self-heal + canonical ISO conf. Replaces the per-node socat forwarder and
  bitcoind RPC credentials living in HA's configuration.yaml.
- pine_ha seeder: REST sensors + four ask-Archy intents (block height,
  peers, sync %, Lightning balance) with LLM tool descriptions; Claude
  conversation agent (anthropic entry from the node's claude-api-key) set as
  pipeline default with prefer_local_intents so exact phrases stay local and
  fuzzy ones tool-route through Claude; mesh-message announce automation on
  every Assist satellite; bounded config markers with legacy-block migration.
- pine-openwakeword joins the pine stack (all lifecycle sites) — on-node
  wake-word engine groundwork for the custom "Yo Archy" model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:59:34 -04:00
archipelagoandClaude Fable 5 b288be314c feat(federation): presence-signing overlay on discovery enable
Demo images / Build & push demo images (push) Failing after 1m43s
Enabling Nostr discoverability now opens a signer overlay (same visual
language as the app NIP-07 identity picker) before anything is published:
a locked signer row fixed to the node's discovery key — deliberately not
pickable, personal identities are never used — plus the exact signed
content (DID, npub, software version, kind 30078/NIP-33 format) and a
Sign & Publish confirm. Disabling stays immediate. Also seeds the mock
with discovery OFF to match the production default, and defaults
dwnSyncLabel to 'Unknown' when the backend omits sync_status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:55:06 -04:00
archipelagoandClaude Fable 5 72fcf96016 chore(mock): node.nostr-pubkey returns exact real shape (hex + genuine npub)
Demo images / Build & push demo images (push) Failing after 1m38s
The mock previously returned a placeholder string with no nostr_npub, so the
new Federation signing-details panel showed 'loading…' forever against the
mock. Now returns a distinct discovery key — 64-char hex plus its real
NIP-19 bech32 encoding (verified against the official test vector) —
mirroring production where the node discovery key is separate from the
personal identity keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:38:52 -04:00
archipelagoandClaude Fable 5 50d5d4d132 feat(federation): signing-details panel on the Nostr discoverability strip
Demo images / Build & push demo images (push) Failing after 1m41s
Adds a 'Signing details' disclosure showing who signs the presence event —
a locked identity row (styled like the NostrIdentityPicker overlay) fixed to
the node's own discovery key, deliberately not pickable so personal
identities can never be used for node discovery — plus a human-readable
breakdown of the signed content: DID, signing npub, software version, and
the event format (kind 30078, NIP-33 replaceable, d-tag archipelago-node),
with a note that every event carries a NIP-01 Schnorr signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:18:58 -04:00
archipelagoandClaude Fable 5 73923d0ffd feat(ui): mobile home — wallet card moves up under My Apps
Demo images / Build & push demo images (push) Failing after 1m40s
On single-column (mobile) the dashboard cards now order My Apps, Wallet,
Cloud, Network; desktop keeps its existing two-column layout via
lg:order-none resets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:11:39 -04:00
archipelagoandClaude Fable 5 905e9cdb98 chore: Cargo.lock version bump missed in v1.7.110-alpha release commit
Demo images / Build & push demo images (push) Failing after 2m53s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 01:25:55 -04:00
archipelagoandClaude Fable 5 46cfc2ccd7 chore(catalog): sync presentation catalogs — HA 2026.7.3, Pine 1.2.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 01:25:55 -04:00
archipelago aaec25e53b chore: release v1.7.110-alpha
Demo images / Build & push demo images (push) Successful in 2m50s
2026-07-21 14:50:29 -04:00
archipelagoandClaude Fable 5 172b1f3e9c docs: sync What's New modal with v1.7.110-alpha changelog
Demo images / Build & push demo images (push) Successful in 2m50s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:22:57 -04:00
archipelagoandClaude Fable 5 c7fe688607 docs: changelog for v1.7.110-alpha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:20:16 -04:00
archipelagoandClaude Fable 5 24b3d33ac3 fix(ui): Cashu/Ark emoji rendered as tofu boxes on kiosk TVs
Demo images / Build & push demo images (push) Successful in 2m50s
The kiosk image ships no color-emoji font, so the wallet card's 🥜 and 
drew as empty squares on TVs. Swap them for inline stroke SVGs matching
the neighbouring rows (wallet card + scan modal), and add
fonts-noto-color-emoji to the ISO package set so remaining emoji across
the UI (peer files, chat, content) render on future installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:55:36 -04:00
archipelagoandClaude Fable 5 b23f46c3d8 fix(kiosk): companion remote control now reaches kiosk displays
Demo images / Build & push demo images (push) Successful in 2m54s
App.vue skipped starting the browser-side remote relay on kiosks, citing a
system-level xdotool injector that no longer exists — the backend's
remote-input path is relay-only (validate + broadcast). With no consumer,
companion input was silently dropped on TVs. Start the relay on kiosks
like everywhere else; rides the next release + ISO via the web bundle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:49:10 -04:00
archipelagoandClaude Fable 5 2a376ab275 feat(scan): camera on companion + insecure origins — WebView bridge & photo fallback
Demo images / Build & push demo images (push) Successful in 2m55s
Two gaps kept the wallet QR scanner camera-less outside a desktop browser:

- Companion app: the WebView's default WebChromeClient silently denies
  getUserMedia. Both WebViews (main + in-app overlay) now implement
  onPermissionRequest — granting video capture only, requesting the
  app-level CAMERA permission on first use (manifest already declares it).

- Plain-http origins (mobile web/PWA on a LAN node): browsers hide
  navigator.mediaDevices entirely, no permission can bring it back. New
  "Take photo of QR" fallback uses <input capture=environment> — the
  native camera needs no secure context — and decodes the shot locally
  with qr-scanner's scanImage. Live preview still used when available.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:44:13 -04:00
archipelagoandClaude Fable 5 5982fceb7c copy: unify Zeus channel text — Olympus node + on-chain sats limits
Demo images / Build & push demo images (push) Successful in 2m47s
All Zeus channel surfaces now read: "Open a channel with Zeus Olympus
node and start sending and receiving Lightning payments. Minimum
150,000 · maximum 1,500,000 on-chain sats required."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:27:05 -04:00
archipelagoandClaude Fable 5 5951d31637 fix(kiosk): pin AIUI to dark theme on kiosk displays
AIUI persists aiui-theme in localStorage and a stored value beats its
prefers-color-scheme fallback, so kiosk profiles from before the launcher
forced a dark color scheme keep white panels forever. Kiosk boot now pins
the same-origin key to dark, healing existing profiles on next load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:27:04 -04:00
archipelagoandClaude Fable 5 dc55ef8a54 fix(kiosk): restore tab-change animations with 2D transitions
The onboarding-era :global(html.kiosk-mode .view-wrapper) transform:none
rule also matched the dashboard's route wrappers, freezing every list<->
detail depth transition into a jump cut on kiosk. Scope that rule to the
onboarding viewport and give the dashboard kiosk-safe 2D equivalents
(scale + opacity, no translateZ/blur/preserve-3d) that the software
compositor can animate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:27:04 -04:00
archipelagoandClaude Fable 5 97358d2314 feat(kiosk): display-size presets in Settings (Auto/Large/Balanced/Native)
system.kiosk-display.get/set RPCs write /etc/archipelago/kiosk-display.conf
(sourced by the kiosk launcher) and try-restart the kiosk so the choice
applies immediately. The Settings section only appears on nodes that have
the kiosk unit installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:27:04 -04:00
archipelagoandClaude Fable 5 1b78da1d7a feat(lnd): accept amount_sats on lnd.payinvoice for zero-amount invoices
Zero-amount BOLT11 invoices need the payer to supply the amount; forward
it to LND REST as the amt field. The wallet scan modal unlocks its amount
field for such invoices and passes the user's entry through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:15:35 -04:00
archipelagoandClaude Fable 5 0213da3fc5 fix(kiosk): background art rendered black — never paint a background on <html>
Demo images / Build & push demo images (push) Successful in 2m44s
html.kiosk-safe-area set background:#000 on the root element. With a root
background, body's background stops propagating to the canvas and paints
as a normal block background instead — which by CSS paint order covers
negative-z-index descendants. The dashboard/login art lives in
.bg-perspective-container at z-index:-10, so every kiosk display showed
solid black behind the UI while desktop browsers (no root background)
showed the art. Verified live on the Framework 4K TV via CDP: making the
root transparent restores the art instantly. Keep the black on body only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:14:49 -04:00
archipelagoandClaude Fable 5 8968d41ca9 feat(wallet): QR scan modal — camera + animated QR, wired into Home/Send/Receive
New WalletScanModal scans QR codes with the device camera (BarcodeDetector
with jsQR fallback) including animated/multi-frame QRs via qrloop, then
routes what it saw: BOLT11 invoices -> lightning pay (amount locked from
the invoice when present), bitcoin:/BIP21 URIs -> on-chain send, Cashu
tokens -> redeem, Fedimint invites -> join. Entry points: camera button on
the wallet card (far right), and a Scan button centered between the
Close/Send and Close/Receive buttons of both modals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:14:31 -04:00
archipelagoandClaude Fable 5 be5122048b fix(kiosk): correct --window-size to DIPs + panel-derived layout target
The kiosk window was sized in physical pixels, but Chromium interprets
--window-size in DIPs: with any device-scale > 1 the window painted
scale-times larger than the panel, and with no window manager in the
kiosk X session --kiosk/--start-fullscreen could not snap it back. On a
4K TV at scale 3 that meant an 11520x6480 window — 9x the panel area —
which blew the compositor tile-memory budget ("tile memory limits
exceeded, some content may not draw") and froze the screen on the last
painted frame; smaller scales cropped content off the right/bottom.

- Size the window in DIPs (panel / scale) so DIPs x scale = the panel.
- Panels >=2560 wide now target a 1920-wide layout (4K -> scale 2.0,
  validated on the Framework's 72" 4K TV: spacious desktop layout at 2x
  sharpness); smaller panels keep the 1280 target (1080p TV -> 1.5).
- Optional ARCHIPELAGO_KIOSK_MAX_WIDTH cap: drop to the best <=N-wide
  xrandr mode for weak hardware (TV upscales instead of us rasterizing).
- Source /etc/archipelago/kiosk-display.conf so the upcoming Settings
  display-resolution picker can manage these knobs per node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 12:46:15 -04:00
archipelagoandClaude Fable 5 918b0275a9 feat(ui): MeshCore RF frequency-plan presets by country + Custom entry
Demo images / Build & push demo images (push) Successful in 2m47s
The RF section shipped as four bare fields; restore a prepopulated plan
list (dropdown) with Custom unlocking free entry, per operator direction.
Preset labels carry the full values, so the number fields render only in
Custom mode — no duplicated display. Presets limited to verifiable
sources: the repo's MeshCore community plans (EU 868/433), the MeshCore
firmware's own build defaults (platformio.ini arduino_base 869.618/62.5/
SF8 CR5; companion example 915.0/250/SF10/CR5), and the operator-attested
Portugal plan (869.618/62.5/SF8/CR8). Stored params are matched back to a
named preset on load; hand-editing flips the dropdown to Custom.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 08:36:59 -04:00
archipelagoandClaude Opus 4.8 a71a18bffd fix(pine): serve the Connect-to-WiFi button over an http->https redirect
Demo images / Build & push demo images (push) Successful in 2m47s
The launcher must be a secure context for Web Bluetooth, but the UI opens local
apps as http://host:port (resolveAppUrl), so an https-only launcher (v1.1.0)
would break the Open button with a TLS mismatch. Serve http on 10380 (the Open
target) that 301-redirects to the https listener on 10381, so the new tab lands
on https where navigator.bluetooth is available. Bump pine 1.1.0 -> 1.1.1;
re-sign the catalog (only the pine entry changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:32:52 -04:00
archipelagoandClaude Opus 4.8 24cab326e2 feat(pine): interactive "Connect Pine to WiFi" button + republish catalog
Demo images / Build & push demo images (push) Successful in 2m47s
Replace the launcher's text instructions with the real Improv-over-BLE
Web-Bluetooth provisioner (a "Connect Pine to WiFi" button, SSID/password
fields, live log) ported from the pine repo. Web Bluetooth needs a secure
context, so the launcher now serves HTTPS (self-signed): :80 (published 10380,
the Open target) 301-redirects to :10381, and open_in_new_tab keeps BLE out of
an iframe. Bump pine 1.0.0 -> 1.1.0.

Regenerate + re-sign releases/app-catalog.json (only the pine entry changes vs
v1.7.109) so nodes reconcile the button page live. Signed by the release-root
key did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 08:25:24 -04:00
archipelagoandClaude Fable 5 811ac1ce50 chore(ota): go live with the signed v1.7.109-alpha manifest
Supersedes the v1.7.108 hold now that the manifest is signed and the
release assets are being published.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 07:53:12 -04:00
archipelagoandClaude Fable 5 69b1a45872 chore(ota): hold the live manifest at v1.7.108 until v1.7.109 is signed and its assets exist
A prep commit accidentally carried the in-progress v1.7.109 manifest onto
main, so nodes saw an unsigned 1.7.109 with download URLs that 404. Restore
the signed v1.7.108 manifest until the v1.7.109 publish flow completes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 07:52:09 -04:00
archipelago 9823e76e8f chore: release v1.7.109-alpha 2026-07-21 07:50:10 -04:00
archipelagoandClaude Fable 5 8d4f6cb75e docs(release): third v1.7.109 bullet (Pine voice assistant) + version-bump prep
Demo images / Build & push demo images (push) Successful in 2m44s
The release gate requires ≥3 curated changelog bullets; the Pine app is
user-facing and deserved one anyway. Carries the version bumps and manifest
from the aborted cut so the tree is clean for the re-run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 07:34:26 -04:00
archipelagoandClaude Fable 5 4bac839fb3 test(ui): did-wallet launch port follows its manifest (8088)
Demo images / Build & push demo images (push) Successful in 2m49s
The pine catalog regeneration (c1dfc667) refreshed generatedAppSessionConfig
from the manifests, where did-wallet publishes host port 8088 — the test's
8083 expectation was stale. The test asserts manifest-generated ports are
used, so it must track the manifest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:59:47 -04:00
archipelagoandClaude Fable 5 ea80815c98 style: cargo fmt over the pine container list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:55:19 -04:00
archipelagoandClaude Fable 5 4348581681 docs(release): curated v1.7.109-alpha changelog + What's New
Demo images / Build & push demo images (push) Successful in 2m47s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:54:05 -04:00
archipelagoandClaude Fable 5 999a100531 feat(mesh): RF-params settings UI + expose lora_radio_params in mesh.status
Demo images / Build & push demo images (push) Has been cancelled
Device panel gets MeshCore RF fields (frequency MHz, bandwidth kHz, SF, CR)
wired to mesh.configure's validated lora_radio_params; shown only for
MeshCore radios, all-empty leaves the radio's flashed settings untouched.
Human units in the UI, firmware field units on the wire. Per operator
direction the panel's static stat rows duplicating editable fields (name,
region, channel) are dropped — the editable control is the display. Stale
'adjust via a MeshCore client' hints updated: the node programs the radio now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:53:10 -04:00
archipelagoandClaude Opus 4.8 a5810d4e51 release: publish signed app-catalog with the Pine voice-assistant app
Regenerate + sign releases/app-catalog.json (65 apps) with the pine,
pine-whisper and pine-piper manifests embedded, so nodes can install the Pine
stack straight from the signed registry (raw URL on main). Signed by the
release-root key did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 06:29:48 -04:00
archipelagoandClaude Fable 5 4c3cd4b6ad feat(mesh): operator-configurable Meshcore LoRa PHY params (freq/bw/sf/cr)
Archy set no radio params on Meshcore devices — SF/CR/BW/freq lived only in
device firmware, so a radio on the wrong preset could not be fixed from the
node (it hears RF energy but demodulates nothing; the fleet hit exactly this
on an RNode: docs/RETICULUM-TRANSPORT-PROGRESS.md item 4).

- protocol.rs: build_set_radio_params — wire format verified against the
  MeshCore companion firmware handler (CMD_SET_RADIO_PARAMS=11:
  [11][freq:u32 LE MHz*1000][bw:u32 LE kHz*1000][sf][cr]); byte-layout test
  pinned to the Portugal preset 869.618 MHz / 62.5 kHz / SF8 / CR8.
- serial.rs: MeshcoreDevice::set_radio_params (device reboots on OK).
- MeshConfig.lora_radio_params: Option — None (default) leaves the radio
  untouched, so only explicitly-configured deployments are affected.
- session.rs: provision on connect, gated on a persisted last-applied marker
  (SELF_INFO offsets shift across firmware versions) + the same attempt cap
  as region/channel so a refusing radio never reboot-loops.
- mesh.configure RPC: lora_radio_params arm with firmware-range validation.

mesh tests: 114/114 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:10:45 -04:00
archipelagoandClaude Opus 4.8 c1dfc6672c feat(pine): surface Pine in the app catalog + UI
Demo images / Build & push demo images (push) Successful in 2m48s
- catalog.json (app-catalog + neode-ui/public): one "pine" entry, home
  category; engines stay out of the catalog
- appsConfig.ts: pine → home category, pine-whisper/pine-piper → SERVICE_NAMES
  (render in Services, not as extra cards), pine-* icon fallback
- generatedAppSessionConfig.ts: regenerated (pine port 10380 + titles +
  open-in-new-tab), via scripts/generate-app-catalog.py

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 05:14:32 -04:00
archipelagoandClaude Opus 4.8 8791ef60f5 feat(pine): wire the Pine 3-container stack into the orchestrator
Register "pine" as a manifest-driven stack (like netbird) across every
lifecycle site so install/stop/start/restart/uninstall/crash-recovery treat
the launcher + two Wyoming engines as one app:

- stacks.rs: pine_stack_app_ids() + install_pine_stack() (orchestrator-first,
  adopt existing, else bail — no hardcoded fallback)
- install.rs: dispatch package_id == "pine" to the stack installer
- app_ops.rs: canonical stack-member table + owning_package STACKS
- dependencies.rs: startup_order + needs_archy_net
- config.rs: all_container_names
- crash_recovery.rs: auto-start members + a StackRecoverySpec (anchor
  pine-whisper) so a genuinely-installed stack self-heals but orphan debris
  does not crash-loop
- docker_packages.rs: exclude the engines from the apps list

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 05:14:32 -04:00
archipelagoandClaude Opus 4.8 1730d02003 feat(pine): add Pine voice-assistant app package (manifests + icon)
Package the PineVoice/Wyoming local voice assistant as ONE store app "Pine"
that runs three rootless containers:

- pine-whisper — Wyoming faster-whisper STT (docker.io/rhasspy/wyoming-whisper
  3.4.1, model base-int8/en), publishes :10300
- pine-piper   — Wyoming Piper TTS (2.2.2, voice en_GB-alba-medium),
  publishes :10200
- pine         — nginx launcher serving a self-contained setup/status page
  (WiFi provisioning + Home Assistant wiring instructions), the Open target

The two engines publish their Wyoming ports so Home Assistant reaches them via
host.containers.internal. All rootless (cap-drop=ALL, no_new_privileges),
manifest-driven — no hardcoded podman/installer. The engines are internal stack
members (added to the drift-check INTERNAL_MANIFEST_IDS allowlist); only "pine"
carries a catalog entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 05:14:32 -04:00
archipelago 146a3bc738 chore: release v1.7.108-alpha
Demo images / Build & push demo images (push) Successful in 2m48s
2026-07-21 03:42:21 -04:00
archipelagoandClaude Fable 5 302f880d99 docs(release): fold everything into v1.7.108-alpha changelog + What's New
Demo images / Build & push demo images (push) Successful in 2m46s
Supersedes the unpublished v1.7.107. Covers the multi-anchor FIPS fix, WiFi
self-heal, mesh fast-rejoin, kiosk TV-scaling, kiosk dark theme / VT hints /
UTF-8 logo / power-button hardening, and the ISO build fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:59:23 -04:00
archipelagoandClaude Fable 5 217eedc093 fix(kiosk): correct VT hints, UTF-8 logo, ignore accidental power-off
Three fresh-install kiosk issues found on a Framework node:
- MOTD said 'Ctrl+Alt+F7 for kiosk' / 'F1 for terminal', but the kiosk's
  Xorg runs on vt1 — so F7 was an empty black VT (the reported black screen
  and the failed 'return to kiosk'). Corrected: kiosk=F1, terminal=F2.
- The UTF-8 block-glyph logo corrupted intermittently because returning from
  the kiosk can leave the console VT out of UTF-8 mode; force it with ESC%G.
- A short power-button press shut the node down instantly. Ignore the short
  press, keep long-press poweroff, so accidental brushes don't kill the node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:48:14 -04:00
archipelagoandClaude Fable 5 d79edb3a5c fix(ui): censor 'Fuck IPs Mesh' -> 'F*ck IPs Mesh'
Demo images / Build & push demo images (push) Successful in 2m49s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:42:19 -04:00
archipelagoandClaude Fable 5 0f13545375 fix(fips): ship a second, reachable default anchor (once-and-for-all)
Nodes depended on a single public anchor (185.18.221.160) that is
unreachable from most real networks — confirmed on the thinkpad WiFi and a
fresh Framework node, both of which islanded (is_root=true, depth=0). Its
DNS even resolves IPv6-first while the daemon is IPv4-only.

Stand up an Archipelago-operated FIPS anchor on vps2 (146.59.87.168, the
OTA host every node already reaches) and ship it as an additional default
anchor. default_public_anchor() -> default_public_anchors() -> Vec; load()
returns the set; the apply loop already dials each, so whichever the node's
network can reach wins. Verified end-to-end: a fresh node joined the tree
via vps2 in 3s (depth 1). anchors tests: 7/7 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:42:18 -04:00
archipelago 7eb4d6a7bf chore: release v1.7.107-alpha 2026-07-20 17:00:20 -04:00
archipelagoandClaude Fable 5 db008abdfd fix(kiosk): force dark color-scheme so bundled AIUI renders its dark theme
AIUI themes via @media (prefers-color-scheme) and falls back to its light
variant (white panels) when the browser reports no preference — which the
minimal kiosk X session does, so AIUI came up light instead of its designed
dark. The main UI hardcodes dark and is unaffected. Push two independent
dark signals on the kiosk Chromium: GTK_THEME=Adwaita:dark (version-
independent, can never force light) and --blink-settings=preferredColorScheme=0
(0 = kDark in modern Chromium). Needs on-device kiosk verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:57:46 -04:00
archipelagoandClaude Fable 5 35e9c62441 docs(release): curated v1.7.107-alpha changelog + What's New
Demo images / Build & push demo images (push) Successful in 2m49s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:39:48 -04:00
archipelagoandClaude Fable 5 cb301b1aef fix(wifi,fips): self-heal polkit rule + fast-retry anchors after daemon restart
Two fleet fixes that OTA can deliver (unlike the ISO-only polkit rule):

- WiFi (issue #99): nodes drive NetworkManager from a system-level service
  with no logind seat, so the stock polkit rule denies them and Wi-Fi setup
  fails with 'Insufficient privileges'. Fresh ISOs since 2026-05 ship the
  scoped rule, but OTA'd nodes never got it (OTA replaces binary + web UI,
  not host system config). Add a startup self-heal that installs the rule if
  missing and best-effort ensures polkitd — both wrapped so an offline/locked
  apt can never fail startup; the rule is written regardless so it activates
  once polkitd lands. Idempotent on the rule's unique subject.user marker.

- FIPS: regenerating fips.yaml (this build does, once, on first boot after
  the OTA) restarts the fips daemon; for a few seconds /run/fips/control.sock
  is gone and every seed-anchor dial fails, islanding the node until the next
  5-min tick. Detect that exact failure (all dials fail on control.sock) and
  retry in 15s instead — bounded to 8 fast retries/episode so a node with no
  fips daemon falls back to the steady cadence rather than busy-looping.

FIPS unit suite: 33/33 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:39:02 -04:00
archipelagoandClaude Fable 5 6a848c38cc fix(iso): extract nostr-rs-relay from its real path /usr/src/app
The relay image was rebuilt to the standard nostr-rs-relay layout — WORKDIR
/usr/src/app, execs ./nostr-rs-relay — but the build still cp'd from the old
/usr/local/bin path, so extraction silently produced nothing. Once the
missing-binary guard was added, that silent miss became a hard build
failure. Verified the binary is a valid amd64 ELF (GLIBC 2.34, under Debian
13's 2.40) at /usr/src/app/nostr-rs-relay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:15:59 -04:00
archipelagoandClaude Fable 5 830811372b fix(iso): stop requiring the removed NostrVPN binary
The ISO build hard-failed at 'Required binaries not extracted: nvpn'
because it still pulled 146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7 — an
image that was deleted from the registry when NostrVPN was removed from
the product. The native nvpn daemon is already masked here (ln -sf
/dev/null nostr-vpn.service) and is never spawned at runtime (core vpn.rs
manages WireGuard/Tailscale, only reading a legacy nvpn config as
fallback), so extracting its binary was dead weight that bricked every
build once the image went away.

Drop the nvpn extraction and remove it from the missing-binary guard.
nostr-rs-relay stays required — its nostr-relay unit is still enabled and
the image still pulls. Change is in STEP 3, so the cached rootfs is
unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:05:34 -04:00
archipelago 38cb3dd252 chore: release v1.7.106-alpha
Demo images / Build & push demo images (push) Successful in 2m52s
2026-07-20 15:36:00 -04:00
archipelagoandClaude Fable 5 50170b866e docs(release): curated v1.7.106-alpha changelog + What's New; pin ISO FIPS to v0.4.1
The ISO built FIPS from an unpinned --depth 1 clone of upstream main, so
every build shipped whatever happened to be on main that day. Pin to
v0.4.1 via a FIPS_VERSION build arg — the version fips/config.rs renders
its typed config against, and the one validated in the field on .198 and
the thinkpad. The pin lands inside the STEP 1 recipe-hash range, so the
cached rootfs correctly invalidates on the next build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 15:09:03 -04:00
archipelagoandClaude Fable 5 c91887a397 chore: commit Cargo.lock version bump left over from v1.7.105-alpha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 15:05:31 -04:00
archipelagoandClaude Opus 4.8 4fb200e938 docs: handoff for the v1.7.106-alpha OTA + ISO release
Captures the three commits this release carries, the FIPS 0.4.1 upgrade
state (2 nodes done, fleet not rolled), the peer-files diagnosis including
what is explicitly NOT explained, the open decisions not taken, and the
exact release + ISO ritual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:01:38 -04:00
archipelagoandClaude Opus 4.8 5fd0d6c3c8 refactor(fips): generate fips.yaml from typed structs; enable mDNS LAN discovery
The daemon config was built by format!-ing a YAML string literal. Upstream's
config structs are #[serde(deny_unknown_fields)], so a key we get wrong does
not degrade — the daemon refuses to start and the node drops off the mesh.
String-built config made that a runtime discovery on a live node.

Replace it with a typed struct tree serialised via serde_yaml, verified
field-by-field against jmcorgan/fips v0.4.1, plus an exact-output snapshot
test so schema drift fails in CI rather than at boot. Also adds tests for
determinism (server.rs compares the render against disk to detect drift, so
instability would cause a reinstall+restart loop) and for the mDNS key path.

Enables node.discovery.lan.enabled (mDNS/DNS-SD, added upstream in v0.4.0)
so co-located nodes peer directly instead of depending on the public anchor
being reachable — an anchor blackhole on one segment currently islands a node
completely. Emitted unconditionally rather than version-gated: v0.3.0's
DiscoveryConfig has no `lan` field and no deny_unknown_fields, so a v0.3.0
daemon ignores it harmlessly and it activates on upgrade with no second
config migration.

Note: the first startup after this lands renders a config that differs from
disk, so the existing drift check reinstalls it and restarts the daemon once.
That is the intended self-healing path and settles immediately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:47:22 -04:00
archipelagoandClaude Opus 4.8 3ab7fb521a fix(rpc): log the full error chain, not just the outermost context
RPC failures logged only the top-level anyhow context, so a peer-files
failure produced exactly "RPC error on content.browse-peer: Failed to
connect to peer" with the real cause discarded. That made it impossible
to tell a dead peer from a slow Tor circuit from a FIPS resolve failure
without reproducing by hand.

Switch the log line to `{:#}` so the whole context chain is rendered.
The client-facing message still uses `{}` through sanitize_error_message,
so no internal detail is leaked to callers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:03:33 -04:00
archipelagoandClaude Opus 4.8 9e3ac9ba8f fix(ui): show the FIPS/Tor transport pill on mobile peer files
Demo images / Build & push demo images (push) Successful in 2m48s
The peer-files header title block is `hidden md:block` (the global header
carries the peer name on mobile), which also hid the transport pill nested
inside it — so mobile users browsing a peer's files had no indication of
whether they were on FIPS or Tor.

Render a `md:hidden` pill alongside the peer icon so the transport is
visible at every width, without duplicating the peer name on desktop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:47:29 -04:00
archipelago e2f83c0157 chore: release v1.7.105-alpha 2026-07-20 03:40:38 -04:00
archipelagoandClaude Fable 5 d5f709a3c3 docs(release): curated v1.7.105-alpha changelog + What's New block
Demo images / Build & push demo images (push) Successful in 2m43s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 03:38:23 -04:00
archipelagoandClaude Fable 5 710f576c77 fix(ui): satisfy noUncheckedIndexedAccess in the WG retry ladder
Demo images / Build & push demo images (push) Successful in 2m43s
vue-tsc in the release build (unlike the gate's type-check) rejects
indexing WG_RETRY_DELAYS_MS with a bare counter; hoist the delay into a
local and gate on undefined instead of the length check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 02:52:43 -04:00
archipelagoandClaude Fable 5 c1d309f21f style: cargo fmt crash_recovery.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 02:28:49 -04:00
archipelago 9c39243969 Merge remote-tracking branch 'gitea-ai/fix/companion-autologin-replay-intro'
Demo images / Build & push demo images (push) Successful in 2m49s
2026-07-20 01:57:19 -04:00
archipelagoandClaude Fable 5 f25febf3bb fix(vpn): refresh a stored peer's WireGuard endpoint to the node's current IP
vpn.peer-config returned the config exactly as stored at creation time, so
after a node moves networks the reused companion peer's QR encodes the old
location's address (seen on .116: Endpoint = 10.125.9.0 from the previous
LAN) and the tunnel can never connect. Rewrite the Endpoint line with the
node's current address before rendering the QR, and persist it back so the
downloadable .conf matches. Endpoint detection is shared with
vpn.create-peer via current_wg_endpoint_host().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:57:08 -04:00
archipelagoandClaude Fable 5 f13fdc6451 fix(recovery): don't brick-loop startup on stale crash-snapshot containers
After an unclean shutdown, crash recovery walked the running-containers
snapshot and retried 'no such container' failures (2 attempts, 10s
backoff) for containers that no longer exist. Recovery runs before the
server binds :5678 and notifies systemd ready, so a stale snapshot
pushed startup past TimeoutStartSec=5min — systemd killed the daemon
mid-recovery, the next boot saw a crash again, and the node looped
forever (151 restarts on .116 after a hard poweroff).

- pre-filter the snapshot against 'podman ps -a' and skip vanished
  containers outright (fail-open if the query fails)
- never retry a 'no such container' failure
- extend the systemd start timeout ahead of each container start so a
  heavy node recovering dozens of real containers isn't killed while
  making progress

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:32:42 -04:00
archipelagoandClaude Fable 5 163bc3af01 fix(tor): stop listing/pre-baking hidden services for uninstalled apps (#79)
Backend: tor.list-services now hides services whose name maps to a
known-but-uninstalled catalog app (aliases covered: bitcoin/knots/core,
electrumx/electrs, btcpay, mempool). The node's own service, the relay,
and custom user services always show.

ISO: first-boot tor setup no longer pre-creates hidden services for six
apps that may never be installed — only the node's own onion is baked.
It also only seeds services.json/torrc on FIRST boot instead of
clobbering backend-managed services on every boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:32:33 -04:00
archipelagoandClaude Fable 5 9a7331cead fix(cli): --version/-V and --help print and exit instead of booting the daemon
A stray `archipelago --version` used to start a full second instance next
to the systemd one (issue #74). Handle -V/--version, -h/--help, and reject
unknown dashed options before any tracing/state init, mirroring the
ceremony subcommand's clean-stdout precedent. Version output matches the
health RPC format: <pkg-version>-<git-hash|dev>.

Fixes #74

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:49:12 -04:00
archipelagoandClaude Fable 5 9eadec6936 chore: sign v1.7.104-alpha OTA manifest
Demo images / Build & push demo images (push) Successful in 2m36s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 02:27:55 -04:00
archipelago 6b0a84e710 chore: release v1.7.104-alpha 2026-07-19 02:17:45 -04:00
archipelagoandClaude Fable 5 fd361fb35e test(update): serialize the apply_update regression tests
Both tests take the global single-flight UPDATE_OP_LOCK; run concurrently
by the test harness, one saw the other's lock and failed with 'another
update operation is already running' instead of its expected refusal.
A shared test mutex makes them mutually exclusive deterministically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 02:01:58 -04:00
archipelagoandClaude Fable 5 8bb61a51e2 style: cargo fmt update.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 00:32:57 -04:00
archipelagoandClaude Fable 5 17d225190a docs: v1.7.104-alpha changelog + What's New sync
Demo images / Build & push demo images (push) Successful in 2m42s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 00:31:12 -04:00
archipelagoandClaude Fable 5 d08c0d29c7 fix(ui): ElectrumX sync screen shows the app's icon instead of a generic glyph
Demo images / Build & push demo images (push) Has been cancelled
The pre-UI sync overlay used a hardcoded orange box-glyph SVG; it now renders
the same resolved app icon the loading screen uses (with the shared image-error
fallback), so the wait page reads as ElectrumX at a glance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 00:29:46 -04:00
archipelagoandClaude Fable 5 a93bd70c5a fix(electrumx): probe bitcoin-knots/bitcoin-core for the daemon host instead of hardcoding knots
The manifest baked bitcoin-knots:8332 into DAEMON_URL. Nodes whose
backend runs under the bitcoin-core name (and any future variant
without a knots DNS alias) left electrumx permanently disconnected —
'connection problem' forever and a block index stuck at 0. The
startup script now picks the first backend name that resolves on
archy-net and falls back to bitcoin-knots.

Catalog regenerated (catalog manifests override disk ones fleet-wide);
this regen also embeds the recently-merged barkd/Ark manifest for the
first time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:55:06 -04:00
archipelagoandClaude Fable 5 25162ee846 feat(update): auto-rollback OTA binaries that crash-loop before they can self-verify
.198 crash-looped 236x on a truncated OTA binary with a good backup
sitting in update-backup/ — verify_pending_update() can't help when
the new binary never runs. New scripts/ota-crash-guard.sh runs as
root from ExecStartPre: while the post-OTA pending-verify marker
exists it counts start attempts, and after 5 failures restores the
backup binary (atomic rename), replaces the marker with an
update-rolled-back.json tombstone, and lets the service come back
on the previous version.

Wiring: fresh ISOs get the ExecStartPre line in the unit file;
existing nodes get a drop-in installed by apply_update's runtime
component step on their next OTA. '+-' prefix so a missing or
failing guard can never block the service.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:42:54 -04:00
archipelagoandClaude Fable 5 837cfdfd1f fix(update): never apply unverified staging — close the OTA truncated-binary race
Root cause of .198 bricking on the 1.7.103 OTA: two concurrent
update.download RPCs shared one staging file, cancel_download wiped
staging mid-flight, a third download began re-filling it, and
apply_update mv'd the 3-second-old 17MB partial of the 49MB binary
into /usr/local/bin -> SEGV boot loop (236 restarts, no rollback).

- Single-flight UPDATE_OP_LOCK across download/apply; concurrent
  callers get an explicit 'already running' error.
- apply_update now requires the .download-complete marker AND
  re-verifies every staged component (size + SHA-256 + BLAKE3)
  against the manifest before touching the system.
- cancel_download only wipes staging when no operation holds the
  lock; otherwise it just flags the in-flight loop to bail.
- Fixed the 'file already complete' path in
  download_component_resumable, which skipped verification and fell
  through to the bogus 'download failed without a captured error'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:52 -04:00
archipelagoandClaude Fable 5 573b469191 chore: sign v1.7.103-alpha OTA manifest
Demo images / Build & push demo images (push) Successful in 2m53s
Also folds the Cargo.lock version bump that create-release.sh missed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 07:48:56 -04:00
archipelago 401f92a24f chore: release v1.7.103-alpha 2026-07-18 07:30:25 -04:00
archipelagoandClaude Fable 5 dc0adbef70 docs: v1.7.103-alpha changelog + What's New sync
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 07:01:45 -04:00
archipelagoandClaude Fable 5 537c9fa70b fix(demo): suppress the PWA update prompt + reload on the public demo
The SW-update modal is noise on the demo (nothing to update to) and both
accept-paths end in a reload that replays the intro from scratch. With
skipWaiting/clientsClaim off, ignoring the waiting worker is safe — the
new build activates on the visitor's next session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 07:01:45 -04:00
archipelagoandClaude Fable 5 a27c7bafbf chore: sign v1.7.102-alpha OTA manifest
Demo images / Build & push demo images (push) Failing after 2m53s
Also folds the Cargo.lock version bump that create-release.sh missed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 16:26:46 -04:00
archipelago 95b9d8f0fe chore: release v1.7.102-alpha 2026-07-17 12:36:44 -04:00
archipelagoandClaude Fable 5 918aba1de3 docs: v1.7.102-alpha changelog + What's New sync
Demo images / Build & push demo images (push) Successful in 2m50s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:22:41 -04:00
archipelagoandClaude Fable 5 49b366fbe4 test(goals): align goal-status tests with manual-step completion semantics
Demo images / Build & push demo images (push) Successful in 2m47s
3aebbcbb made goal completion require walking the manual steps (running
apps alone no longer finish a goal) but left the store tests asserting
the old auto-complete behavior. Tests now walk the manual step and also
pin the new running-but-not-walked => in-progress case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:46:38 -04:00
archipelagoandClaude Fable 5 7eaf99873e chore: cargo fmt — settle formatting drift blocking the release gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:41:31 -04:00
archipelagoandClaude Fable 5 a76a92cff8 fix(iso): zstd-compress bundled core container images (-160MB on the ISO)
Bundling fmcd for offline installs (6dcdada3) shipped the raw
uncompressed podman save tar and grew the ISO ~220MB (2.3G -> 2.5G in
RC6-RC9). Save the core bundle as .tar.zst instead — podman load
auto-detects compression, verified locally against the RC9 fmcd.tar
(228M -> 65M, loads cleanly into rootless storage). The first-boot
loader and installer copy globs now pick up .tar.zst too.

Also trims the installer's USB->disk image copy and first-boot load
time on fresh installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:31:43 -04:00
archipelagoandClaude Fable 5 a177ef3b38 fix(kiosk): clear stale Chromium profile lock so rename doesn't kill the kiosk (#98)
Chromium's SingletonLock is a symlink encoding <hostname>-<pid>. After a
node rename changes the OS hostname, the lock left from the previous
boot reads as "another computer" holding the profile; Chromium refuses
to start, --noerrdialogs hides the only dialog, and the kiosk
black-screens forever.

Two layers:
- kiosk launcher removes Singleton{Lock,Cookie,Socket} before every
  Chromium start (any prior owner is dead at that point — pkill at
  session start / loop respawn). Ships via ISO splice AND the binary's
  include_str! self-heal, so existing nodes get it with the next OTA.
- the rename handler clears the lock immediately as a hostname
  side-effect, covering nodes still running a pre-fix launcher.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:31:43 -04:00
archipelagoandClaude Fable 5 ea0cd87b3b fix(auth): sync OS login password when the UI password is set at install (#97)
auth.setup only wrote the web password hash to user.json, so the
archipelago system user kept the image default password ("archipelago")
on console/SSH even after the user picked a real password during
onboarding. Reuse the existing usermod-based sync (already used by
auth.changePassword's alsoChangeSsh path) at setup time, best-effort so
a failure can never break onboarding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:31:27 -04:00
archipelagoandClaude Fable 5 1ee1b56f70 fix(ui): desktop Tor rows get their inline actions back + card actions bottom-align across grid cards
Demo images / Build & push demo images (push) Failing after 1m55s
The card-action sweep put the mobile 50/50 Delete|Rotate row under every
Tor service on desktop too — desktop reverts to the original compact
inline Rotate / trash / toggle beside each row, while mobile keeps the
thumb-safe stacked layout. The VPN and Network Interfaces cards' bottom
buttons now pin to the card bottom (mt-auto) so they stay vertically
aligned when the grid stretches one card taller than the other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 23:15:44 -04:00
archipelagoandClaude Fable 5 73d181abea fix(setup): wizard navigation round-trips — back to Setup tab, goal-aware channels back button, configure launches the app
Demo images / Build & push demo images (push) Failing after 1m56s
- "Back to Goals" now returns to Home's Setup tab (?tab=setup) instead of
  landing on the dashboard tab.
- The channels screen remembers when a setup wizard sent you there
  (?from=goal) — its back button reads "Back to Setup" and returns to the
  wizard; it also now uses the shared BackButton pill instead of a bare
  link.
- "Open & Configure" steps launch the actual app via the app launcher —
  iframe apps overlay on top of the wizard, tab-only apps (BTCPay,
  Nextcloud) open a tab, mobile uses the in-app browser — instead of
  routing to the app-details page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:16:58 -04:00
archipelagoandClaude Fable 5 9e264611e2 docs: Nostr signer-login research + identity-import UX plan
Research report on the most frictionless "sign in with your Nostr signer"
flow for node login (NIP-07 extension + NIP-46 QR scan with Amber, password
always kept as fallback), and a plan for adding existing Nostr identities
(nsec import / npub watch-only / browser extension) to the Nostr Identities
screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:59:54 -04:00
archipelagoandClaude Fable 5 f32c4db7e2 style(ui): companion banner uses the Setup-tab hero image
Demo images / Build & push demo images (push) Failing after 3m10s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:51:12 -04:00
archipelagoandClaude Fable 5 8e13f981d0 fix(demo): IndeeHub opens its real site — the same-origin proxy broke its assets
The nginx sub_filter rewrite couldn't touch the SPA's runtime-built asset
URLs, so the iframed IndeeHub rendered with broken links and images. The
real site refuses iframing (X-Frame-Options), so the demo now opens
https://indee.tx1138.com/ directly via the demo-external mechanism, whose
isDemoExternal() check was previously hardcoded off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:51:12 -04:00
archipelagoandClaude Fable 5 3aebbcbbb8 feat(setup): Zeus lightning journey — fund-wallet step, IBD timer + finish-setup toast, channel suggestions
Every Lightning setup (Open a Shop, Accept Payments, Run a Lightning Node)
gains a guided path to a working channel:

- New "Fund Your Bitcoin Wallet" step, gated on the blockchain finishing
  its initial sync: while syncing it shows a live progress bar with a
  counting-down time-remaining estimate; once synced it shows the on-chain
  balance and a "Fund Wallet" button that opens the receive modal with a
  fresh address, QR, and the Zeus channel limits (min 150,000 / max
  1,500,000 sats) noted.
- When the sync finishes while a Lightning setup is mid-flight, a toast
  pops with a "Finish setup" link straight back to the wizard (toasts now
  support action links). If several setups are in flight, one is chosen —
  the shared steps complete the lightning part of any of them.
- Channel steps are Zeus-branded (logo + copy) and land on the Lightning
  Channels screen, which now carries an "Open a channel with Zeus" card
  that prefills the open-channel modal with the Olympus peer URI, 150k
  sats, and private-channel checked. "Get Zeus" links to zeusln.com.
- Setup completion now actually requires walking the manual steps (fund,
  open channel, configure) — previously any step whose app was installed
  was silently auto-ticked and running apps marked the whole goal done.
- Completion CTAs now go to the app you just set up ("Go to my shop
  (BTCPay)" etc.) instead of the generic services list; iframe apps open
  in the on-top app overlay.
- On-chain send modal gains a "Send all funds" sweep toggle, backed by
  LND's send_all on the backend (amount no longer required when sweeping).
- Demo/mock: bitcoin.getinfo now returns the block_height/sync_progress
  contract the UI reads and simulates a ~90s IBD ramp per visitor so the
  timer, toast, and fund flow can all be demoed live; channel list data
  fixed (status/channel_point/liquidity totals — the panel previously
  crashed on the missing status field); sendcoins supports send_all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:51:12 -04:00
archipelagoandClaude Fable 5 2e5f67a59a feat(ui): companion app banner in the App Store + APK download unblocked
Demo images / Build & push demo images (push) Has been cancelled
The App Store gains a "Your Node. In Your Pocket." hero banner for the
mobile companion app, in the same format as the featured-app banner, with
the app running on a phone mockup rising out of the right edge. Install
(or clicking anywhere on it) opens the Remote Companion download/pairing
modal — now openable on demand via a shared trigger instead of only
auto-showing once.

Also fixes the companion APK download on PWA installs: /packages/ now
bypasses the service worker's SPA fallback, which was answering the
download click with index.html instead of the file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:46:08 -04:00
archipelagoandClaude Fable 5 3028685e6b feat(ui): companion modal pairing screen — scan a QR to auto-connect the app
Demo images / Build & push demo images (push) Successful in 2m45s
The Remote Companion intro modal gains a second screen: an "I've installed
it" button next to the download button slides over to a pairing QR the
companion app scans to load the server details automatically, with a Back
button to return. The QR is an archipelago://pair deep link carrying the
server URL — the demo embeds https://demo.archipelago-foundation.org plus
the shared demo password so a scan lands in a logged-in demo session; real
nodes send only the address (kiosk advertises the mDNS .local name since
its own origin is localhost). Phones also get a tap-to-open deep-link
button since you can't scan your own screen.

App-side contract + requirements handed off in docs/companion-pairing-qr.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:07:56 -04:00
archipelagoandClaude Fable 5 1a3170f1c3 fix(ui): companion popup waits for sustained calm; connection banner stops crying wolf
Demo images / Build & push demo images (push) Successful in 2m36s
Two "unpolished" moments, worst on the demo:

- CompanionIntroOverlay gated on a point-in-time check of the reveal
  cinematic flag. On a cold cache the entrance video can start buffering
  after the 5s base delay, so the flag was still false when sampled and
  the popup cut into the cinematic anyway. It now shows only after the
  scene has been continuously calm for the full grace window.

- ConnectionBanner's flat 2.5s debounce fired on every tab-return and on
  first dashboard paint: a dead WebSocket is the NORMAL state right then
  (browsers kill background-tab sockets; first paint races the initial
  connect), and reconnects routinely exceed 2.5s over real links. Those
  moments now get a 10s runway (15s window after load/resume); genuine
  mid-session drops keep the 2.5s response. On the demo the blip banner
  is suppressed entirely — it runs against a local mock, so "Connection
  lost" there is pure noise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:42:57 -04:00
archipelagoandClaude Fable 5 547f674ac8 fix(pwa): defer update reloads until the intro cinematic finishes
Demo images / Build & push demo images (push) Successful in 3m23s
A genuine service-worker update still reloaded the page the instant the
new worker claimed it. The kiosk and the public demo replay the intro on
every boot/visit, so an update landing mid-sequence restarted the whole
cinematic — the "typing intro resets to the start after several seconds"
report. Hold the reload until the splash is complete and the
dashboard-reveal cinematic is over, then apply it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:48:56 -04:00
archipelagoandClaude Fable 5 92d221bbf1 fix(ui): show the controller focus ring only during actual arrow/gamepad navigation
Demo images / Build & push demo images (push) Failing after 1m42s
The orange "gamepad selection" lift+glow was plain CSS :focus styling on
[data-controller-container] — any tap on a tabindex card lit it up (worst
on mobile), and useControllerNav's autoFocusMain programmatically focused
the first card on mount and every route change, painting the ring with no
controller input at all.

Gate it behind html.controller-nav, toggled by actual input modality:
arrow-key navigation and gamepad connection turn it on, any pointerdown
turns it off. autoFocusMain now no-ops outside directional-nav mode (a
first arrow press focuses the first container instead of spatially
navigating from <body>), so pointer users never get surprise rings while
keyboard/gamepad users keep the exact same navigation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:38:46 -04:00
archipelagoandClaude Fable 5 e9cfc234cd fix(kiosk): flatten the onboarding/login background too + roomier renderer heap
The 454c4bb2 kiosk flattening only covered the Dashboard's background
stack, but the kiosk boots into the login/onboarding screen —
OnboardingWrapper.vue — whose scoped 3D stack (perspective +
preserve-3d bg layers + mix-blend-mode glitch overlays) has the exact
compositing pattern that fails to repaint under the kiosk's software
compositor, leaving the black body fill visible. Extend the same
html.kiosk-mode flattening to this screen via :global() rules: 2D
opacity crossfades stay, 3D transforms / blur filters / blend-mode
glitch layers go.

Also raise the kiosk chromium JS heap cap 128→256MB: the intro
cinematic (video + audio + app) inside a 128MB V8 old-space risks
renderer OOM, and a crashed kiosk tab auto-reloads — indistinguishable
from the reported "flickers and refreshes".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:38:22 -04:00
archipelagoandClaude Fable 5 06186ab30c fix(intro): restore the typing intro on fresh kiosk installs + seamless splash-to-onboarding handoff
Three regressions of the fresh-install cinematic, all in App.vue:

1. The kiosk chromium opens /kiosk (a marker route that stamps
   localStorage.kiosk and redirects to /). The deep-route splash
   suppression added in PR #91 reads window.location.pathname, so the
   kiosk boot was classified as a deep route and the typing intro →
   "Welcome Noderunner" → onboarding sequence never played. Normalize
   /kiosk to / for the splash decision.

2. The pre-splash onboarding-status check runs a ~30s retry ladder;
   against a still-booting backend it held the black !isReady screen the
   whole time. Bound it with a 2.5s race — unknown means "play the
   intro" (a fresh install IS the slow-backend case; onboarded nodes
   answer in milliseconds so their suppression is unaffected).

3. handleSplashComplete revealed RouterView before routing, flashing
   RootRedirect's spinner at '/' between the intro's final frame and the
   onboarding screen. Commit the destination route first, then reveal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:35:47 -04:00
archipelagoandClaude Fable 5 e5c7210663 fix(install): stall-aware, retried image pulls in the orchestrator runtime
PodmanClient::pull_image had a single attempt with a hard 600s wall
clock — on a fresh node, any image that legitimately takes >10 min to
download got killed mid-pull and failed the install. Podman keeps the
completed layers, so the user's retry started half-done and succeeded:
the exact "first install fails, second works" pattern reported for
btcpay and indeedhub (and earlier for LND, whose fix in 72d7fa07 only
covered the legacy install path — this is the same cure ported to the
orchestrator path both fresh installs and upgrades use).

A pull is now only killed when nothing is observably happening — no
stderr output AND no staged-byte growth in TMPDIR for 180s — or at a
1800s absolute ceiling, with 3 attempts and backoff on top. Dead
registries still fail fast (no bytes ever land). Failures now surface
podman's actual stderr tail instead of a bare exit status, and the
pull stages via the user's containers tmp like every other pull path
(rootless /var/tmp is read-only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:02:53 -04:00
archipelagoandClaude Fable 5 500472944c fix(install): stream per-member progress for orchestrator stack installs
Orchestrator-installed stacks (indeedhub, btcpay, netbird, immich,
mempool) sat at "Preparing… 5%" for the entire install — the
install_stack_via_orchestrator path never called set_install_phase /
set_install_progress after the initial Preparing, so a 7-image,
multi-gigabyte indeedhub pull gave the user zero feedback ("just says
preparing for ages"). The legacy installers already report phases and
the frontend already interpolates X-of-N across the PullingImage band;
the new path simply never fed it.

Now: PullingImage phase up front, an X-of-N counter plus a per-member
"Installing postgres (2 of 7)…" message as each member installs, and
the legacy installers' truthful PostInstall→Done settle at the end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:02:05 -04:00