npm run build's vue-tsc -b pass caught TS2532 (possibly-undefined array
access) on two array-index reads the vitest run alone doesn't type-check —
optional-chain them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Following the confirmed 2026-07-30 Coinkite COLDCARD low-entropy incident,
plan three deliverables as a single 3-task quick plan:
1. docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md — evidence-backed audit of
every key-material path (Rust/TS/shell), adjudicating research findings
[ARCHY-1]..[ARCHY-4] with file:line evidence, incl. the one-ISO-many-nodes
correlation risk and an explicit UNVERIFIED on-node checklist.
2. docs/security/PSBT-SIGNING-ARCHITECTURE.md — descriptor watch-only,
wsh(sortedmulti) multisig, air-gap transport, honest LND limits
(channel/revocation/HTLC keys cannot be air-gapped), hot wallet as
explicitly secondary, migration path, phased rollout.
3. Remediation backlog into docs/UNIFIED-TASK-TRACKER.md + one gated
hardening fix (explicit-OsRng injection at the mnemonic call site) proven
by a known-answer test that cannot exist before the change.
Audit-and-spec only — no wallet/signing implementation. Verify gates enforce
file:line evidence density and a secret-shaped-string check on both docs, and
assert no commit authored by this plan touches the concurrent agent's files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dorian verified the sibling-height match, internal scroll, and unchanged
stacked layout on his running dev session — all correct. The only issue was
the xl:min-h-[20rem] floor (an unmeasured judgement call, flagged as such in
01-12-PLAN.md): when node discovery is disabled, Web5NodeVisibility renders
short, the floor takes over, and 20rem left the Connected Nodes card looking
stunted. Doubled to xl:min-h-[40rem] per his direct instruction ("twice as
tall"). Test updated to pin the new value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 2 of 01-01-PLAN.md, closing the gap left after Task 1's initial commit
(2f99db5e):
- record_peer_transport and update_node now hold FEDERATION_STORE_LOCK for
their whole load-mutate-save cycle via the *_inner variants, instead of
calling the public (separately-locked) load_nodes/save_nodes — closing
the same class of race the lock was introduced to fix, just for the two
mutators Task 1 didn't reach.
- Add test_remove_errors_when_tombstone_write_fails: pre-creates the
removed-nodes path as a directory so the tombstone write fails, then
asserts remove_node returns Err AND load_nodes still contains the node —
proving a failed removal never half-applies.
cargo test -p archipelago federation::storage: 14/14 green (was 11, +3 across
Task 1/2). cargo build -p archipelago: no new warnings, no dead-code warnings
on any *_inner fn. Public signatures unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cloud.vue's viewPaidItem() called window.open() instead of the in-app
MediaLightbox, and its content.owned-get fetch had a 60s timeout with no
loading indicator and a swallowed catch. Moves the fetch/decode/route logic
into a new usePaidItemViewer composable: image/video route to a second
MediaLightbox instance fed a synthetic FileBrowserItem, audio still goes to
the global bottom-bar player, and anything with no in-app viewer keeps
today's browser-tab fallback. The Paid Files row now shows an "Opening…"
spinner (matching PeerFiles' existing treatment) for the fetch's duration,
becomes non-interactive to prevent double-fetch, and a real error surfaces
through the view's existing alert-error block instead of an empty catch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
usePipSession() owns an off-screen div under document.body; adopt(video)
moves the element there so it survives the unmount of whatever view
rendered it (a Teleport and a KeepAlive'd view both move their subtree on
deactivation, which the picture-in-picture spec treats as removal).
release() tears down playback and detaches the element; a
leavepictureinpicture listener on the adopted element is the primary
release path so an orphaned owner can never leak it.
Also adds isPipSupported() to pip.ts — a call-time version of the existing
import-time pipSupported const, needed because a test can't restub
document.pictureInPictureEnabled after import. togglePip and pipSupported
are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three tab panes carried max-h-72 xl:max-h-none, so at the xl breakpoint
(where the Web5 row becomes two grid columns) the cap lifted with nothing to
replace it: the visible pane grew to fit every row, stretched the grid row,
and the scrollbar the user expects never appeared.
Give each pane xl:flex-1 xl:basis-0 xl:max-h-none instead — zero flex-basis
means the pane contributes no intrinsic height, so the grid row is sized by
the Web5NodeVisibility sibling alone, grid's default align-items: stretch
gives the card that height, and flex-1 hands the leftover height back to the
pane, which scrolls inside it via the existing overflow-y-auto. The card root
gets min-h-0 (so the flex column can shrink below content height) plus an
xl:min-h-[20rem] floor so a short sibling still leaves a usable list area
instead of collapsing to the header+tabs strip.
Below the row breakpoint nothing changes: the stacked cap (max-h-72) and
scroll are untouched, and dropping flex-auto (replaced by nothing, i.e. the
default 0 1 auto) has no visible effect since single-column stacked cards
have no extra flex space to distribute anyway.
Adds Web5ConnectedNodesScroll.test.ts to pin the contract across all three
tab panes and the card root so a future cleanup cannot reintroduce the
grow-to-fit regression a third time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Release and OTA assets are live; the installer ISO was blocked three times by
a dirty shared tree. Records the exact command, TMPDIR requirement, the
background-execution lesson, the build-from-HEAD decision and its reasoning,
and which gate stages were already observed passing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
apps/botfights/manifest.yml went to 1.2.11 in aea17248, but the two unsigned
catalogs (app-catalog/catalog.json and its neode-ui/public copy) still
advertised 1.2.9, failing the release gate's catalog-drift check and blocking
the ISO build. releases/app-catalog.json was already correct and signed.
Regenerated via scripts/generate-app-catalog.py (syncs from manifests, no key
needed). app_ports.rs was rewritten by the same generator; verified the port
set is byte-identical in content (35 ports, none added or removed) and
re-normalised with cargo fmt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ships botfight repo commits f5f57e6, c162d5e, 41f1b93, 10d4209:
- Fixed 6 instances of the same trust-a-client-supplied-pubkey pattern
across auth/payments/queue routes, two of them critical: GET
/winnings/:botId had no auth at all and leaked live spendable Cashu
bearer tokens; POST /connect-wallet let anyone redirect a victim
bot's future payouts to an attacker's wallet by pubkey (public by
design in nostr).
- Added owner-reachable AI-answer settings (existing bots, not just at
creation) and a "claim your winnings" UI (Cashu payouts were minted
server-side but had no frontend consumer at all until now).
Co-Authored-By: Claude <noreply@anthropic.com>
Both the 20G sparse qcow2 test disk and the serial console log were
hardcoded to /tmp regardless of $TMPDIR, so pointing the ISO release
build at a disk-backed scratch dir (to avoid tmpfs space pressure)
would not have covered this stage. Falls back to /tmp when TMPDIR is
unset — no behavior change for existing callers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed by Dorian via scripts/sign-manifest.sh; signature verified
against the pinned release-root did:key by the signing script itself
and independently re-checked here (check-release-manifest.sh: version,
changelog line count, and both components' sha256/size all match the
on-disk artifacts).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
create-release-manifest.sh's changelog extraction pulls every non-blank
line between the version header and the next "## ", not just "- "
bullets — so the previous commit's "### Known gap" markdown heading and
its paragraph leaked into releases/manifest.json (and, via
sync-whats-new.py, the Settings "What's New" modal) as a malformed,
truncated entry (the closing clarification sentence was cut by the
extractor's 10-line cap).
Rewritten as a single "- " bullet, matching every other CHANGELOG entry,
so it renders cleanly and completely in both the OTA manifest and the
in-app modal instead of showing raw "### " syntax to node operators.
Also folds in core/Cargo.lock's version bump, which create-release.sh's
own commit step omits from its `git add` list.
Same binary/frontend artifacts as the prior commit (identical sha256/
size in the regenerated manifest) — only the changelog text changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
scripts/sync-whats-new.py --check (part of the release gate) requires
every CHANGELOG version to have a matching block in the Settings
"What's New" modal. Also strips CHANGELOG markdown bold/italic markup
from the v1.7.119-alpha bullets first — the modal renderer only
strips backticks, not **/* emphasis, so it would have leaked literal
asterisks into the user-facing modal text.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Whitespace-only reflow in storage.rs/seed.rs/update.rs (rustfmt line-
wrapping rules) and app_ports.rs (array literal reflow after the port
list grew). No logic change. tests/release/run.sh's cargo-fmt --check
stage was failing on this before v1.7.119-alpha could be cut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Written ahead of create-release.sh, which validates a curated
CHANGELOG.md section already exists rather than generating one.
Leads with FED-08 (private-channel invoice route hints) and FED-09
(doctor/Tor setgid restart-loop fix), covers phase-02 sessionStorage
security hardening and the PWA auto-update decision, and records the
5x lifecycle gate omission (node .228 unreachable) as a known gap
rather than a footnote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Untracked Vite cache dir was tripping build-iso-release.sh's
git-status-porcelain clean-tree preflight check.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pure npm metadata: 'peer': true flags dropped by a differing npm version.
No dependency added, removed, or version-changed. Committed rather than
reverted so nothing another session did is discarded; the ISO release
preflight requires a clean tree on main.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 02 (ui-performance) verified passed. 12 plans (8 planned + 4 gap
closure). PERF-01/02/03 all met. Two residuals formally overridden by the
user: Discover's entrance-animation replay (animations are his domain) and
OpenWrtGateway (no device connected, unmeasurable).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reassessed after the OpenWrtGateway override (commit b4350e24) closed the
single open item from the prior human_needed pass. Judged the override on
its merits: well-formed, corroborated by a direct first-person quote from
Dorian this session, with one noted imprecision (the rationale slightly
overstates that no measurement at all is obtainable, when the
disconnected-state UI could technically still be re-measured) that doesn't
change the substance of a legitimate stakeholder scope call. With both
residual, non-poller-fixable costs (Discover's animation replay,
OpenWrtGateway's untestable hardware dependency) now individually accepted
by rationale-backed override, and every other named regression fixed or
substantially recovered, status is passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No OpenWrt device is connected to this node, so the surface cannot be
exercised and no post-fix measurement is obtainable. The user decided to pass
it for this milestone. Also notes that its earlier baseline/regressed figures
measured a disconnected-device UI rather than the real screen, and records the
residual risk for the next milestone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Independently re-derived all 02-11/02-12 claims: recomputed every claimed
median directly from 02-PERF-FINAL.json's raw samples (all match), confirmed
the frozen perf harness is still byte-for-byte untouched, re-ran the
keepAliveLifecycle regression suite (19/19) and the full workspace suite
(788/788), and confirmed the three leaked-poller fixes are genuinely wired
in source. Discover's override is well-formed and corroborated across three
independent artifacts. Status: human_needed — OpenWrtGateway is the one
named regression with zero post-fix measurement (crashed for an unrelated
reason) and no override scoped to it; everything else is either verified
fixed, substantially recovered with an honest residual, or formally
overridden by Dorian.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dorian decided entrance-animation behavior is his domain and is not to be
changed, cancelling this gap-closure plan before any source file shipped.
The composable drafted during investigation was deleted (never imported).
Preserves the investigation findings (blast radius, the ~241ms-1716ms
measured cascade, the first-paint-901ms-of-1095ms evidence, the Apps-revisit
184-267ms calibration, and the pre-phase-2-baseline note) so the analysis
isn't lost or re-done. Discover's revisit cost stands as a formally accepted
deviation at its 02-11 FINAL number.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signs releases/app-catalog.json after the corrected manifest fix in
3c7a1fbb (data_uid: 999:999, matching the image's actual internal UID).
Co-Authored-By: Claude <noreply@anthropic.com>
The re-verification required either a fix or an accountable stakeholder
decision. Plan 02-11 fixed four of five measurable surfaces. 02-12 isolated
Discover's remainder to the card-stagger entrance animation replaying on every
revisit; the only fix changes animation behavior, which Dorian rules off-limits
as his domain. Decision recorded with rationale, evidence and attribution.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prior fix (5745db51) copied the fedimint-clientd/barkd data_uid
pattern (1001) without verifying it against this image. Live on
x250-beta the container still crash-looped with the same SqliteError:
unable to open database file — `podman exec botfights id` showed
uid=999(botfights) gid=999(botfights), not 1001. The image's
Dockerfile does `useradd --system` with no explicit UID, which lands
at 999, and security.user in the manifest is descriptive only — it is
not read by this app's (non-Quadlet) install path, so it can't be used
as the source of truth either.
Fixed data_uid to 999:999 and corrected security.user to 999 to match
reality. Verified live: manually re-chowned the existing bind mount to
999:999 on x250-beta, restarted the container, confirmed
`database migrated` + `listening on http://localhost:9100` +
`/api/health` returns ok, and confirmed /api/bots on x250-beta returns
identical data to the canonical arena (arena-proxy forwarding
correctly). Regenerated catalog; cargo test -p archipelago-container
manifest passes (38/38).
Co-Authored-By: Claude <noreply@anthropic.com>
Signs releases/app-catalog.json after the manifest fix in 5745db51
(absolute bind-mount path + data_uid: "1001:1001" for botfights).
Co-Authored-By: Claude <noreply@anthropic.com>
Two root-caused bugs found live during a fresh install on a second node
(x250-beta) that never surfaced on archi-dev-box by accident of that
node's prior state:
- volumes.source was a bare relative "botfights-data" instead of an
absolute host path, inconsistent with every other app's manifest.
Resolved to /var/lib/archipelago/botfights on archi-dev-box but to
/home/archipelago/botfights-data on x250-beta, which doesn't exist
there. Fixed to the absolute path, matching netbird-server and every
other app's convention.
- data_uid was missing entirely. The container runs as internal UID
1001 (security.user), but without data_uid the orchestrator's bind-dir
ownership fixup only fires via a same-owner-as-anchor fallback that
assumes no-data_uid apps run as container-internal root. Root cause of
a real SqliteError: unable to open database file crash-loop on
startup. Fixed by adding data_uid: "1001:1001", same pattern as
fedimint-clientd and barkd.
Bumped catalog via generate-app-catalog.sh; cargo test -p
archipelago-container manifest passes (38/38).
Co-Authored-By: Claude <noreply@anthropic.com>
releases/app-catalog.json regenerated and signed by the release-root key
(did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur). botfights is
the only content change vs the previously-published catalog (verified
structurally — all other 65 apps unchanged): 1.2.8 -> 1.2.9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bumps to botfights:1.2.9, which carries: Cashu token payment wired as the
primary entry-fee UX in WalletConnect.vue (was built server-side already
but never called from any UI), Lightning/NWC demoted to secondary, and a
fix so anonymous poll-mode bots (not just nostr-authenticated humans) can
use ranked/staked fights — join-ranked previously required a pubkey
unconditionally, silently locking out the entire AI-agent audience.
No archy-side manifest changes beyond the version/image bump.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
02-11 named a second, distinct cause of Discover's revisit slowness with
full profiling evidence (card-stagger/home-card-animate classes never
removed from the DOM, so Chromium restarts the CSS entrance animation on
every KeepAlive reattach) but deliberately left it unfixed — the blast
radius (Discover/Apps/Marketplace/Home + Web5 sub-cards) exceeded that
plan's scope. This gap plan fixes every affected site with one shared
composable rather than per-file patches, decouples Home.vue's dual-purpose
animateCards ref so its overlay/EasyHome visibility logic stays untouched,
and ends at a blocking human-verify checkpoint before any SUMMARY is written.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix_tor_permissions() exact-matched stat's output against the literal
string "700", but stat -c '%a' omits leading zeros so Tor's own
setgid HiddenServiceDir mode (2700) never matched. Every ~5-minute
doctor run "fixed" it back to 700, restarted Tor, and Tor immediately
set 2700 again — so Tor never survived long enough to build a usable
consensus/HSDir cache, breaking the mesh's Tor fallback entirely.
- Compare only the last 3 mode digits (owner/group/other), which is
the property that actually matters, so 700 and 2700 both pass while
750/707/2755 etc. are still corrected.
- Add a 30-minute restart backoff (timestamp file under
/var/lib/archipelago/) so no future condition can reproduce a
restart storm even if the fix fires repeatedly.
- Log clearly in both directions: a debug-level no-op line when a
directory is already correct, and an explicit "was NOT fully
denied" line when a real fix is applied, plus a line when a restart
is skipped by the backoff.
Verified statically against temp directories (2700 accepted with 0
restarts; 750/707/2755 corrected with exactly 1 restart; a second
real fix inside the backoff window logs a skip instead of
restarting). No live Tor/doctor/systemd unit was touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The code-fixer correctly declined this change twice on relayed consent. It was
then made by the orchestrator holding first-hand authorization from Dorian, who
chose forced auto-update with the mid-payment reload risk explicitly stated and
rejected warning UI. Records the quote, and that the implementation reused the
existing kiosk auto-apply path (preserving the cinematic and first-install
guards) rather than flipping build-time skipWaiting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 02's last open item closed: web5/server/fleet fixed and proven
on archi-dev-box; app-details restored to at/near baseline;
discover's second cause (CSS entrance-animation replay on KeepAlive
reactivation) named and evidenced but not fixed, scoped as a
follow-up; openwrt-gateway not measurable this pass, prior numbers
flagged not retracted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
container-doctor.sh's fix_tor_permissions() exact-matches mode '700', but Tor
sets its hidden-service dirs to 2700 (setgid). Every 5-minute doctor run
'fixes' 2700->700 and restarts tor@default; Tor resets it and the cycle
repeats, so Tor never builds a usable consensus/HSDir cache. Result on a live
node: 'No more HSDir available to query', onion peers unresolvable, and with
the FIPS direct path also timing out, mesh sends failed entirely.
Planned as 01-20 in wave 1 so it ships in the same release. FIPS direct
connect_fail is a separate concern, handed to FED-03's transport review.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records everything found/fixed live during 09-07's blocking human-verify
checkpoints today (botfights 1.2.2-1.2.8): iframe embed, native signer
bridge, mode-picker discoverability, proxy-URL leaks, round-jump backfill,
broken profile images (CSP), AI-answer discoverability, and the webhook_test
signature exception. Also records the full threat register worked through
for the user-directed Cashu fixed-stake entry-fee request (21 sats, winner
takes all) — mint configured and verified live (Minibits), actual
token-accepting implementation deliberately NOT done yet, scoped as a
follow-up.
Note: this section was written once already earlier in the session and
appears to have been lost to a shared-tree overwrite before it was
committed (CLAUDE.md "Concurrent agent in shared tree" hazard) — recreated
and committed immediately this time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deployed the poller fix to archi-dev-box and re-ran the frozen harness
(02-PERF-FINAL.json, 5 runs/surface). Web5 fixed (275ms, below both
its 566ms pre-phase-2 baseline and the 300ms pass bar). Server's
regression closed (574ms, below 738ms baseline) though not yet under
300ms. Fleet substantially improved (790ms, down from a 2631ms
regression). AppDetails restored to at/near its own baseline.
Investigated the two open items the coordinator raised:
- OpenWrtGateway: this run's 5/5 samples failed with a Chromium
"Target crashed" error cascading from an unrelated surface earlier
in the same harness run — recorded as not-measurable, not written
in as data. Separately confirmed via source (OpenWrtGateway.vue's
h1 renders unconditionally, and a "No router configured" RPC error
deterministically shows a real Connect-to-Router form) that the
prior baseline/after/remeasure numbers were measuring a genuine,
substantive disconnected-state UI render, not an empty/error page —
so the "six confirmed regressions" count is not retracted, but the
numbers are flagged as reflecting one specific code branch.
- Discover (1389ms, worst remaining, least improved): profiled
directly and found a SECOND, distinct, phase-2-class cause —
showStagger/card-stagger entrance-animation classes are baked into
the DOM at first mount and never programmatically removed (the flag
is a correctly-scoped once-per-session const, but nothing ever
re-renders to strip the class), so every KeepAlive detach/reattach
cycle restarts the CSS animation on reactivation, replaying the full
entrance cascade on every revisit. Confirmed via a diagnostic
showing DOM card count doubling transiently on every revisit and an
extended animationstart/animationend event log. Not fixed this pass
— the safe fix's blast radius spans 5+ files outside this plan's
scope (Apps.vue, Marketplace.vue, Home.vue, several Web5 sub-cards)
and needs its own real-device verification budget, matching the
precedent 02-02's original KeepAlive rollout needed for this exact
class of change. Named and evidenced, recommended as a dedicated
follow-up rather than expanded into this plan under time pressure.
REQUIREMENTS.md's PERF-02/PERF-03 rows updated to the final state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
releases/app-catalog.json regenerated and signed by the release-root key
(did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur). botfights is
the only content change vs the previously-published catalog (verified
structurally — all other 65 apps unchanged): 1.2.7 -> 1.2.8, fixes broken
profile pictures (CSP img-src) and makes the AI-answer feature discoverable
by default.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bumps to botfights:1.2.8, which carries: the CSP img-src fix (nostr profile
pictures come from user-supplied kind:0 metadata URLs on arbitrary domains
— img-src was locked to 'self'/data:/blob: with no https:, so every
external profile picture rendered as a broken image), and discoverability
fixes for the new AI-answer feature (poll mode is now the default
connection mode, matching BOTFIGHTS.md's own documented default, and the
AI-answer section is expanded by default instead of collapsed behind an
extra click).
Canonical arena on VPS2 already rolled to 1.2.8 directly (docker compose
pull/up) ahead of this catalog publish, since it's a separate deployment
from the archy-catalog-driven per-node install path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fleet.vue's useFleetData() (60s telemetry.fleet-status/-alerts poll),
Server.vue's FipsNetworkCard.vue (15s fips.status poll), and Web5.vue's
Web5Monitoring.vue (30s system.stats poll — redundant with Home.vue's
own correctly-gated 10s poll of the same store) all armed their
setInterval in onMounted and only disarmed it in onUnmounted/
onBeforeUnmount. That was harmless before 02-04 registered their
owning views in KEEP_ALIVE_PATHS (the view was destroyed on every
tab-away, so the teardown hook fired every time); once KeepAlive keeps
the instance alive, the teardown hook never fires again and the poll
ran forever in the background regardless of which dashboard tab was
showing.
Gated arm/disarm to onActivated/onDeactivated, mirroring Server.vue's
own vpnPollInterval fix from 02-04 exactly. Added regression tests to
keepAliveLifecycle.test.ts mounting each real component under a
synthetic KeepAlive with fake timers; confirmed RED against the
pre-fix code (git stash) before confirming GREEN with the fix restored.
Full suite (95 files/788 tests), type-check and build all green.
keepAliveTabs.test.ts is byte-for-byte unmodified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
handle_lnd_createinvoice posted to LND's /v1/invoices with only value/memo,
so LND defaulted private to false and returned invoices with empty
route_hints. Any node whose only channels are private/unannounced was
unpayable through the wallet UI's Receive flow. Diagnosed on
archy-x250-mad2, whose only channel (to Olympus by ZEUS) is private with
~40.8k sats usable inbound; three wallet-UI invoices never received an
HTLC.
Audited every other invoice-creation call site and found a second one with
the identical omission: create_invoice, the seller-side/peer-file paid-
content flow (content.rs -> handler for paid downloads). Same bug, same
fix, wider blast radius than the one-node report suggested -- paid-file
sales were unreceivable on private-channel nodes too.
Extracted both call sites' invoice_body construction into one shared
build_invoice_request_body() that sets private: true unconditionally, and
added a unit test pinning that field so neither site can silently drift
back to false. private:true is harmless on nodes with public channels --
LND still prefers a direct public route and the hint is just an unused
alternate path.
CPU profile evidence (CDP Profiler + Tracing, additive
neode-ui/e2e/perf/profile-revisit.spec.ts, frozen harness untouched)
shows 86-99% of every revisit window spent in (idle)/(program) with
under 10% genuine app JS self-time on every surface — ruling out
expensive computed re-evaluation, watcher cascades, and whole-subtree
re-renders as the dominant cost, per the plan's own explicit list of
hypotheses to check before accepting one.
A follow-up source-level lifecycle audit (grep every setInterval call
site for a missing onActivated/onDeactivated pair, extending 02-04's
own audit convention past the top-level view files it originally
checked) found three child components/composables inside the
KeepAlive'd Fleet/Server/Web5 subtrees that arm a poll in onMounted
and only ever clear it in onUnmounted — harmless before phase 2
(the view was destroyed on tab-away) and now a permanent, session-long
background-RPC cost once KeepAlive keeps the parent instance alive:
useFleetData.ts (60s), FipsNetworkCard.vue (15s, Server), and
Web5Monitoring.vue (30s, Web5 — redundant with Home.vue's own,
correctly-gated 10s poll of the same store).
This directly explains the idle-dominated CPU signature (background
network/scheduling contention, not compute) and why 02-10's 5-run
remeasure regressed further than the 3-run baseline/after runs even
as disk pressure eased: a longer session accumulates more of these
always-on pollers, degrading every subsequent navigation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
releases/app-catalog.json regenerated and signed by the release-root key
(did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur). botfights is
the only content change vs the previously-published catalog (verified
structurally — all other 65 apps unchanged): 1.2.3 -> 1.2.7, carries the
nostr-provider.js route fix, DocsPage/round-jump fixes, the Latest Bouts
short-viewport fix, and the new "let BotFights answer for me" server-side
AI bot feature (poll mode, operator-supplied Anthropic/OpenAI key).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bumps to botfights:1.2.7, which carries the botfight repo's new
"let BotFights answer for me" feature (ca5b634) — an operator can paste an
Anthropic or OpenAI API key so this node's BotFights server answers fight
challenges automatically for a poll-mode bot, no external script needed.
Storage/security follows the same pattern this node already uses for its
own AIUI Anthropic key (system.settings.set "claude_api_key" in
core/archipelago/src/api/rpc/system/handlers.rs): 0600 file, never echoed
back. Also carries the nostr-provider.js 404 fix, DocsPage/round-jump
fixes, and the HomePage "Latest Bouts" short-viewport visibility fix from
the prior 1.2.4-1.2.6 iterations that were built and tested locally but
not yet pushed through the signed-catalog path.
No archy-side manifest changes beyond the version/image bump — 1.2.2's
ARCHY_EMBEDDED/ARENA_UPSTREAM_URL/generated JWT_SECRET are unchanged;
the new AI-bot feature's key storage lives entirely inside the botfights
app's own data volume, no new archy secret/env wiring needed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fleet/AppDetails/Web5/OpenWrtGateway/Server/Discover all measured
slower on revisit than the pre-phase-2 baseline (02-10's three-way
dispersion analysis). This plan profiles the real cause per surface
before touching source (D-10), fixes what's fixable without
reverting any T-02-01 persist:false decision or touching the
visual/animation contract, and re-measures with the frozen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
archy-x250-mad2 is a user's device holding real funds. Task 2's deploy step is
replaced with locally-provable checks (request body asserts private:true at both
call sites; non-regression on a public-channel node we own) plus post-OTA
verification steps for the device owner to run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diagnosed on archy-x250-mad2: handle_lnd_createinvoice omits LND's private
flag, so invoices carry route_hints: [] and are unroutable for any node whose
channels are unannounced. Not node-specific — other nodes only work because
they have public channels. Planned as 01-19 in wave 1 so it ships early.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>