Slow multi-hop payments (>15s routing) surfaced as "Payment failed"
while LND settled them in the background: the shared LND REST client's
15s total timeout aborted the synchronous /v1/channels/transactions
wait, and every UI path treated that abort as a definitive failure. The
payment then succeeded anyway and only appeared in history on the next
background poll.
Backend: lnd.payinvoice now decodes the invoice up front for its payment
hash, pays on a dedicated 120s client, and answers status:"pending" with
the hash (never an error) when the wait elapses after the payment was
handed to LND — only a pre-connect failure is still a hard error. New
lnd.paymentstatus RPC reports succeeded/failed/in_flight (with humanized
failure reasons) from /v1/payments.
Frontend: new rpcClient.payLightningInvoice() pays then polls
lnd.paymentstatus to a real terminal state (3s interval, up to 2 min);
all five call sites (send modal, scan modal, web5 unified send, peer-file
purchase, app-launcher payments) migrated. Failure is only shown when LND
itself declares FAILED; a still-settling payment shows an in-flight state
and success fires the transaction refresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plain-text seed QRs didn't scan into Passport Prime — wallets that
import seeds by QR (Passport, SeedSigner, Keystone, Nunchuk, Sparrow)
expect the SeedQR standard: each BIP39 word as its zero-padded 4-digit
wordlist index, concatenated into a numeric QR.
- new utils/seedqr.ts encodes BIP39 words per the SeedSigner spec
(@scure/bip39 wordlist; vector-checked abandon=0000, zoo=2047)
- new shared SeedRevealPanel (Words/QR tabs, tap-to-reveal blur) now
backs the LND reveal AND the Settings→Backup recovery-phrase reveal,
so every current and future seed reveal behaves the same
- onboarding seed + Settings reveal: QR defaults to SeedQR with a
plain-text toggle
- LND seed stays plain-text-only with an explicit note: aezeed is not
BIP39 and only restores into LND-based wallets (Zeus/Blixt/another
node) — SeedQR-encoding it would just mislead
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Observed live on .116 after the dedup/backoff fixes: the kiosk browser's
hot-swap auto-probe (mesh.probe-device) still interleaved its handshakes
with the listener's open sequence on the same tty every backoff window —
Linux double-opens ttys silently, both handshakes corrupted each other,
and every collision's open() DTR/RTS-reset the board again. The retry
heuristic from 5f01ec31 narrowed but couldn't close the race.
A static PORT_OPEN_LOCK now covers the listener's whole device-open
sequence and each probe attempt, so exactly one prober touches the port
at a time. With this + the settle/backoff/dedup fixes, the .116 radio
completed its first MeshCore handshake in days (node-fa161601, 8
contacts) and the session has been stable since.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both seed screens get the wallet-settings segmented tab style: words
stay the default first view; the QR tab renders the space-joined seed
words for wallets that support seed import by scan. The LND reveal QR
sits behind the same tap-to-reveal blur as the words, and both panes
warn that the code is equivalent to the words.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/dev/mesh-radio is a udev symlink to a ttyUSB*/ttyACM* node that is also
in SERIAL_CANDIDATES, so one board was detected, probed and DTR/RTS-reset
twice per reconnect cycle (and shown twice in the UI):
- detect_serial_devices() dedupes candidates by canonical path, keeping
the stable /dev/mesh-radio name
- auto-detect fallback skips the preferred path it just probed this cycle
instead of immediately resetting the same board again
- probe_device's active-session guard compares canonical paths, so a
probe via the alias can no longer open the tty the live session holds
Together with the 2s boot-settle and stable-session backoff gate, this
takes a plugged-in CP2102/ESP32 board from up to 9 reset events per
cycle at a permanent 5s retry floor down to one probe sequence per
backoff window — enough for the radio to actually finish booting.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extracted from beff5dd5 on archy-hwconfig (the rest of that commit is
flash-feature code staying on its branch): a device that connects then
drops within seconds — e.g. mid-boot-loop — kept resetting backoff to
5s forever, and every retry's open() toggles DTR/RTS which itself
resets ESP32-family boards. Backoff now only resets after a session
survives STABLE_SESSION_THRESHOLD (20s), so an unstable device gets
progressively longer quiet gaps to actually finish booting.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sats/BTC unit toggle on the on-chain amount field with live conversion
hint; canonical value stays sats end-to-end
- Fast / Standard / Slow fee presets (1 / 6 / 144 block targets) plus a
custom pane taking target blocks or an explicit sat/vB rate
- confirm pane shows LND's fee estimate for the chosen speed via the new
lnd.estimatefee RPC (GET /v1/transactions/fee)
- lnd.sendcoins now accepts target_conf / sat_per_vbyte with the same
mutual-exclusion + range validation as channel opens
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consumes the lnd.closedchannels RPC and closing/force_closing statuses
that shipped backend-side in 00b7e179 but never reached the panel:
- tab bar with per-state counts; pending covers pending_open/closing/force_closing
- closed-channel cards with close type, settled balance, block height and
closing-tx explorer link
- closing/force_closing status dots + closing-tx link on in-flight closes
- Close button hidden for channels already mid-close
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- lnd.closedchannels: closed-channel history via /v1/channels/closed
- channel list now includes waiting-close and force-closing pending
channels with their closing_txid
- closechannel reads the close stream's first update with a dedicated
client instead of hanging until the closing tx confirms; returns the
closing txid in display byte order
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The wallet card (Home + Web5 views) now leads with a Total Bitcoin row
summing all rails (on-chain + lightning + cashu + fedimint + ark), using
the orange ₿ mark. The on-chain row swaps ₿ for a link/chain icon.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A settled payment now takes over the modal: animated check with radiating
rings (emerald + one Archipelago-orange), the amount large, method label,
then copyable payment hash (lightning) / transaction ID (on-chain) and a
settling note, with Send-another / Done. Replaces the old alert-line at the
bottom of the form. Reduced-motion honored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The companion reaches the node at its fips0 ULA, and the web UI builds app
links as http://[<ULA>]:<direct port> — but rootless-podman published ports
bind 0.0.0.0 only, so every app URL was refused over the mesh (:8334 first).
A reconcile loop in the backend mirrors public IPv4 listeners: any port
>=1024 on 0.0.0.0 without an IPv6 any-listener gets a v6-ONLY [::] forwarder
to 127.0.0.1, following /proc/net/tcp* so install/remove and hardcoded
companion ports are covered without touching a single container. Purely
additive: IPv4/LAN/Tor access paths are untouched, v6only listeners cannot
collide with or intercept v4 traffic, and foreign IPv6 listeners win.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phones on the FIPS mesh reach the node at http://[<fips0 ULA>], but every
shipped nginx config listened on IPv4 only — nothing answered [::]:80/443,
so mesh HTTP failed with the tunnel fully healthy (found live on
framework-pt during the vc24 5G session, 2026-07-23). Canonical conf gains
listen [::] lines and patch_nginx_conf self-heals deployed nodes (covers
the sites-enabled -> archipelago-http dev variant via the existing
canonicalize pass); validated by the existing nginx -t + rollback flow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
- archipelago-gamepad-keys: evdev->uinput daemon (pure python3 stdlib) that
mirrors any attached controller as a virtual keyboard — D-pad/stick to
arrows, A/B to Enter/Escape, X/Y to Space/f, shoulders to Tab/Shift+Tab.
The browser sees real keys, so controllers work inside EVERY app iframe
(IndeeHub, Jellyfin…) with zero per-app code. Ships via the ISO splice and
bootstrap self-heal (kiosk nodes only), hotplug via 5s rescans.
Implements docs/tv-input-iframe-apps.md.
- useControllerNav: an open [role=dialog][aria-modal] owns navigation —
focus is pulled inside, arrows move spatially between its controls, Enter
activates, Escape stays with the modal's own close handling. One standard
mapping for every modal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The session subtree now ALWAYS lives under <body>; inline panel mode is
emulated with a fixed-position rect synced from the layout placeholder
(ResizeObserver + resize), so toggling panel/overlay/fullscreen no longer
re-parents — and therefore no longer reloads — the app iframe.
- Display modes are strictly per-app: saved per-app pick → app default
(IndeeHub: fullscreen) → panel. The global fallback is gone, so one app's
mode can never change how another opens.
- The iframe takes focus when the app loads, so keyboard / the gamepad
bridge work inside it immediately (TV).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A pasted fixed-amount invoice auto-fills and locks the amount ('set by
invoice' — nothing to type, just review and send); zero-amount invoices get
an explicit 'enter how many sats' hint. Plus a clipboard Paste button on the
destination field (hidden where clipboard read can't work).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Paid sharing: AccessControl::Paid gains an accepted-methods list
(lightning/onchain/ecash/fedimint; empty = all, back-compat). Sellers pick
methods in ShareModal, gated on what the node can actually receive (LND
running/channel open, ecash wallet, fedimint joined) with an ⓘ that
explains exactly how to enable a missing rail. Enforced server-side (the
invoice/onchain mints refuse non-accepted methods; the serve gate only
honors tokens/hashes for accepted rails) and the buyer's pay modal only
offers what the seller accepts.
- Purchased music: the two remaining lightbox paths now use the bottom-bar
player — the immediate post-ecash-purchase viewer and the Paid Files
tab's window.open.
- Picture-in-picture buttons on the peer video player and the cloud media
lightbox (utils/pip.ts; Chromium/Safari, no-op elsewhere).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Another agent's feat/companion-3finger-native-scan: native wallet QR scanner
in the companion (WalletQrScannerModal.kt), three-finger menu gesture,
WebView file uploads, and the same every-open scan/upload chooser as a
dedicated pane with native-scanner handoff. Conflict in WalletScanModal.vue
resolved in the branch's favor — its choose-pane implementation replaces the
in-pane interstitial from 6502f13e (same UX, plus the native handoff).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
evdev->uinput gamepad-as-keyboard daemon on kiosk nodes (works in any
iframe, any origin) + shell-side focus handoff via useControllerNav;
CDP/per-app injection rejected. Implementation order included.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Per-app default display mode: explicit user pick (remembered per app) ->
app default -> last global -> panel; IndeeHub defaults to fullscreen.
- data-controller-primary: a media file card's gamepad-select clicks the
card (audio -> bottom-bar player, video -> lightbox) instead of the
card's download link.
- Discovered-nodes list scales with the viewport (max(14rem,45vh)) instead
of cramming a long list into a fixed 224px box.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Owned/purchased and cloud audio never opens a lightbox: PeerFiles'
owned-content viewer and the Cloud/CloudFolder preview route audio to the
global bottom-bar player (video keeps the lightbox).
- Web5 shared-content 'Buy' no longer fires the node-ecash payment on
click: it opens a confirmation with balance -> price -> balance-after,
and pays only on explicit confirm.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Pasting an invoice/address now gets the same second-step confirmation the
scan flow has: method, destination, live balance -> amount -> balance
after (invoice-fixed amounts win over the typed one), for every rail
including token minting. Nothing pays until Confirm & Send.
- The scan modal opens on a chooser every time: scan with camera or
upload/take a photo of the QR — the camera no longer auto-starts, and
Back only relights it if camera was the user's choice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pairing QR was effectively IP-first: the phone dialed the scanned LAN
host and went dark when the LAN renumbered or it left home. FIPS peers on
npubs; IPs are only dial hints. fanchors now leads with the paired node
ITSELF (fnpub @ current host, npub-keyed so LAN contact is direct p2p over
FIPS and survives DHCP), and fips.pair-info always includes the Archipelago
vps2 public anchor (pairing hint only — the node's own anchor file is not
modified) so the phone can rendezvous through the public mesh when away.
Contract doc updated with the REQUIRED app-side changes (built on the Mac).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Name fields sit at firmware-version-dependent offsets; when an offset lands
in binary data, from_utf8_lossy handed the UI replacement-character soup in
the mesh modals and settings panel. decode_mesh_name(): strict UTF-8 to the
first NUL, no control chars, else a readable fallback (short pubkey /
node-<id>).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--enable-features=OverlayScrollbar gives the thin auto-hiding scrollbars a
remote browser shows (Peers view had a permanent classic scrollbar on the
TV). Rides the existing kiosk self-heal + ISO splice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause of silent HDMI on kiosk TVs (framework-pt, LG TV):
1) fresh ISOs installed NO audio stack even though the kiosk launcher
expects PipeWire-Pulse — add pipewire/pipewire-pulse/pipewire-alsa/
wireplumber/alsa-utils to the rootfs and put the archipelago user in
'audio' (linger user manager gets no logind seat ACLs on /dev/snd);
2) the kiosk's boot-time Xorg modeset races the i915→HDA audio-component
bind, the ELD notify is lost, and every HDMI profile stays
'available: no' forever. Verified live: one off/on modeset re-delivers
the ELD and WirePlumber immediately switches to HDMI.
New archipelago-audio-router daemon (same splice-from-configs pattern as
the kiosk, ISO + include_str! self-heal): routes the card to the best
available HDMI profile, keeps the default sink on it, migrates live
streams on hot-plug, unmutes (HDMI forced 100% — the TV owns volume), and
re-modesets a connected external output once when no ELD reports a
monitor. bootstrap::ensure_audio_stack() installs packages + router on
already-deployed kiosk nodes via OTA (apt through systemd-run — the
service sandbox keeps /usr and dpkg read-only). main.rs also spawns the
pine satellite keeper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HA stores a voice satellite as a pinned LAN IP and never re-resolves; when
the LAN renumbers (framework-pt: entry at 192.168.1.241, LAN now
192.168.63.0/24) the speaker silently drops forever. A periodic keeper
probes IP-pinned wyoming entries, sweeps the node's local /24s for the
satellite's port when one dies, rewrites core.config_entries (HA stopped
around the edit so its shutdown flush can't clobber it) and starts HA again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- payments.rs: an expired invoice can never succeed on retry, so the
error now says so and tells the payer the way out (ask the recipient
for a fresh invoice) instead of echoing LND's raw reason.
- middleware.rs: the error sanitizer masked every Lightning payment
failure as 'Operation failed. Check server logs' — but LND's reasons
('invoice expired', 'unable to find a path') are written for the
payer and are all actionable. 'Payment failed', 'Invalid payment
request' and 'Missing payment_request' now pass through, with a
regression test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Browsers only honor a transparent same-origin iframe canvas when the
embedder and the iframe agree on color-scheme; the neode-ui shell's
:root{color-scheme:dark} was forcing the AIUI frame opaque and hiding
the background art behind the chat.
Also adds neode-ui/vite.preview.config.mts (preview on :8100 against
the mock backend on :5959).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gateway crash-looped dialing bitcoind at host.archipelago:8332 (the
host gateway IP, where bitcoind does not listen). Root-cause fix, three
parts:
- fedimint-gateway manifest: --bitcoind-url now comes from
$FM_BITCOIND_URL, filled by a {{BITCOIN_HOST}} derived-env — works on
Knots, Core, or any future Bitcoin distro.
- fedimint manifest: same derived-env replaces the hardcoded
FM_BITCOIND_URL=http://bitcoin-knots:8332 environment entry.
- orchestrator: removed the hardcoded FM_BITCOIND_URL=bitcoin-knots
override that clobbered the derived-env, and bitcoin_host() now
accepts any running bitcoin*/bitcoin container (excluding -ui and
archy-* sidecars), preferring the known names in order.
Catalog regenerated + signed (nodes install from the signed catalog, so
the manifest fixes only reach them via this regen).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The double-share/double-pay chain (user hit it live, 2026-07-22):
ShareModal's already-shared lookup compared the slash-stripped stored
path against the leading-slash filepath — never matched — so every
re-share minted a NEW catalog entry with a new id, and the buyer's
owned-guard (keyed by id) saw the duplicate as unowned and paid again.
Three independent walls now: (1) ShareModal normalizes both sides so
re-shares reuse the entry; (2) content_server::add_item dedupes by
filename server-side (updates in place, keeps the id so buyers' owned
records stay valid); (3) the buyer REFUSES to pay for content it
already owns — matched by (onion, content_id) OR (onion, filename) —
and serves the cached copy instead, before any ecash is minted.
Purchases also auto-file into Photos/Music/Documents on the node (same
buckets as the Cloud view, collision-safe naming) so bought files show
up where files live on every device, and Cloud gains a Paid Files tab
listing every purchase (name, size, sats paid, date) with in-app view.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- LND watchdog: framework-pt's LND sat wedged 14h (RPC up, server never
ready, channels inactive) with nothing noticing. 15 consecutive bad
minutes now bounce the container automatically (30min cooldown) —
silent multi-hour Lightning outages become self-healed blips.
- Send modal: Auto tab hidden; Ecash splits into Cashu and Fedi (new
wallet.fedimint-send RPC wrapping the existing spend_from_any); both
rails render the generated token as a scannable QR alongside the
copyable text.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mesh listener spawns /usr/local/bin/archy-reticulum-daemon for
Reticulum sticks, but the ISO never shipped it (framework-pt connected
its RNode only after a hand-copy, 2026-07-22). Bundled from the build
host with a loud warning when absent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'Insufficient balance: need 80 sats, have 0 sats' reached the UI as
'Operation failed. Check server logs.' — the sanitizer allowlist didn't
know wallet errors. Also allowlists the calm Lightning still-starting
notice, which it would have swallowed the same way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the companion (plain-http LAN = no secure context = no live camera)
the photo path IS the scanner, and qr-scanner's single wasm pass often
misses the dense QR of a full BOLT11 invoice. Android WebView's native
BarcodeDetector handles them easily — try it first, wasm as fallback,
plus a 'Reading photo…' status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Channels panel's 'Funding tx in Mempool' link still hardcoded the
local app (missed in the explorer rollout): now it routes like every
other tx link — local Mempool when running, else the saved external
explorer, with the first-time consent modal setting it up and then
opening the tx. The consent modal moves to z-3600 so it renders above
the Wallet Settings modal that can trigger it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fleet log sweep (2026-07-22): (1) recovery paths podman-start'ed
containers that were already running, producing hundreds of benign but
scary conmon 'Failed to create container' + cgroup Permission-denied
pairs per node per day — both paths now check state first; (2) the 30s
companion-reconcile loop retried registry pulls/builds forever against
unreachable registries (~174×/image/day on an offline node) — failing
rounds now back off exponentially to a 1h cap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Outside-close listeners move from click to pointerdown: pointerdown
fires before the open-toggle re-render, so the kiosk's slow software
compositing can no longer detach the click target mid-flight and close
the menu the instant it opens. color-scheme:dark (CSS + meta) pins every
native control — select popups, scrollbars, pickers — dark regardless of
the user's OS theme; explicit select/option colors cover Firefox.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three silent failure modes killed nostr peer requests (analyzed from the
live code 2026-07-22): (1) nothing ever polled the relays except the
manual Federation Poll button — a 5-minute background poll now fetches
inbound requests and nudges the websocket when something lands (the
handler's discoverability gate still applies); (2) handshake send/poll
used only the two hardcoded config relays (one defunct) and ignored the
user-managed relay list — every nostr op now uses the merged set;
(3) publish errors were swallowed (let _ =) so 'ok' was reported even
when no relay accepted the event — now a delivery failure is an error
the UI shows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reachability probes ran one at a time with a 30s Tor timeout each, so
Refresh sat disabled for N-offline-peers × 30s. Now the list renders and
the button re-enables immediately; probes run concurrently and fill the
dots in as they land.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BaseModal becomes a flex column: title row and the new #header slot
(tabs) stay fixed at the top, #footer (action buttons) fixed at the
bottom, only the default slot scrolls (default max-h 90vh unless the
caller sets one). Wallet Settings migrates: tabs pinned, four duplicated
in-pane Close rows collapse into one pinned footer that carries the
active tab's primary action.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pine/mempool get explicit icon entries (their extensions aren't .png, so
the default guess 404'd on every render). filebrowser-client: central
authedFetch does ONE transparent re-login when the short-lived JWT
expires (an expired cookie previously kept _authenticated=true and every
Files-card poll 401'd forever), plus a 60s login cooldown so a missing
filebrowser doesn't spam either.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
detected_device_info now carries plugged_at (the /dev node's creation
time; udev recreates it per plug). Dismissals store (path -> plugged_at),
so swapping sticks or a fast unplug/replug between status polls can no
longer be suppressed by an old 'Not Now' (v1 path-only dismissals are
abandoned wholesale).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The QR mirrored the browser origin — an operator browsing over Tailscale
handed the phone a 100.x address it has no route to, so pairing silently
never connected (reported 2026-07-22; the 'random password' alongside it
was the device token working as designed). system.get-hostname now also
returns the default-route LAN IPv4; the QR substitutes it (or the mDNS
name) whenever the origin is localhost or a CGNAT/tailnet 100.64/10 IP.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Linux double-opens ttys, so a probe racing the reconnect loop corrupted
both handshakes into silence (framework-pt, real Reticulum stick). Three
attempts 7s apart land in the listener's backoff gaps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two real install failures from .116 today: (1) the hard "ElectrumX must
be running" gate — but mempool-api reconnects to electrum on its own and
a resync can take days, so it's now a warning; (2) a 10s podman-ps probe
timeout tripped by ElectrumX compaction disk load — now 45s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AppLauncherOverlay sat at z-2400 under BaseModal's z-3000, so an app
opened from e.g. the Transactions modal loaded invisibly underneath it.
z-4000 lets the existing enter animation play over the modal; closing
the app returns to the modal you came from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pruned nodes can't run the Mempool app, but tx links blindly opened it
anyway. Now: local app when running; otherwise an external explorer
(default tx1138.com) behind a one-time amber consent modal that spells
out what the other server's operator learns (tx of interest + IP) and
lets the user point at their own instance (placeholder mempool.guide).
Wallet Settings gains an On-chain tab (explorer URL + don't-warn toggle);
tabs renamed Cashu/Fedi so five fit in the row.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend streams LND /v1/transactions/subscribe (fires on mempool arrival
and each confirmation) and nudges the /ws/db revision per event; the Home
wallet card subscribes and refetches (debounced 800ms). An incoming
broadcast now appears while the 30s poll is still asleep. Reconnects
forever with capped backoff when LND is down/locked/absent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LND's RPC answers before its p2p server finishes loading, so connect/open
during that window failed red with the raw "server is still in the
process of starting". Now: quiet ~30s retry first; if still starting, a
calm amber "still finishing its startup" notice instead of an error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plugging in any LoRa radio now surfaces the device setup modal EVERY time
(dismissals clear on unplug; works while mesh is enabled; 2-poll debounce
so ordinary reconnect blips don't flash it), showing what's currently on
the stick before anything is written:
- mesh.probe-device RPC: identifies the firmware read-only in the same
Reticulum->Meshcore->Meshtastic order as auto-detect. Meshtastic yields
region/modem-preset/channels (want_config is a read); MeshCore yields
name/node-id + firmware version via the previously-unused
CMD_DEVICE_QUERY; RNode via the bare KISS DETECT (no daemon spawn).
Path must be a detected candidate port; the live session's port refuses.
- "Keep As Is": manage_radio=false persists in MeshConfig and the session
skips every on-connect config write (region, channel, PHY params, advert
name) — the radio runs exactly as flashed, hot-swappable.
- "Set Up with Archipelago Settings": second screen shows the params that
will be applied (channel, region, the node's persisted RF params - e.g.
the validated Portugal preset - with the per-region community plan as
fallback) before writing anything.
- Stale-type fix: disconnect now resets device_type/firmware_version, so
a swapped stick no longer shows the previous radio's firmware; probed
kind pins device_kind so the right probe runs first on connect.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both lived only in RAM: every archipelago restart/reboot wiped the whole
chat history (user-reported), and — worse — reset the per-target outbound
sequence counters, so peers' (sender_pubkey, sender_seq) dedup silently
dropped the first messages sent after a reboot as replays.
mesh-messages.json in the data dir (0600 — DM plaintext), restored in
MeshService::new before the listener spawns; a 5s debounced persister
task snapshots at one choke point instead of hooking every mutation path
(store, delivered/transport/encrypted stamps, edits, deletes, prunes).
Atomic write-then-rename; corrupt/missing file skips restore instead of
blocking mesh startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Image default is beam 5 on x86 (1 on ARM). Benchmarked on framework-pt
(i5-1135G7, base-int8, real speech): beam 1 transcribes identical text
~45% faster; extra CPU threads made it slower, so only the beam changes.
App revision 3.4.2 > image 3.4.1 so catalog-driven nodes roll the args
change; a 3.4.1-1 suffix would semver-compare LOWER and never ship.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The seeded announce automation blocked state transitions out of None/
unavailable — but None is exactly the sensor's state before the first
received message (and after the in-memory store restarts empty), so a
fresh node's first DM was silently swallowed (framework-pt 2026-07-22).
Only 'unknown' (the HA-restart marker) still blocks; rest sensors don't
restore state, so restart announce-storms remain impossible. Seeder
upgrades an already-seeded automations.yaml by replacing exactly the v1
clause, leaving user edits untouched.
Sensor scan_interval 30->15s: the sensor only carries the latest message
id, so two messages inside one poll window coalesce into one announce —
halving the window halves both the lag and the coalescing odds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Home wallet card counted every incoming tx with num_confirmations < 3
in the pulsing 'Incoming N' badge. Lightning history hardcodes 1 conf and
ecash/fedimint/ark receives are normalized to 1 conf, so instant payments
qualified permanently — old receives showed as 'incoming' forever (seen
on .228 with week-old lightning orders).
- On-chain keeps confirmation-based logic (< 3 confs, expires naturally).
- Instant rails (lightning/cashu/fedimint/ark) now surface in the incoming
panel only for 5 minutes after receipt — the nice arrival moment without
the perpetual pending state.
- Panel rows: 'Unconfirmed / N conf' badge and mempool link are now
on-chain-only; instant rows get a rail badge instead.
- Home now polls wallet balances+transactions every 30s (like Web5.vue),
so pending on-chain receives actually flip to confirmed and the badge
appears/expires without a manual wallet action.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sensors with a unit (sync %, lightning/onchain sats) rendered the string
'None' when bitcoind is early in IBD or LND is down, which HA rejects
with a ValueError on every 30s scan. Mark them unavailable instead; the
intent scripts already speak a fallback for unavailable states.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regression: only X-Frame-Options apps were routed to the companion's
in-app WebView; iframeable apps fell through to the iframe session, which
is slower on the phone and loses the native back/forward/reload controls.
Now any launch inside the companion (ArchipelagoNative.openInApp bridge
present) goes to the WebView — both openSession and the legacy open()
overlay fallback. Mobile web/PWA keeps the iframe session. With regression
tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HA's seeded REST sensor reads attributes via json_attributes_path
"$.mesh_message"; a null there makes HA log a "JSON result was not a
dictionary" warning on every 30s scan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HA's config-entries store already carries the current schema minor_version,
so entries we append are never migrated — a missing created_at is an
unguarded KeyError in config_entries.async_initialize that crash-loops HA
at boot (hit on framework-pt during install verify). Write created_at/
modified_at/discovery_keys/subentries on both the anthropic and wyoming
entries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The creds-off-argv script used printf "rpcuser=%s" in the manifest's
custom_args; quadlet copies Exec= into the generated service's ExecStart,
where systemd expands %s (user's shell) at load time — bitcoind's rpc.conf
came out as rpcuser=/bin/bash and every RPC consumer got 401s on quadlet
nodes (framework-pt: bitcoin-status, HA block-height sensor, LND chain RPC).
Two-layer fix: quadlet.rs now escapes % -> %% in Exec/Entrypoint/HealthCmd/
Environment emission (with regression test), and the bitcoin manifests write
rpc.conf with plain echo so the catalog also heals nodes still running older
binaries. Release catalog regenerated with the fixed scripts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The info card above the action buttons now shrinks with the viewport and
scrolls internally (min-h-0 + overflow-y-auto in a max-h-full flex column),
keeping Cancel / Sign & Publish always visible. The hero disc and viz ring
scale down on narrow or short screens via a --seg-radius CSS variable so
the animation honors the smaller ring.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pine-openwakeword manifest (wyoming-openwakeword 2.1.0, :10400, /custom model
dir for the future Yo Archy model). Pine 1.3.0: launcher page gains a live
node-status card fed by /api/pine/status via a same-origin nginx proxy, and
copy for the new intents / Claude fallback / mesh announcements. Catalogs,
app-session config, drift ids regenerated; release catalog embeds 56
manifests (unsigned until the ceremony). Framework PT test plan in docs/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
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>
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>
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>
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>