642 Commits

Author SHA1 Message Date
archipelago
3ee20430b0 docs(02-08): confirm KEEP_ALIVE_MAX=6 against on-device memory (FA-D)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m18s
Deployed this phase's frontend + AIUI to archi-dev-box (the dev pair;
x250-dev is currently offline via Tailscale, recorded rather than
silently skipped) via scripts/deploy-to-target.sh --frontend-only.
Verified the served bundle (not just local dist) contains this phase's
changes.

Then took a real on-device measurement instead of leaving the FA-D
KEEP_ALIVE_MAX estimate unexamined: a headless Chromium session on
archi-dev-box cycled all 11 main tabs through 4 full round-trips (44
navigations), reading the JS heap via CDP before/after each cycle.
Memory fluctuated 10-21MB with no monotonic growth across cycles that
each exceed the cap 10 distinct registered paths against KEEP_ALIVE_MAX=6.
Left the constant at 6, now backed by a recorded measurement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 21:19:50 -04:00
archipelago
faf4a75ddf fix(02-07): loading overlay can never wedge the Chat/AIUI UI permanently
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m12s
Belt-and-suspenders fix on top of AIUI's own root-cause fix (the
archyBridge origin bug, fixed in the AIUI repo): the archy-side loading
overlay now gets pointer-events:none (it has no interactive content of
its own, so it should never have blocked clicks reaching the iframe
underneath) and a bounded 8s timeout that unconditionally hides it if
no 'ready' message ever arrives — regardless of AIUI/backend state.
The timeout only dismisses the overlay; it does not fabricate a
successful connection, so the connected indicator still reflects
reality.

Two new tests in chatAiuiEmbed.test.ts cover the timeout firing at
exactly 8s and not firing prematurely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 20:19:28 -04:00
archipelago
e2b2ade3b2 feat(02-07): stable AIUI embed URL carrying both D-14 defaults
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m16s
aiuiUrl now appends chatExpanded=true and mobileChat=true alongside
the pre-existing embedded=true and hideClose=true. Both are static
strings with no reactive dependency, so the computed's value never
changes after first evaluation — preserving the URL-stability
contract 02-04 relies on to keep the AIUI iframe from reloading on a
tab switch.

AIUI reads these two flags (commit 900c0b9 in the AIUI checkout,
recorded in 02-AIUI-D14.md) to start the chat expanded and, on
mobile, on the chat view rather than the context view. The deployed
AIUI build on any node does not yet carry that commit (anonymous push
to the AIUI remote was rejected — see 02-AIUI-D14.md's Deployment
Impact), so neode-ui's two new query params are inert no-ops against
today's deployed AIUI until that commit is merged and shipped; both
flags are additive and harmless in the meantime.

New test file chatAiuiEmbed.test.ts covers: both D-14 flags plus the
pre-existing embedded/hideClose params present in the URL; URL
string-equality across a simulated viewport resize and across a
KeepAlive deactivate/reactivate cycle; onAiuiMessage still rejecting
a foreign-origin message; and aiuiConnected surviving a
deactivate/reactivate cycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:26:52 -04:00
archipelago
926fa60691 feat(02-06): cache Home's system/update/storage groups and guarantee wallet freshness on re-entry
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m29s
- System stats (homeStatus.refresh), update status and cloud storage usage
  are now every-entry, TTL-gated useCachedResource entries (10s/300s/30s),
  hosted in Home.vue rather than inside the homeStatus Pinia store — a
  store's own defineStore(id, setup) runs in a bare effectScope where
  onActivated() silently no-ops (same finding as 02-05's Mesh.vue)
- Wallet is the deliberate exception (T-02-13): a new home.wallet-status
  resource wraps the existing loadWeb5Status() composite fetch and
  revalidates UNCONDITIONALLY on every activation rather than TTL-gated,
  keeps the prior figure rendered throughout, and persist:false (never
  written to sessionStorage). hydrateWalletSnapshot()'s separate localStorage
  path is untouched.
- Read Web5.vue's two existing resources (web5.networking-profits,
  web5.lnd-info) and did NOT share either key: profits is an unrelated
  dataset, and lnd-info's own default persist:true (Web5.vue out of this
  plan's file scope) would leak balance data via its own independent
  refresh cycle regardless of what Home declares, and Home's wallet fetch
  is a strictly broader 7-call composite (not the same single-call dataset)
