20 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, requirements-note, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | requirements-note | coverage | duration | completed | status | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-ui-performance | 06 | ui |
|
|
|
|
|
|
|
|
PERF-02 is NOT marked complete — 02-07 (Chat/AIUI) still extends the KeepAlive/cache architecture to the one remaining unconverted main tab, per the precedent set by 02-02/02-03/02-04/02-05's own summaries. |
|
73min | 2026-07-30 | complete |
Phase 02 Plan 06: Server and Home Tab Cache Summary
Server's seven load groups (five already on useCachedResource from a pre-phase commit, gaining their first explicit TTL/persist/dedup here; disk status newly converted) and Home's system/update/storage groups on every-entry TTL-gated cache, with the wallet composite on an unconditional-revalidate-on-entry resource that never persists to sessionStorage
Performance
- Duration: ~73 min
- Started: 2026-07-30T20:26:48Z
- Completed: 2026-07-30T21:39:25Z
- Tasks: 2 (Task 1 auto/tdd, Task 2 auto/tdd)
- Files modified: 7 (2 new test files, 5 modified)
Accomplishments
-
RESEARCH A3 settled (Server.vue): read every one of the seven loader bodies —
checkTorStatus,loadNetworkData,loadInterfaces,loadVpnPeers,loadFipsSummary,loadTorServices,loadDiskStatus. None consumes another's result or side effect;networkRes's fetcher reads only its OWN previous cached value to merge partial RPC results, which is self-referential, not a cross-resource dependency. Verdict: independent, concurrent fan-out is correct as-is — no aggregate endpoint or ordering change was needed. -
Found: 5 of Server's 7 groups were already on
useCachedResourcefrom a pre-phase legacy commit (ea254f63, predates this UI-performance milestone) —networkRes,fipsSummaryRes,vpnPeersRes,interfacesRes,torServicesRes. None had an explicitttlMs/persist(relying on the composable's 30s/persist:truedefaults), and several underlying calls lackeddedup:true.checkTorStatus()turned out to just calltorServicesRes.refresh()and derive a label — it shares that resource rather than being an eighth key. OnlyloadDiskStatus()was a genuinely uncached plain fetch, forced on every activation. -
Server.vue's per-group TTL/persist table (all explicit, none defaulted, per T-02-01):
Key TTL Persist Reason server.network-summary10,000ms trueFast tier (WiFi/VPN/DNS state); this node's own status/wgPubkey, not peer identity server.interfaces10,000ms trueFast tier; local hardware info (MAC/IP), no identity server.disk-status10,000ms trueFast tier; usage figures carry no identity/financial payload server.vpn-peers30,000ms falseNear-default; carries npub (peer Nostr identity) server.tor-services30,000ms falseNear-default; carries onion_address server.fips-summary60,000ms trueNear-static (installed/service_active/key_present rarely change; authenticated_peer_count can drift, so not fully static); key_present is a boolean flag, not the key itself -
Home.vue's cache keys (all hosted in Home.vue, not inside the
homeStatusPinia store — a store'sdefineStore(id, setup)runs in a bare effectScope whereonActivated()silently no-ops, the same finding 02-05 made for Mesh's store actions):Key Wraps TTL Persist Reason home.system-statshomeStatus.refresh(packages)(system/bitcoin/vpn/fips/tollgate, 5 RPCs)10,000ms trueMatches the pre-existing 10s poll cadence; aggregate status, no identity home.update-statuscheckUpdateStatus()300,000ms trueNear-static; an available update doesn't appear/disappear quickly home.cloud-usagefileBrowserClient.getUsage()30,000ms trueDefault tier; non-sensitive home.wallet-statusloadWeb5Status()(7-call composite: lnd.getinfo + ecash/fedimint/ark balances + 3 histories)n/a — never TTL-gated, always unconditional falseT-02-13 exception: a money figure must never be presented as current without a visible re-check -
Web5.vue key-sharing evaluated and declined (read both of its resources):
web5.networking-profitsis an unrelated dataset (routing/content-sale profit totals).web5.lnd-infocovers only the singlelnd.getinfocall and — the reason sharing was declined — is declared with the composable's implicit defaultpersist:true; Web5.vue is outside this plan's file scope to fix, so sharing that key would either corrupt its differently-shapedentry.data(a sentinel timestamp here vs. its typed balance object there) or silently fail to close the sessionStorage gap this task exists to close, since Web5.vue's own hook instance would keep persisting on its own independent 30s-interval refresh regardless of what Home declares. Home's wallet fetch is also a strictly broader 7-call composite, not the same single-call dataset. -
dedup:trueadded to every underlying RPC call touched by this plan: Server'snetwork.diagnostics/router.list-forwards(inline innetworkRes's fetcher) plusvpnStatus()/dnsStatus()/diskStatus()(parameterless convenience methods inrpc-client.ts, givendedup:truedirectly in their bodies, matching the existinggetNodeDid()/meshContactsList()precedent); Home's 7-call wallet composite andcheckUpdateStatus()'s two calls;homeStatus.ts'ssystem.stats/bitcoin.getinfo/fips.status/openwrt.get-status(itsvpn.statuscall already picked updedup:truefor free via thevpnStatus()change). -
RefreshIndicatorwired into both headers: Server gained a new minimal top-of-page row (it previously had no visible page header at all — itsQuickActionsCardheader isv-if="false"); Home's got added inline next to the existing typed-welcome<h1>, both driven by aloadState-derived computed (serverRefreshIndicatorState= any of six groups refreshing;homeRefreshIndicatorState= the wallet resource's ownloadState, directly — the wallet is the one group whose refresh visibility matters most, per T-02-13). -
Server's
loadDiskStatus()converted to the seventhuseCachedResourceentry (server.disk-status);armServerEntryEffects()no longer force-calls it on every activation — the resource's own internalonActivated(added in 02-02) now self-heals it staleness-gated, exactly like the other six. -
Home's
armLiveDataPolling()restructured aroundhomeCachedGroups/refreshHomeGroupIfStale(the 02-05refreshMeshGroupIfStalepattern), kicked via a singlePromise.allSettled— the plan's required move offileBrowserClient.getUsage()'s trailingawaitinto the same concurrent fan-out as the other loaders.
Task Commits
Each task was committed atomically:
- Task 1: Cache the Server tab's seven load groups -
e6ed5536(feat, tdd) - Task 2: Cache the Home tab and guarantee wallet freshness on re-entry -
926fa606(feat, tdd)
Plan metadata: (this commit) - docs(02-06): complete Server and Home tab cache plan
Note: both tasks are TDD tasks; tests were written and made to pass within each task's own commit, per this repo's established single-commit-per-task convention (see 02-01 through 02-05 history).
Files Created/Modified
neode-ui/src/views/Server.vue— explicit TTL/persist added to the five pre-existing cached resources;loadDiskStatusconverted to a sixth-and-seventh (server.disk-status) cached resource;dedup:true/signal threaded throughnetworkRes's two inline calls;RefreshIndicatoradded to a new minimal header row;armServerEntryEffects/onMountedcomments updated to reflect the settled A3 verdictneode-ui/src/views/Home.vue—systemStatsRes/updateStatusRes/cloudUsageRes/walletStatusResadded;armLiveDataPollingrestructured aroundhomeCachedGroups/refreshHomeGroupIfStale+ unconditional wallet refresh;onMountedno longerasync/no trailingawait;dedup:trueadded to the wallet composite andcheckUpdateStatus's calls;RefreshIndicatoradded next to the header<h1>; newdefineExposeblock (loadWeb5Status,homeRefreshIndicatorState) for test accessneode-ui/src/stores/homeStatus.ts—dedup:trueadded tosystem.stats/bitcoin.getinfo/fips.status/openwrt.get-statusneode-ui/src/api/rpc-client.ts—dedup:trueadded insidevpnStatus()/dnsStatus()/diskStatus()neode-ui/src/views/__tests__/serverTabCache.test.ts— new; 7 tests covering Task 1's five required behaviors plus persist/dedup/indicator-wiring assertionsneode-ui/src/views/__tests__/homeTabCache.test.ts— new; 8 tests covering Task 2's six required behaviors plus dedup/websocket-path assertionsneode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts— one assertion fixed (see Deviations): the pre-existing Server.vuevpnPollIntervaltest's reactivation count was invalidated byserver.network-summary's new explicit 10s TTL
Decisions Made
See key-decisions in frontmatter for the full list. Highlights:
- RESEARCH A3 settled (independent, no ordering dependency) — see Accomplishments.
- Five of Server's seven groups were already converted by a pre-phase legacy commit; this task's real work was explicit TTL/persist/dedup, not the initial
useCachedResourcewiring. - Web5.vue key-sharing evaluated and declined for a documented, safety-driven reason (would either corrupt Web5's typed entry or fail to close the sessionStorage gap).
homeStatus.refresh()wrapped at the view (Home.vue), not inside the store, per the 02-05 Pinia-effectScope finding.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug, caught by the existing test suite] keepAliveLifecycle.test.ts's Server.vue vpnPollInterval reactivation count invalidated by the new explicit TTL
- Found during: Task 1, full-suite run after adding
networkRes's explicit 10s TTL - Issue: The pre-existing test deactivated Server for 20s (chosen, per its own comment, to be "comfortably under networkRes's [then-default] 30s TTL"). Once
server.network-summarygot an explicit 10s TTL, that 20s deactivation window now makesnetworkResitself stale, so reactivation triggers a SECONDvpnStatus()call (vianetworkRes's ownonActivatedrevalidation) in addition toarmVpnPoll's immediate tick — the test's+1assertion needed to become+2. - Fix: Updated the assertion and its comment to explain both contributing calls.
- Files modified:
neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts - Verification: Full suite green (94 files / 767 tests) after the fix.
- Committed in:
e6ed5536(Task 1 commit)
Total deviations: 1 auto-fixed (Rule 1 — a test assertion directly invalidated by this task's own required TTL change) Impact on plan: Necessary correction within a file this task's change directly affected. No scope creep — the fix only updates an assertion and its explanatory comment.
Known Stubs
None — no stub data, placeholder text, or unwired data sources were introduced. Every cached group's fetcher performs a real RPC round-trip (or wraps an existing store/view function that does).
Threat Flags
None beyond what the plan's own <threat_model> already anticipated (T-02-01, T-02-13, T-02-16, T-02-17) — no new network endpoints, auth paths, or trust-boundary-crossing surface was introduced by this plan.
Issues Encountered
- The
keepAliveLifecycle.test.tsassertion invalidation above — resolved by updating the test to match the new, plan-mandated TTL behavior; no production-code bug. flushPromises()(a single macrotask boundary) needed to be called twice inhomeTabCache.test.tsto fully settleloadWeb5Status()'s deeply nestedPromise.allSettledchains under fake timers before the wallet resource'sloadStatereliably reached'ready'— a test-authoring detail (settle()helper), not a production bug.- A leaking permanent
mockImplementation()override (used by one test to simulate a never-resolving RPC) was caught before commit —beforeEachnow unconditionally restores the default mock implementation alongsidemockClear().
User Setup Required
None - no external service configuration required.
Next Phase Readiness
- Every main tab in
KEEP_ALIVE_PATHSexcept Chat/AIUI now has its data layer onuseCachedResourcewith explicit TTL/persist decisions (Marketplace/Discover 02-02, secondary screens 02-03, Mesh 02-05, Server/Home this plan) — 02-07 (Chat/AIUI) is the one remaining conversion. - The "host
useCachedResourceat the component, not inside a Pinia store setup" finding (02-05, reconfirmed here forhomeStatus.ts) is now established across two independent stores — a strong signal for 02-08's on-device pass to treat as settled architecture, not a per-case judgment call. - No blockers for 02-07/02-08.
Phase: 02-ui-performance Completed: 2026-07-30