- dedup:true added to all 12 underlying rpcClient calls (Home.vue's wallet
  composite + checkUpdateStatus, homeStatus.ts's 5 status calls)
- RefreshIndicator wired next to the Home header, bound to the wallet
  resource's loadState
- The websocket wallet-push path and hydrateWalletSnapshot's pre-network
  paint are both left exactly as 02-04 placed them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 17:38:01 -04:00
archipelago
e6ed5536c9 feat(02-06): cache the Server tab's seven load groups with explicit TTL/persist
Some checks failed
Demo images / Build & push demo images (push) Failing after 3m46s
Server.vue already had five of the seven load groups (network summary, FIPS
summary, VPN peers, interfaces, Tor services) on useCachedResource from a
pre-phase commit, but none declared an explicit ttlMs/persist (relying on
the composable's 30s/persist:true defaults) and dedup:true was missing from
several underlying RPC calls. loadDiskStatus was the one remaining plain
uncached fetch, forced on every activation.

- Explicit TTL per group (10s fast tier: network-summary/interfaces/
  disk-status; 30s near-default: vpn-peers/tor-services; 60s near-static:
  fips-summary)
- Explicit persist:false for server.vpn-peers (npub/peer identity) and
  server.tor-services (onion addresses) per T-02-01; other groups persist
- New server.disk-status cached resource replaces the plain fetch; it now
  self-heals via the composable's own onActivated instead of an explicit
  every-entry call
- dedup:true added to all underlying rpcClient calls, including the
  parameterless vpnStatus()/dnsStatus()/diskStatus() convenience methods
- RefreshIndicator wired into a new minimal header row, driven by whether
  any of the six cache entries is refreshing
- RESEARCH assumption A3 settled: read all seven loader bodies; none
  consumes another's result — the concurrent fan-out is correct as-is
- Fixed a keepAliveLifecycle.test.ts assertion invalidated by the new 10s
  network-summary TTL (reactivation now also revalidates that resource,
  adding one more vpnStatus() call the test didn't previously account for)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 17:03:55 -04:00
archipelago
31389bcc3c feat(02-05): cache the six Mesh tab-entry fetch groups without serializing them
- Mesh.vue: six useCachedResource entries wrap mesh.refreshAll(),
  transport.fetchStatus(), refreshFederationNodes(), refreshSelfOnion(),
  refreshSelfDid() and refreshContacts(), each with an explicit TTL
  (10s reachability/transport, 30s federation/contacts, 300s self
  DID/onion) and persist decision (T-02-01: every group carrying peer
  or self identity data is persist:false; only aggregate transport
  status may persist)
- armMeshLive's Promise.all fan-out becomes a single Promise.allSettled
  over refreshMeshGroupIfStale() per group, so a revisit inside TTL
  issues zero RPC, a stale revisit revalidates concurrently (not a
  serial chain, T-02-16), and one rejected group never blocks the rest
- RefreshIndicator wired into the Mesh header, driven by whether any of
  the six groups is refreshing — visible while peer reachability
  revalidates on re-entry so a resumed tab never shows a frozen
  reachability state as current (T-02-13)
- dedup: true added to every underlying rpc-client.ts/mesh.ts/
  transport.ts fetcher call backing the six groups
- useCachedResource() calls live in Mesh.vue rather than inside
  stores/mesh.ts or stores/transport.ts: Pinia's defineStore(id, setup)
  runs in a bare effectScope, not a component instance, so the
  composable's internal onActivated() would silently no-op there; the
  fetchers still wrap the stores' own actions unchanged, so those
  actions' other callers (clearAllMesh, setMeshOnly, the pre-send
  balance check) keep their guaranteed-fresh, uncached reads

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 16:14:32 -04:00
archipelago
03a3e4e0c1 feat(02-04): widen KEEP_ALIVE_PATHS to the full audited main-tab set
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m49s
Task 2 of 02-04 — finishes the remaining tabs' lifecycle audit (Apps.vue,
Discover.vue) and widens registration from the 02-02 tracer's single seed
path to every main tab the profiling pass showed remounting.

- Apps.vue: the 15s "unable to connect" timer follows activate/deactivate
  (idempotent re-arm, cleared on exit) and resets connectionError on entry so
  a since-reconnected node doesn't show a stale error instantly; the intro
  flag stays once-per-session.
- Discover.vue: loadCommunityMarketplace/loadBitcoinPruneStatus now route
  through the same shared 'app-catalog'/'bitcoin.prune-status' cache keys
  Marketplace.vue introduced in 02-02, rather than duplicating the fetch;
  RefreshIndicator wired to the catalog resource's loadState. Discover's own
  dynamic-catalog-first fetcher (fetchAppCatalog with a curated-list
  fallback) is preserved as this key's fetcher for this view — both views
  are valid producers of the same shared cache entry.
- Fleet.vue: confirmed no lifecycle side effects (grep for the five tokens
  found none) — left unchanged, registered as-is.
- keepAliveRoutes.ts: KEEP_ALIVE_PATHS now derives from TAB_ORDER (single
  source of truth) plus /dashboard/discover, deliberately withholding
  /dashboard/settings even though it's in TAB_ORDER — Settings.vue's child
  sections (SystemDangerZone's reboot poll interval,
  VpnStatusSection/KioskDisplaySection/TransportPrefsCard/ClaudeAuthSection's
  one-shot onMounted fetches) were never in this plan's file scope and would
  misbehave under KeepAlive exactly as this plan exists to prevent. Every
  other TAB_ORDER path measured Remounted:true or was unmeasured in
  02-FINDINGS.md, so per the plan's literal exclusion rule (only a measured
  Remounted:false excludes) they all stay registered, including Mesh and
  Chat.
- keepAliveLifecycle.test.ts extended (in the prior commit) with
  shouldKeepAlive true/false assertions across every registered path and six
  secondary-screen paths, plus the KEEP_ALIVE_MAX+2 eviction test against the
  real DashboardRouterView + KEEP_ALIVE_PATHS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 15:06:03 -04:00
archipelago
f177a505b4 feat(02-04): main-tab side effects placed for activate/deactivate lifecycle
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
Task 1 of 02-04 — audits every side effect owned by Home.vue, web5/Web5.vue,
Chat.vue, Cloud.vue, Server.vue and Mesh.vue and places each into one of
three buckets (once-per-session, every-entry, only-while-visible) so their
instances are safe to keep alive once KEEP_ALIVE_PATHS widens in Task 2.

- Home.vue: systemStats/wallet polling, the wsClient wallet-push
  subscription and its debounce timer follow activate/deactivate with an
  immediate re-sync on entry; hydrateWalletSnapshot/checkUpdateStatus/cloud
  usage stay once-per-session.
- Chat.vue: the window `message` listener and ContextBroker follow
  activate/deactivate; aiuiConnected is never reset on deactivate since the
  iframe's one-time 'ready' message won't resend on re-entry.
- Web5.vue: the six child-component data loaders (none use
  useCachedResource internally) and the 30s LND poll move to
  activate/deactivate; the DID lookup and intro flag stay once-per-session.
- Cloud.vue: the per-peer transport/reachability warm-cache
  (loadPeerFiles/loadCounts/loadPeers) re-runs every entry — the one path
  here that bypasses useCachedResource and would otherwise render stale peer
  reachability (T-02-13).
- Server.vue: the previously module-scope-armed 15s VPN poll interval now
  follows activate/deactivate (it used to run forever regardless of
  visibility); loadDiskStatus becomes every-entry.
- Mesh.vue: the entire live-communications surface (window/document
  listeners, the 5s/15s poll intervals, the ws peer-push subscription, and
  the six-way federation/self/contacts refresh) follows activate/deactivate;
  a share-to-mesh handoff via direct navigation is now correctly picked up
  on every activation, not just the first mount.
- useCachedResource.ts: onActivated's staleness check now skips an
  `immediate: false` resource that has never been explicitly fetched, so a
  tab-gated lazy resource (Cloud.vue's Paid Files / My Files walk) isn't
  eagerly force-loaded the moment its owning view is kept alive.
- Every arm/disarm pair is idempotent and duplicated into both onMounted and
  onActivated, since onActivated is a no-op outside a KeepAlive boundary
  (caught by CloudPeersRefresh.test.ts, which mounts Cloud.vue bare) —
  fresh-mount guard flags avoid double-firing the heavier loaders
  (Home/Mesh/Web5/Server) on a KeepAlive-wrapped first mount.
- New neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts
  covers the six lifecycle behaviors plus a real-view assertion
  (Server.vue's VPN poll, mounted inside a real KeepAlive).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 15:04:41 -04:00
archipelago
2668705516 fix(02-02): restore page margins and slide transitions broken by KeepAlive restructure
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m36s
The Task 3 checkpoint failed on the real preview: outer page margins broke
and the up/down main-tab slide animations stopped playing. Root cause: the
02-02 restructure moved view-wrapper (absolute inset-0) onto each view's
root inside the padded wrapper, and split navigation across two sibling
Transitions behind a stable intermediate div — but dashboard-styles.css
scopes every transition as a compound selector on .view-wrapper, which must
be the keyed direct child of .perspective-container.

- Restore the pre-02-02 rendered DOM exactly: single Transition whose child
  is a keyed div.view-wrapper containing the per-route wrapper shape
- Re-integrate KeepAlive via statically-named per-route wrapper components
  (dashboardViewWrappers.ts) so the keyed div.view-wrapper is the cached
  component's own root; cache membership gated by :include on wrapper names
  derived from KEEP_ALIVE_PATHS
- Drop the scroll-retention Map: scroll containers now live inside keyed/
  cached wrappers, restoring the old reset-to-top behavior for non-kept routes
- Pin the visual contract structurally in keepAliveTabs.test.ts (padded
  wrapper classes must render INSIDE div.view-wrapper)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:40:27 -04:00
archipelago
ec89901fa9 feat(02-03): OpenWrt gateway status caches on repeat visits; CloudFolder left as-is
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m43s
- OpenWrtGateway.vue: manual resources-store usage (no TTL gating — every
  mount force-refetched) replaced with useCachedResource (key
  server.openwrt-status, no item id — one gateway per node, fixed route
  with no :id param). load() keeps its explicit force-refresh semantics
  for connect/tollgate actions via a pendingParams closure; onMounted now
  only force-fetches when the cache is missing or past its 30s TTL.
- Fixed a real bug this conversion exposed (Rule 1): `loading` conflated
  'refreshing' with 'loading', hiding the already-rendered status panels
  behind the full skeleton on every background revalidation. Every mount
  used to force a fetch, so this was previously masked — cached content
  never got a chance to render before the skeleton took over. Now only a
  true first-load (no data yet) blocks on the skeleton (D-07).
- secondaryScreenCache.test.ts: repeat-open call-count coverage for
  OpenWrtGateway (within-TTL: one fetch; after TTL: cached paint + one
  more fetch).
- CloudFolder.vue: left unchanged. See 02-03-SUMMARY.md for the
  cache-placement decision and why a clean useCachedResource conversion
  needs a cloud.ts change outside this plan's files_modified scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:21:53 -04:00
archipelago
7c6c487a03 feat(02-03): app detail screens paint from cache on repeat visits
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m26s
- AppDetails.vue: bitcoin-sync and credentials converted to keyed
  useCachedResource (app-details:bitcoin-sync:<id>, app-details:credentials:<id>),
  each keyed by the route app id so two items never collide. Credentials
  is persist:false (credential material, D-08/T-02-01). Both loaders stay
  fire-and-forget from onMounted (already parallel — not touched). Stop/
  restart/uninstall now invalidate() the credentials resource so a stale
  healthy state can't outlive a destructive action (T-02-12).
- MarketplaceAppDetails.vue: the one RPC call in this view that isn't a
  measurement artifact of the Home-tab-transit confound (package.versions)
  is now a keyed useCachedResource (app-details:versions:<id>, 120s TTL —
  near-static catalog metadata). getCurrentApp() is a sync store read and
  the bitcoin-prune check is a plain fetch(), neither need conversion.
- secondaryScreenCache.test.ts: covers per-item isolation (rendered
  content, not just call counts), TTL-gated no-refetch, TTL-lapse
  revalidation, and keep-last-value on a rejected refresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:13:35 -04:00
archipelago
a9a20039eb feat(02-02): tracer tab refreshes silently and every side effect is placed
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m32s
Puts Marketplace.vue's data on the cache and adds the subtle background-
refresh indicator D-05 calls for:

- RefreshIndicator.vue: presentational, state-driven (ResourceLoadState).
  Renders nothing for ready/idle/loading — a first load is the view's own
  skeleton's job, not this component's — and a role="status"
  aria-live="polite" spinner only while refreshing. A fixed-size outer slot
  keeps its appearance/disappearance from ever shifting layout.
- Marketplace.vue: loadCommunityMarketplace()/loadBitcoinPruneStatus() move
  onto useCachedResource behind shared keys app-catalog (300s TTL, persist —
  near-static catalog, D-06 discretion) and bitcoin.prune-status (default
  30s TTL, persist — both non-sensitive/small per T-02-01). app-catalog is a
  shared key so Discover.vue's identical loader picks up the same entry
  without its own conversion in 02-04. Error handling is keep-last-value
  (D-07): a rejected refresh sets the existing communityError banner ref,
  never a toast, and prior app cards stay on screen.
- Side-effect audit (the precedent 02-04 repeats across remaining tabs):
  marketplaceAnimationDone is genuinely once-per-session intro state, stays
  in onMounted; the two data loads needed no onMounted/onActivated hook of
  their own at all — useCachedResource's internal onActivated (wired in
  Task 1) already revalidates them on every kept-alive tab re-entry,
  staleness-gated so a quick revisit issues no fetch. No interval,
  subscription or window listener exists in this view, so no onDeactivated
  teardown was needed either.
- Tests: RefreshIndicator's full render-nothing/render-something matrix
  added to keepAliveTabs.test.ts (no router dependency, safe to colocate).
  The D-07 rejected-refresh-keeps-content-and-no-toast test mounts
  Marketplace.vue itself but lives in a new file,
  views/__tests__/MarketplaceRefresh.test.ts, following the in-repo
  CloudPeersRefresh.test.ts mount-the-view pattern — vi.mock('vue-router')
  is hoisted file-wide, so colocating it in keepAliveTabs.test.ts would
  clobber that file's real createRouter/createMemoryHistory used by the
  DashboardRouterView tests (Rule 3 auto-fix; deviation from the plan's
  literal single-test-file file list, documented here for the SUMMARY).
  Marketplace.vue gains a defineExpose({ loadCommunityMarketplace,
  loadBitcoinPruneStatus }) for tests, mirroring Cloud.vue's existing
  defineExpose({ loadPeers }).

Full suite (87 files / 709 tests), type-check and build all green; the
built Marketplace chunk carries refresh-indicator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:43:34 -04:00
archipelago
385c9d866e feat(02-02): tracer tab survives KeepAlive round-trip with revalidation
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m48s
Wires the phase's shared architecture end to end through one main tab
(Marketplace, the worst-measured revisit per 02-FINDINGS.md):

- keepAliveRoutes.ts: exact-match route classifier (shouldKeepAlive,
  KEEP_ALIVE_PATHS, KEEP_ALIVE_MAX=6), seeded with only the tracer tab's
  path. Deliberately not KeepAlive `include` name-matching (async
  components under `<script setup>` have no inferable name).
- DashboardRouterView.vue: extracts Dashboard.vue's nested RouterView into
  a host where <KeepAlive> is a permanent element (never torn down by
  v-if) with its child conditionally present via shouldKeepAlive(route);
  a sibling Transition renders non-cached routes. Both original wrapper
  shapes (full-bleed chat/mesh vs. padded/scrollable default) are
  preserved via computed helpers on one stable, unkeyed wrapper div; the
  :key moves onto <component> itself. Adds per-route scroll retention
  since the scroll container is now stable across navigations.
- useCachedResource.ts: registers onActivated(() => refreshIfStale())
  alongside the existing onScopeDispose block, closing the gap where a
  kept-alive tab would otherwise never revalidate on reactivation
  (onScopeDispose doesn't fire on deactivate; window focus doesn't fire
  on an in-SPA tab switch). No-ops safely for all 8 existing consumers
  outside a KeepAlive boundary.
- useRouteTransitions.ts: exports TAB_ORDER so 02-04 can widen
  KEEP_ALIVE_PATHS from the same source of truth.
- Dashboard.vue: renders DashboardRouterView in place of the inline
  block; removes the now-superseded detail-route scroll save/restore
  (querySelector target no longer exists post-restructure — the new
  per-route Map in DashboardRouterView.vue is a strict superset).

Tests: keepAliveTabs.test.ts proves an included path's instance survives
a round trip (1 mount, 2 activations) while a detail path remounts (2
mounts); useCachedResource.test.ts proves no refetch inside the TTL,
exactly one refetch after it lapses, safe use outside KeepAlive, and
keep-last-value + sticky-ready semantics on a rejected refresh.

Full suite (706 tests), type-check, and build all green; built bundle
carries the new KeepAlive wiring (web/dist/neode-ui/assets).
2026-07-30 07:15:17 -04:00
archipelago
d54517cf0b fix(demo): companion app skips the demo intro — lands straight on /login
When the demo build runs inside the Android companion WebView
(window.ArchipelagoNative bridge, detected via the existing
isCompanionApp()), all four IS_DEMO intro branch sites now skip the
typing splash and /onboarding/intro and route directly to /login, as if
the intro was already seen:
- App.vue root-boot replay request (companion never requests the splash)
- App.vue post-splash demo routing
- RootRedirect proceedToApp() and the server-up onMounted demo branch

The skip paths write nothing to localStorage/sessionStorage (RootRedirect
skips even its boot log() there), so the browser/PWA demo intro — which
replays on every fresh root boot — is byte-identical to before, and
non-demo builds short-circuit on IS_DEMO before isCompanionApp() runs.
Adds a small isCompanionApp() bridge-detection unit test (700 tests green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 14:11:12 -04:00
archipelago
b80e7c3487 fix(web5): connected-nodes list fills card height on xl — constant gap above footer buttons
The three tab panes (Trusted/Observers/Requests) were hard-capped at
max-h-72 with an mt-auto footer, so a tall sibling Node Visibility card
stretched the shared xl grid row and opened a growing dead gap between
the list end and the Find Nodes / Refresh buttons. The panes are now the
flexible middle of the card's column flex (flex-auto min-h-0, cap lifted
at xl via xl:max-h-none) so the gap is always exactly the footer's pt-4;
below xl the max-h-72 cap and current sizing are unchanged. Footer gets
shrink-0 so buttons can never be compressed by a long list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 14:04:17 -04:00
archipelago
f52c540744 fix(peer-files): free-image lightbox, click-to-open routing, in-app open after every payment rail
- Card click now dispatches through openItem(): owned -> cached viewer,
  paid+playable -> 10% preview, paid non-playable -> pay modal (image is
  never fetched pre-purchase), FREE image -> full-screen lightbox streaming
  from /api/peer-content (fixes the click no-op where the old ternary fell
  through to undefined for non-playable free items)
- Viewer footer caption is state-aware: green 'Owned · unlocked' only for
  owned items, neutral 'Free · shared by peer' for free ones; Save streams
  free files instead of calling content.owned-get
- Lightning / invoice-QR / on-chain payment successes now share
  openPurchased() with the ecash flow: mark owned, autoplay audio in the
  bottom bar or open image/video in the viewer (previously a browser
  download that silently fails on the mobile companion)
- closeViewer only revokes blob: URLs (free items use plain stream URLs)
- Added a regression test: free image click opens the lightbox

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 13:51:36 -04:00
archipelago
66d540f8b5 feat(demo): launch IndeeHub in the in-app iframe via the :2101 proxy
- useDemoIntro: replace DEMO_EXTERNAL_URLS (external-tab workaround) with
  DEMO_PROXY_PORTS; demoAppUrl('indeedhub') now resolves to
  <protocol>//<current-hostname>:2101/ at runtime (no hardcoded host/IP);
  isDemoExternal returns false (kept exported so call sites compile
  unchanged); isDemoApp still true for indeedhub so the NEW_TAB bypass
  keeps it in the in-app session
- useAppIdentity: suppress the identity-picker modal under IS_DEMO — the
  embedded IndeeHub is already signed in via the seeded throwaway demo
  account; real-node picker behavior untouched (IS_DEMO compile-time false)

Verified: 195 unit tests green (IS_DEMO=false path); VITE_DEMO=1 build
bundle contains the :2101 launch logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 12:28:21 -04:00
archipelago
6e2c8d7410 fix(apps): drop IndeeHub open-fullscreen default — opens as panel like other apps
All checks were successful
Demo images / Build & push demo images (push) Successful in 4m38s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 11:40:26 -04:00
archipelago
ac09fc5ded feat(mesh): redesign hop-route visualization — branded, animated, vertical on mobile
- New self-contained HopVizModal.vue (Teleport to body): 560px balanced panel,
  glowing endpoint medallions ringed with EQ segments (ScreensaverRing motif),
  per-transport accent track with staggered relay markers and an animated
  packet traveling sender → recipient
- Vertical stacked chain below 560px (sender top → recipient bottom, packet
  travels downward); prefers-reduced-motion disables all loops
- Accent colors match the chat transport pills exactly (meshtastic mint,
  meshcore orange, reticulum blue, lora amber, fips violet, tor indigo)
- Tor (3 anonymous relays), FIPS (direct P2P) and unknown-transport shapes
  preserved, as are SNR/RSSI + E2E/delivery metadata (now glass chips)
- Old inline modal markup removed from Mesh.vue; .mesh-hopviz-* rules removed
  from mesh-styles.css (shared transport-modal classes untouched)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 11:36:45 -04:00
archipelago
8bea3707ca feat(lightning): instant pay feedback, balances never vanish mid-payment
Some checks failed
Demo images / Build & push demo images (push) Failing after 4m20s
Framework-pt report: a paid invoice stalled the UI with no success
shown, and lightning/total balances disappeared until it settled.
Three compounding causes, three fixes:

- Backend payinvoice's synchronous wait drops 120s → 8s. Fast payments
  (the majority) still settle in one round trip; slow multi-hop routes
  return pending + payment_hash quickly and the caller's 3s poll takes
  over — instead of the modal freezing for up to two minutes.
- payLightningInvoice gains an onPending hook: SendBitcoinModal and the
  scan modal now flip to a visible "Settling…" success pane the moment
  the payment goes pending (safe to close), and the ongoing poll
  upgrades it to Paid — or replaces it with LND's real failure.
- One slow lnd.getinfo poll (5s budget) flipped the Home wallet card to
  "disconnected", hiding balances the user already knew. Three
  consecutive failures are now required (~30s) before the card gives up;
  last-known balances keep rendering throughout.

rpc-client tests 75/75.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 10:47:39 -04:00
archipelago
49ec294dea fix(update): concurrent apply reads as progress, not failure; idle-IO extraction
All checks were successful
Demo images / Build & push demo images (push) Successful in 5m18s
"Another update operation is already running" surfaced as a scary
failure while the update was in fact applying fine (OptiPlex, v1.7.118
rollout). The apply path now joins the in-flight install — same
overlay, same wait-for-new-version polling — and a concurrent download
attempt shows a calm in-progress note (EN+ES strings added). The
backend's tarball extractions run under ionice -c3 nice -n10 so a
200MB update can't starve podman/status calls into multi-minute
timeouts on small disks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 10:38:11 -04:00
archipelago
338bfd43a7 docs: v1.7.118-alpha changelog + What's New
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m9s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 07:53:44 -04:00
archipelago
3da1d0b0f7 fix(ui): mesh unread badges persist seen-state; more button + animated route modal
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
Unread badges came back on every visit (framework showed a phantom "2"
with nothing new): unreadCounts was memory-only, so each page load
replayed the entire message history as "new". Seen-state now persists
as a per-contact highest-seen-message-id watermark in localStorage
(ids are backend-monotonic across restarts); first run after this
ships seeds the watermark from history so nobody gets a wall of stale
badges. Opening a chat advances and persists the watermark for all
twins of the merged conversation.

The hop-route modal the user asked for is now reachable from a visible
per-message "⋯" button (the transport pill remains clickable too), has
a fallback title/branch for messages that predate transport tracking,
and animates: endpoints and link reveal in sequence, a pulse travels
the link, and relay dots blink in order — all disabled under
prefers-reduced-motion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 07:52:56 -04:00
archipelago
d0463196a3 docs: changelog + What's New — Reticulum relay (transport mode) bullet
Some checks failed
Demo images / Build & push demo images (push) Failing after 59s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 06:23:05 -04:00
archipelago
8e51164321 docs: v1.7.117-alpha changelog covers everything since 1.7.116 + What's New sync
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m4s
Rewrites the prepared 1.7.117 section to include the mesh flash flow,
first-class Reticulum fixes, radio-first routing, mesh chat polish, the
transactions-modal phone fixes, services-vs-apps classification, the
lightning slow-payment fix, cached-resource page loads, load-shedding,
FIPS uptime hardening, and companion 0.5.25. What's New modal block
regenerated from the new bullets (sync-whats-new --check passes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 05:49:45 -04:00
archipelago
da14c135e4 feat(apps): backend-only services classify as services with no Launch button
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
A published port no longer implies a web UI. The package scanner used to
synthesize interfaces.main.ui="true" for any container with a port or
onion address, so headless backends — including self-deployed compose
stacks like podsteadr — showed up as launchable apps. New ui_detection
module decides instead: a manifest interfaces declaration (catalog
overlay first, disk second) is definitive; undeclared apps get a short
HTTP probe of the launch port (HTML page, redirect, or browser auth
wall = UI; JSON APIs, raw TCP, dead ports = service), with cached
verdicts and probes gated on running containers. Frontend canLaunch
now refuses curated services outright and only treats a bare runtime
address as launchable for curated known apps.

Works identically for manifest apps and containers deployed by hand
outside the orchestrator. ui_detection tests 6/6, frontend suite
696/696.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 05:48:38 -04:00
archipelago
6ba39041d0 feat(ui): mesh header "Flash LoRa" button opens the in-app flash flow
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m9s
Replaces the external flasher.meshcore.co.uk link with a button that
opens the global device-setup modal directly at its flash step, via a
new manual entry point in the mesh store (flashFlowPath). Manual opens
target the connected radio (else the first detected stick), skip the
read-only probe — the port is held by the live session and a second tty
opener corrupts it; the backend flash job stops the listener itself —
and close the modal instead of stepping back to the detection screen.
Button is disabled with a hint when no radio is present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 04:43:38 -04:00
archipelago
0aa3941c40 feat(ui): mesh chat polish — transport pills in image modal, hop-route modal, reaction dropdown, real read-tracking
Some checks failed
Demo images / Build & push demo images (push) Failing after 3m29s
- Image quality modal: 'Send via' pills (LoRa / FIPS / Tor) when the
  peer is federation-reachable — mesh.transport-advice now returns
  has_fips + last_transport alongside has_tor. Picking FIPS/Tor routes
  the image over the content-ref path instead of the radio.
- Attachment modals (transport chooser, image quality, new hop modal)
  Teleport to body so the backdrop dims the FULL viewport — rendered
  in-place they sat inside a transformed glass panel that trapped
  position:fixed to the right chat panel.
- Click a message's transport pill → route modal: radio hops + live
  SNR/RSSI quality for LoRa transports, overlay/circuit shape for
  FIPS/Tor, delivery + E2E state.
- Reactions move behind a compact 'React ▾' dropdown with a larger
  12-emoji palette.
- Unread badges now clear like a normal chat app: opening a contact
  clears ALL twins of the merged conversation (badge sums every
  contact_id — clearing just the clicked one left it stuck), and only
  once the chat has scrolled to the latest messages; scrolled up into
  history, new arrivals accumulate until you scroll back down.
- Refresh button shows only the spinner while refreshing (text+spinner
  overflowed the fixed button width).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 21:01:36 -04:00
archipelago
79c3cc5947 fix(mesh): radio-first transport policy + attachments to merged contacts route via the radio twin
Some checks failed
Demo images / Build & push demo images (push) Failing after 3m50s
Two halves of the same twin-resolution gap, found live while testing
images between archi-dev-box and archy-x250-dev:

- peer_dest_prefix resolved the given contact row's own pubkey. For the
  UI's merged conversation (the federation-synthetic id) that's the
  Archipelago ed25519 identity key, NOT a radio routing key — so every
  Reticulum resource send (images/files over LoRa) failed with 'Unknown
  Reticulum prefix' while the UI showed the message as sent. It now
  resolves through the radio twin (same arch identity, radio-range id).
- send_typed_wire sent EVERY federation-synthetic contact over the
  federation path (FIPS→Tor), even with the same node one LoRa hop away.
  Policy per operator: LoRa first when the payload fits and the radio
  twin is reachable, then FIPS, then Tor. Verified live: text to the
  merged contact now logs 'Radio-first routing' and lands with
  transport=reticulum on the peer.

Also restyles the mesh-chat attachment download controls: the pre-fetch
button was a bare .btn that squished to text width in the narrow mobile
bubble; now a full-width glass pill with a download icon and fetch
spinner, and the on-image overlay swaps the emoji glyph for a crisp SVG
in a properly-sized glass circle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 18:16:40 -04:00
archipelago
3f76b4960a feat(ui): mesh Refresh/Broadcast feedback, Set Recommended modal with probe progress bar, live list refresh
All checks were successful
Demo images / Build & push demo images (push) Successful in 3m43s
- Refresh button: real handler — calls the new mesh.refresh (radio
  re-query) plus contacts/federation/outbox re-reads, disabled with a
  spinner while running (was an unawaited cache repaint with no feedback
  that skipped half the list's data sources).
- Broadcast button: success ('Sent ✓') and failure states with the error
  in the tooltip; failures no longer vanish as unhandled rejections.
- The store's 5s status poll no longer wipes the error banner each tick.
- Contacts/aliases, federation nodes and the outbox badge refresh every
  ~30s (were mount-only and went permanently stale).
- Peer-list empty state keys on the merged list, so federation rows and
  the channel rows still render with no radio attached.
- Device setup modal: 'Set Recommended' naming, probe progress bar with
  stage labels instead of an anonymous spinner.
- Device panel: name save clears properly (empty = fall back to server
  name) and the confirmation reflects the new live apply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 16:37:16 -04:00
archipelago
537c52d11c feat(ui): FIPS network + seed-anchor cards render from cached resources (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m6s
The two FIPS containers on the Server page were the last network cards
still fetching-on-mount into local refs — every visit was a blank card
until fips.status / fips.list-seed-anchors answered. Both now ride the
cached-resource layer: FipsNetworkCard shares the server.fips-summary
key with the Local Network card's FIPS row (one fetch, never disagree),
seed anchors cache under server.fips-seed-anchors, and mutations
write the RPC's authoritative result straight into the cache. The 15s
status poll skips hidden tabs — revalidate-on-focus covers the return.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:48:34 -04:00
archipelago
8907cc47d9 feat(ui): Credentials + OpenWrtGateway render from cached resources — B4 complete
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m10s
- Credentials: identity.list + identity.list-credentials become
  useCachedResource entries; explicit reloads still toast on failure.
- OpenWrtGateway: openwrt.get-status caches in the shared store (revisits
  paint the last router state instantly); the connect flow's
  params/No-router-configured semantics are preserved on top of the entry.
- ContainerApps assessed and left as-is: its Pinia store already persists
  across navigation, keeps last data on error, and gates the spinner on
  empty — same class as Apps/Marketplace/Fleet.

This closes the B4 rollout list from docs/FIPS-UPTIME-AND-UI-STATE-PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 06:27:05 -04:00
archipelago
8fd72b947a test(ui): adapt SWR contract tests to the cached-resource layer — 692/692
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m9s
The keeps-data-visible-while-refreshing tests for PeerFiles, Server, and
LightningChannels mounted without Pinia (the converted components now
pull the resources store in setup) — add createPinia to the mounts.
LightningChannelsPanel: refresh the main channel list before the closed
history so the primary entry gets the first response, and null-guard
both fetchers' response shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 05:55:12 -04:00
archipelago
ea254f63af feat(ui): Server page renders from cached resources (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m9s
network summary (4-RPC allSettled aggregate), fips row, vpn peers,
interfaces, and tor services become useCachedResource entries — revisits
paint instantly, background refreshes keep content on screen. Mutations
write through the cache: DNS apply + the 15s vpn poll patch the network
aggregate via optimistic() instead of refetching all four RPCs; peer
removal filters the cached list. loading/refreshing flags derive from
entry loadState (drops the hand-rolled hasLoaded bookkeeping).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 05:45:17 -04:00
archipelago
1a306c7450 feat(ui): Federation adopts the cached-resource store (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m7s
federation nodes + dwn.status become useCachedResource entries: revisits
paint the node list instantly, `loading` fires on true first-load only
(the old showLoader semantics), the 5s poll refreshes silently like the
old surfaceErrors:false path, and explicit reloads after mutations still
surface failures in the error banner. Replaces the hand-rolled
loadNodesWithOptions SWR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 05:36:26 -04:00
archipelago
43e50e669e feat(ui): Monitoring renders from cached resources (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m7s
monitoring.current/history/alerts/alert-rules become useCachedResource
entries: revisiting the page paints the last snapshot, chart, and alert
list instantly and the 5s poll revalidates behind them (refreshes dedup
in the store; errors keep last-known values instead of blanking).
Alert-rule toggles and acknowledgements refresh their entries after the
mutation as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 05:22:06 -04:00
archipelago
529c7fe25d feat(ui): Web5 wallet/profits render from cached resources (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m2s
- lnd.getinfo and wallet.networking-profits become useCachedResource
  entries (web5.lnd-info / web5.networking-profits): revisits paint
  instantly from cache, errors keep last-known values, refreshes dedup.
- walletConnected is now derived from the lnd-info entry (with a manual
  disconnect override preserving the connect/disconnect toggle).
- Drop the eager wallet.ecash-balance + lnd.gettransactions loaders and
  their 30s polling — they fed only the hidden wallet card; the lnd-info
  poll remains for the connected pill until B5 moves it to WS-push.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 05:03:47 -04:00
archipelago
d605d0d544 feat(ui): PeerFiles renders from the shared peer-browse cache (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m11s
- PeerFiles.vue reads the SAME `cloud.peer-browse:<onion>` entry Cloud.vue's
  per-peer fan-in fills, so Cloud → peer files paints instantly from cache
  and revalidates behind it; catalog/error/loading/transport are now
  computed views over the store entry.
- preview-peer fan-out is capped at 3 concurrent with a queue (was one 30s
  RPC per media item, all at once, unbounded) and aborts on unmount.
- browse + preview RPCs drop to maxRetries:1 — retry×3 turned one slow
  peer into a 90s spinner.
- fix useCachedResource's interface types: `ReturnType<typeof computed<T>>`
  resolves to the writable overload (WritableComputedRef), which broke
  vue-tsc against the plain computed() returns; use ComputedRef<T>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 04:46:26 -04:00
archipelago
c83bade022 feat(ui): Cloud page renders from cache — per-peer incremental fan-in, live FIPS/Tor badges, per-path folder cache
Some checks failed
Demo images / Build & push demo images (push) Failing after 3m24s
Part B3 of docs/FIPS-UPTIME-AND-UI-STATE-PLAN.md — the worst
fetch-on-every-navigation offender converted to the cached-resource layer:

- section counts / peer nodes / my files / paid items are cached resources:
  revisits paint instantly, refresh happens behind the content
  (sticky-ready), errors keep last-known data
- peer files: per-peer cached browse entries replace the all-or-nothing
  Promise.allSettled — each peer's rows render the moment it answers, with
  "still fetching from N peers" + unreachable counts; browse-peer runs
  with maxRetries:1 so one dead peer costs its timeout once, not ×3
- peer cards get a live transport badge (FIPS green / Tor amber, with
  measured latency) from the transport field the browse response already
  carried — the per-peer FIPS-uptime view, for free
- cloud store: per-path listing cache with stale-while-revalidate
  navigate() and a last-wins guard; CloudFolder no longer reset()s the
  store on every folder entry (that wipe forced a spinner each time)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 22:49:30 -04:00
Dorian
c598bb8796 fix(android): preserve top inset for fixed app headers 2026-07-27 20:11:02 +01:00
Dorian
70996203f9 fix: complete fips unit fallback coverage 2026-07-27 19:28:53 +01:00
Dorian
709922c293 fix: harden fips startup and app port relays 2026-07-27 19:18:39 +01:00
Dorian
24582128c5 fix(ui): re-pad mobile dashboard when Android injects safe-area top
The mobile nav sampled --safe-area-top once at mount, but the companion
WebView injects it asynchronously. An authenticated session mounts the
dashboard before the injection lands (fresh installs mount after login,
long after it), so the content padding baked in 0 while the fixed tab
bar grew by the real inset — content slid underneath by exactly the
status-bar height, the update-install-only overlap.

Re-read on the WebView's new archy-insets event, with a retry ladder as
fallback for APKs that predate the event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:12:20 +01:00
archipelago
9cd507269c fix(lightning): never report a slow in-flight payment as failed
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>
2026-07-27 05:33:02 -04:00
Dorian
6dffd8e2e8 chore: bump version to 1.7.116-alpha + changelog/What's New
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m59s
Bundles all post-115 fixes into an OTA release: boot READY-before-recovery
+ Restart=always (no more 'server starting up'), and the app-install
daemon-kill fix (v6 relay only bridges live app ports; port-cleanup
excludes our own PID).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 07:59:48 +01:00
Dorian
9739bbb3db docs+fix: ship fips0 web-UI firewall allowance from core; changelog + What's New for v1.7.115-alpha
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
fips::config::install() now writes /etc/fips/fips.d/80-web-ui.nft
(tcp 80/8443 accept) and reloads the baseline on every install/upgrade —
the hardening firewall default-denies inbound on fips0 and the UI was
unreachable over the mesh without it (root-caused live 2026-07-26).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 22:03:25 +01:00
archipelago
0365cc0f9d docs: changelog + What's New for v1.7.114-alpha
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m52s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 12:24:36 -04:00
archipelago
0880824fb3 fix(ui): seed QRs use the SeedQR standard so hardware wallets can scan them
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m52s
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>
2026-07-26 12:09:43 -04:00
archipelago
c8d0dda656 feat(ui): Words / QR code tabs on LND seed reveal + onboarding seed
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m54s
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>
2026-07-26 07:34:19 -04:00
archipelago
0fadbb1d0f docs: sync What's New modal for v1.7.113-alpha
Some checks failed
Demo images / Build & push demo images (push) Failing after 48s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:29:48 -04:00