313 lines
22 KiB
Markdown
313 lines
22 KiB
Markdown
---
|
|||
|
|
phase: 02-ui-performance
|
||
|
|
plan: 06
|
||
|
|
type: execute
|
||
|
|
wave: 4
|
||
|
|
depends_on: ["02-04"]
|
||
|
|
files_modified:
|
||
|
|
- neode-ui/src/views/Server.vue
|
||
|
|
- neode-ui/src/views/Home.vue
|
||
|
|
- neode-ui/src/views/__tests__/serverTabCache.test.ts
|
||
|
|
- neode-ui/src/views/__tests__/homeTabCache.test.ts
|
||
|
|
autonomous: true
|
||
|
|
requirements: [PERF-02]
|
||
|
|
|
||
|
|
must_haves:
|
||
|
|
truths:
|
||
|
|
- "Returning to the Server tab within the TTL issues no RPC for any of its seven load groups"
|
||
|
|
- "Returning to the Home tab within the TTL issues no RPC for its system, update, wallet or storage-usage groups"
|
||
|
|
- "A stale return to either tab keeps the previous content on screen while exactly one background revalidation runs per stale group, with a visible refresh indicator"
|
||
|
|
- "The Server tab's seven loads still run concurrently — the conversion does not turn a parallel fan-out into a chain"
|
||
|
|
- "Any Server load that genuinely consumes another load's result remains ordered, and the dependency is recorded rather than assumed away"
|
||
|
|
- "The wallet figures on Home are revalidated on tab re-entry, so a resumed tab never presents a paused-poll balance as current"
|
||
|
|
- "Wallet balances, transaction history and identity material from either tab are held in memory only and never written to sessionStorage (D-08)"
|
||
|
|
- statement: "First entry to either tab in a fresh session may still show a loading state; only revisits are required to be instant"
|
||
|
|
verification: backstop
|
||
|
|
prohibitions:
|
||
|
|
- "MUST NOT present cached data as live — a money- or liveness-critical surface (wallet balance, incoming payment, mesh peer reachability, app install or health state) must never render from cache without a visible refresh signal and an in-flight revalidation"
|
||
|
|
- "MUST NOT persist wallet balances, transaction history, credentials, DIDs, seed or identity material, or peer identity payloads to sessionStorage"
|
||
|
|
- "MUST NOT achieve perceived speed by removing behavior or hiding state — no suppressing the refresh indicator, no dropping a fetch the surface needs, no disabling a feature to win the metric"
|
||
|
|
artifacts:
|
||
|
|
- path: "neode-ui/src/views/__tests__/serverTabCache.test.ts"
|
||
|
|
provides: "Per-group call-count assertions and a concurrency assertion for the seven Server loads"
|
||
|
|
- path: "neode-ui/src/views/__tests__/homeTabCache.test.ts"
|
||
|
|
provides: "Call-count assertions plus the wallet-freshness-on-re-entry assertion"
|
||
|
|
key_links:
|
||
|
|
- from: "neode-ui/src/views/Server.vue"
|
||
|
|
to: "neode-ui/src/composables/useCachedResource.ts"
|
||
|
|
via: "each of the seven load groups becomes a keyed cached resource"
|
||
|
|
pattern: "useCachedResource"
|
||
|
|
- from: "neode-ui/src/views/Home.vue"
|
||
|
|
to: "neode-ui/src/composables/useCachedResource.ts"
|
||
|
|
via: "system stats, update status, wallet status and storage usage become keyed cached resources"
|
||
|
|
pattern: "useCachedResource"
|
||
|
|
- from: "neode-ui/src/views/Home.vue"
|
||
|
|
to: "neode-ui/src/components/RefreshIndicator.vue"
|
||
|
|
via: "the wallet card's refresh state drives the indicator so a resumed balance is never shown as settled"
|
||
|
|
pattern: "RefreshIndicator"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
Cache the two remaining uncached-fetch main tabs: Server, whose seven independent loads
|
||
|
|
re-run in full on every tab entry, and Home, whose system, update, wallet and storage
|
||
|
|
figures do the same on top of two polling intervals.
|
||
|
|
|
||
|
|
Purpose: PERF-02. RESEARCH.md classifies both as uncached fetch rather than as waterfalls
|
||
|
|
— their calls are already concurrent — so the work here is caching, not reordering. Home
|
||
|
|
carries the phase's sharpest liveness constraint: a wallet balance is the one figure where
|
||
|
|
"instant from cache" must never mean "quietly out of date".
|
||
|
|
|
||
|
|
Output: both tabs' fetches on keyed cached resources with deliberate TTLs and persist
|
||
|
|
choices, still concurrent on cold load, with wallet freshness guaranteed on re-entry.
|
||
|
|
</objective>
|
||
|
|
|
||
|
|
<execution_context>
|
||
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
||
|
|
</execution_context>
|
||
|
|
|
||
|
|
<context>
|
||
|
|
@.planning/PROJECT.md
|
||
|
|
@.planning/ROADMAP.md
|
||
|
|
@.planning/STATE.md
|
||
|
|
@.planning/phases/02-ui-performance/02-CONTEXT.md
|
||
|
|
@.planning/phases/02-ui-performance/02-RESEARCH.md
|
||
|
|
@.planning/phases/02-ui-performance/02-PATTERNS.md
|
||
|
|
@.planning/phases/02-ui-performance/02-FINDINGS.md
|
||
|
|
@.planning/phases/02-ui-performance/02-02-SUMMARY.md
|
||
|
|
@.planning/phases/02-ui-performance/02-04-SUMMARY.md
|
||
|
|
@.planning/codebase/CONVENTIONS.md
|
||
|
|
@CLAUDE.md
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto" tdd="true">
|
||
|
|
<name>Task 1: Cache the Server tab's seven load groups</name>
|
||
|
|
<files>neode-ui/src/views/Server.vue, neode-ui/src/views/__tests__/serverTabCache.test.ts</files>
|
||
|
|
<read_first>
|
||
|
|
- `neode-ui/src/views/Server.vue` — 889 lines; grep for `onMounted`, `onActivated`, `useCachedResource`, `checkTorStatus`, `loadNetworkData`, `loadInterfaces`, `loadDiskStatus`, `loadTorServices`, `loadVpnPeers` and `loadFipsSummary` first, then read the `onMounted` block at line ~831 and each loader body. Do not read the file whole.
|
||
|
|
- `neode-ui/src/views/__tests__/ServerNetworkRefresh.test.ts` — an existing test for this view; follow its mocking setup and extend rather than duplicate its conventions.
|
||
|
|
- `neode-ui/src/views/Cloud.vue` lines 505-530 and 945-970 — the resource-definition and keep-last-value reference.
|
||
|
|
- `neode-ui/src/composables/useCachedResource.ts` — the options contract, including the `onActivated` revalidation added by plan 02-02.
|
||
|
|
- `neode-ui/src/api/rpc-client.ts` — the `dedup: true` option.
|
||
|
|
- `.planning/phases/02-ui-performance/02-04-SUMMARY.md` — the side-effect bucket table for `Server.vue`.
|
||
|
|
- `.planning/phases/02-ui-performance/02-FINDINGS.md` — Server's measured revisit RPC count and primary cause.
|
||
|
|
- `.planning/phases/02-ui-performance/02-RESEARCH.md` assumption A3 — flags that the seven loads are *assumed* independent and that the assumption is unverified.
|
||
|
|
</read_first>
|
||
|
|
<behavior>
|
||
|
|
- Mounting Server, deactivating, and reactivating inside the TTL issues zero new RPCs across all seven groups
|
||
|
|
- Reactivating after the TTL issues exactly one revalidation per stale group with previous content still rendered
|
||
|
|
- A cold load issues the independent groups concurrently — their recorded start times overlap
|
||
|
|
- A rejected refresh in one group leaves the other six unaffected and keeps that group's last known data rendered
|
||
|
|
- Disk and network state carry a shorter TTL than FIPS summary state, so fast-moving figures do not sit stale
|
||
|
|
</behavior>
|
||
|
|
<action>
|
||
|
|
Convert each of the seven loads to a keyed `useCachedResource` entry following the
|
||
|
|
`Cloud.vue` pattern, with `computed` views over `entry.data` and `entry.loadState`
|
||
|
|
and keep-last-value error handling that sets the view's existing error ref rather
|
||
|
|
than raising a toast (D-07). Some sub-cards in this view already use the hook —
|
||
|
|
reuse their keys rather than introducing a second entry for the same dataset, and
|
||
|
|
say in the SUMMARY which ones you found.
|
||
|
|
|
||
|
|
Before parallelizing or caching anything, settle RESEARCH.md assumption A3: the seven
|
||
|
|
loads are *assumed* independent, and that assumption is explicitly flagged as
|
||
|
|
unverified. Read each loader body and confirm none of the seven consumes another's
|
||
|
|
result or side effect. Record the verdict per loader in the SUMMARY. If any pair does
|
||
|
|
have a real dependency, keep that pair ordered and cache them individually — do not
|
||
|
|
flatten a genuine dependency into a concurrent group to make a number look better.
|
||
|
|
|
||
|
|
Set `ttlMs` explicitly per group using the D-06 discretion: disk status, network data
|
||
|
|
and interface state move fast enough for a short TTL of around 10000 ms; Tor status,
|
||
|
|
Tor services and VPN peers sit near the 30000 ms default; the FIPS summary is
|
||
|
|
near-static and warrants a longer value. Give the reason per group in the SUMMARY.
|
||
|
|
|
||
|
|
Set `persist` explicitly per group. Anything carrying VPN peer identity, Tor onion
|
||
|
|
addresses or key material is memory-only (`persist: false`). Non-identity system
|
||
|
|
figures may persist.
|
||
|
|
|
||
|
|
Pass `dedup: true` on every underlying `rpcClient.call`.
|
||
|
|
|
||
|
|
Keep the fan-out concurrent, per D-13. `Server.vue`'s `onMounted` today issues all
|
||
|
|
seven without awaiting them in sequence, which is already correct; the conversion must
|
||
|
|
preserve that shape. Where a resource needs an explicit kick, use `immediate: false`
|
||
|
|
and call `refresh()` inside a single `Promise.allSettled` array. D-13 reserves an
|
||
|
|
aggregate endpoint for a screen needing three or more genuinely dependent calls — if
|
||
|
|
the A3 verdict turns up such a chain here, D-12 bounds the response to an additive new
|
||
|
|
handler with no refactor of existing handlers and nothing touching the orchestrator,
|
||
|
|
and the task stops for a checkpoint before any `core/` change since no backend work is
|
||
|
|
otherwise in this plan's scope.
|
||
|
|
|
||
|
|
Wire `RefreshIndicator` into the Server header, driven by whether any group is
|
||
|
|
`refreshing` — the subtle in-header signal D-05 specifies, not a stale-age badge.
|
||
|
|
|
||
|
|
Create `neode-ui/src/views/__tests__/serverTabCache.test.ts` covering the five
|
||
|
|
behaviors above with `vi.fn()` fetchers, `<KeepAlive>` mounting, fake timers and
|
||
|
|
recorded invocation timestamps for the concurrency assertion.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd neode-ui && npm run test -- src/views/__tests__/serverTabCache.test.ts src/views/__tests__/ServerNetworkRefresh.test.ts && npm run test && npm run type-check</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- `neode-ui/src/views/Server.vue` imports `useCachedResource` and every one of the seven loads resolves through a cached entry
|
||
|
|
- `npm run test -- src/views/__tests__/serverTabCache.test.ts` exits 0 with all five behaviors covered
|
||
|
|
- The reactivate-inside-TTL test asserts zero additional fetcher calls across all seven groups
|
||
|
|
- The concurrency test asserts the independent groups' cold-load starts overlap
|
||
|
|
- The pre-existing `ServerNetworkRefresh.test.ts` still passes unmodified in intent
|
||
|
|
- Groups carrying VPN peer identity, onion addresses or key material are declared `persist: false`
|
||
|
|
- Every fetcher passes `dedup: true`
|
||
|
|
- `neode-ui/src/views/Server.vue` renders `RefreshIndicator`
|
||
|
|
- `npm run test` exits 0 and `npm run type-check` exits 0
|
||
|
|
- The SUMMARY records the A3 independence verdict per loader, plus each group's TTL, persist choice and reason
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>All seven Server loads are cached with verified independence, deliberate TTLs and persist choices; a revisit inside the TTL issues no RPC; the cold-load fan-out is still concurrent.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="auto" tdd="true">
|
||
|
|
<name>Task 2: Cache the Home tab and guarantee wallet freshness on re-entry</name>
|
||
|
|
<files>neode-ui/src/views/Home.vue, neode-ui/src/views/__tests__/homeTabCache.test.ts</files>
|
||
|
|
<read_first>
|
||
|
|
- `neode-ui/src/views/Home.vue` lines 293 and 524-560 — the `onMounted` block calls `hydrateWalletSnapshot()`, `loadSystemStats()`, `checkUpdateStatus()`, `loadWeb5Status()` and `await fileBrowserClient.getUsage()`, and arms `systemStatsInterval` (10s), `walletRefreshInterval` (30s), a `wsClient.subscribe` and a `wsWalletDebounce`. Read each loader body too.
|
||
|
|
- `.planning/phases/02-ui-performance/02-04-SUMMARY.md` — the bucket table for `Home.vue`; plan 02-04 already moved the intervals and the websocket subscription onto activate/deactivate and required an immediate loader call on re-entry. This task must build on that placement, not undo it.
|
||
|
|
- `neode-ui/src/views/web5/Web5.vue` lines 140 and 293 — two existing `useCachedResource` definitions in this codebase covering wallet-adjacent data; check whether Home can share a key with them rather than creating a parallel entry for the same dataset.
|
||
|
|
- `neode-ui/src/components/RefreshIndicator.vue` — as created by plan 02-02, and its `state` prop typing.
|
||
|
|
- `neode-ui/src/composables/useCachedResource.ts` and `neode-ui/src/stores/resources.ts` — the `persist` option and the sessionStorage snapshot path it controls.
|
||
|
|
- `.planning/phases/02-ui-performance/02-FINDINGS.md` — Home's measured revisit RPC count and primary cause.
|
||
|
|
</read_first>
|
||
|
|
<behavior>
|
||
|
|
- Mounting Home, deactivating, and reactivating inside the TTL issues zero new RPCs for the system, update and storage-usage groups
|
||
|
|
- Reactivating always triggers a wallet revalidation regardless of TTL, and the refresh indicator is visible while it is in flight
|
||
|
|
- The wallet figure previously on screen stays rendered throughout that revalidation — the card never blanks or falls back to a skeleton
|
||
|
|
- A rejected wallet refresh leaves the last known figure rendered and raises no toast
|
||
|
|
- No wallet balance, transaction record or identity value is written to sessionStorage by any Home resource
|
||
|
|
- The existing wallet snapshot hydration still paints last-known figures before any network round-trip
|
||
|
|
</behavior>
|
||
|
|
<action>
|
||
|
|
Convert Home's fetches to keyed cached resources: system stats, update status, wallet
|
||
|
|
or Web5 status, and cloud storage usage. Where `Web5.vue` already defines a resource
|
||
|
|
for the same dataset, share its key rather than creating a second entry for the same
|
||
|
|
data.
|
||
|
|
|
||
|
|
Set `ttlMs` explicitly per group: system stats are fast-moving and should carry a
|
||
|
|
short TTL matching the existing 10s poll cadence; update status is near-static and
|
||
|
|
warrants a long value; storage usage sits near the default.
|
||
|
|
|
||
|
|
Wallet is the exception this task exists for, and it does not get a normal TTL-gated
|
||
|
|
treatment. A balance is a money figure: showing yesterday's number with no visible
|
||
|
|
signal that it is being re-checked is the failure this plan's first prohibition
|
||
|
|
forbids. So on tab re-entry the wallet resource revalidates unconditionally rather
|
||
|
|
than only when its TTL has lapsed, the previously known figure stays rendered
|
||
|
|
throughout, and `RefreshIndicator` is bound to that resource's `loadState` so the
|
||
|
|
re-check is visible. Plan 02-04 already placed an immediate loader call in
|
||
|
|
`onActivated`; wire the cached resource so that call is what revalidates it, rather
|
||
|
|
than adding a second independent call path.
|
||
|
|
|
||
|
|
Declare `persist: false` for the wallet or Web5 status resource and for anything else
|
||
|
|
carrying balances, transaction history, DIDs or identity material. The existing
|
||
|
|
`hydrateWalletSnapshot()` mechanism stays exactly as it is — it is the view's own
|
||
|
|
deliberate last-known-figures path and is not being replaced by the resource cache.
|
||
|
|
Storage usage and system stats are non-sensitive and may persist.
|
||
|
|
|
||
|
|
Pass `dedup: true` on every underlying call. Keep the existing concurrency: today's
|
||
|
|
`onMounted` fires the loaders without awaiting them in sequence except for the
|
||
|
|
`fileBrowserClient.getUsage()` await; move that into the same `Promise.allSettled`
|
||
|
|
group rather than leaving it as a trailing await.
|
||
|
|
|
||
|
|
Leave the websocket-driven wallet refresh from plan 02-04 in place. It is what makes a
|
||
|
|
zero-confirmation incoming transaction appear in seconds, and removing or debouncing
|
||
|
|
it harder to reduce request counts would be exactly the metric-gaming this plan's
|
||
|
|
third prohibition forbids.
|
||
|
|
|
||
|
|
Create `neode-ui/src/views/__tests__/homeTabCache.test.ts` covering the six behaviors
|
||
|
|
above. The sessionStorage assertion should seed the store, mount, deactivate and
|
||
|
|
reactivate, then assert no `resource:` key exists for the wallet entry.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd neode-ui && npm run test -- src/views/__tests__/homeTabCache.test.ts && npm run test && npm run type-check && npm run build</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- `neode-ui/src/views/Home.vue` imports `useCachedResource` and renders `RefreshIndicator` bound to the wallet resource's `loadState`
|
||
|
|
- `npm run test -- src/views/__tests__/homeTabCache.test.ts` exits 0 with all six behaviors covered
|
||
|
|
- A test asserts reactivation triggers a wallet revalidation even when the TTL has not lapsed
|
||
|
|
- A test asserts the previously rendered wallet figure is still in the DOM during that revalidation
|
||
|
|
- A test asserts no sessionStorage key exists for the wallet resource after a mount and reactivation cycle
|
||
|
|
- The wallet or Web5 status resource is declared `persist: false`
|
||
|
|
- `hydrateWalletSnapshot` is still called from `onMounted` and still paints before any network call
|
||
|
|
- The `wsClient.subscribe` wallet-push path from plan 02-04 is still present and still triggers a wallet refresh
|
||
|
|
- `npm run test` exits 0, `npm run type-check` exits 0, `npm run build` exits 0
|
||
|
|
- The SUMMARY records each Home key with its TTL, persist choice and reason, and states which keys are shared with `Web5.vue`
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Home's system, update and storage figures come from cache on revisit, the wallet always re-checks visibly on re-entry without blanking or persisting, and the real-time wallet push path is intact.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<threat_model>
|
||
|
|
## Trust Boundaries
|
||
|
|
|
||
|
|
| Boundary | Description |
|
||
|
|
|----------|-------------|
|
||
|
|
| node RPC responses → browser cache | System, network, Tor, VPN and wallet payloads now live longer in memory and possibly in sessionStorage |
|
||
|
|
| authenticated session → sessionStorage | Anything persisted is readable by any script on the origin and survives reload |
|
||
|
|
| cached wallet figure → user's financial decision | A balance shown from cache can drive a send decision |
|
||
|
|
|
||
|
|
## STRIDE Threat Register
|
||
|
|
|
||
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
||
|
|
| T-02-01 | Information Disclosure | Wallet balances, transaction history, VPN peer identity and Tor onion addresses written to sessionStorage by the default `persist: true` | high | mitigate | Both tasks require explicit per-resource `persist` decisions; every identity-bearing or financial group is `persist: false`, asserted by a test in Task 2 |
|
||
|
|
| T-02-13 | Spoofing | A cached wallet balance rendered as current after a paused poll | high | mitigate | Task 2 revalidates the wallet unconditionally on re-entry rather than on TTL lapse, keeps the prior figure rendered, and binds `RefreshIndicator` to the in-flight state so the re-check is visible |
|
||
|
|
| T-02-17 | Tampering | Flattening a genuine load-order dependency among the seven Server loads to reduce measured latency | medium | mitigate | Task 1 requires RESEARCH assumption A3 to be settled by reading each loader body, with the verdict recorded per loader and any real dependency kept ordered |
|
||
|
|
| T-02-16 | Denial of Service | Converting an already-concurrent fan-out into a serial chain of awaited refreshes | medium | mitigate | Both tasks forbid per-group awaiting, require `immediate: false` plus a single `Promise.allSettled`, and assert overlapping start times |
|
||
|
|
| T-02-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package-manager installs are in scope. A task that finds it needs a new dependency stops and routes through the Package Legitimacy Gate with a blocking human checkpoint before installing |
|
||
|
|
</threat_model>
|
||
|
|
|
||
|
|
<artifacts_this_phase_produces>
|
||
|
|
## Artifacts this phase produces
|
||
|
|
|
||
|
|
Created or changed by this plan — new API, not drift:
|
||
|
|
|
||
|
|
- `neode-ui/src/views/__tests__/serverTabCache.test.ts`
|
||
|
|
- `neode-ui/src/views/__tests__/homeTabCache.test.ts`
|
||
|
|
- Server cache keys for Tor status, network data, interfaces, disk status, Tor services, VPN peers and the FIPS summary
|
||
|
|
- Home cache keys for system stats, update status, wallet/Web5 status and cloud storage usage (some shared with `Web5.vue`)
|
||
|
|
|
||
|
|
Created elsewhere in Phase 02: `shouldKeepAlive()`, `KEEP_ALIVE_PATHS`, `KEEP_ALIVE_MAX`,
|
||
|
|
`DashboardRouterView.vue`, `RefreshIndicator.vue`, `resources.clearAll()`,
|
||
|
|
`useCachedResource.test.ts`, `keepAliveTabs.test.ts`, `keepAliveLifecycle.test.ts`,
|
||
|
|
`meshTabCache.test.ts`, `secondaryScreenCache.test.ts`, `resourcesClear.test.ts`,
|
||
|
|
`e2e/perf/{surfaces,measure,surface-perf.spec}.ts`,
|
||
|
|
`.planning/phases/02-ui-performance/{02-FINDINGS.md,02-PERF-BASELINE.json,02-PERF-AFTER.json}`.
|
||
|
|
</artifacts_this_phase_produces>
|
||
|
|
|
||
|
|
<assumptions_and_flagged_items>
|
||
|
|
## Assumptions & Flagged Items
|
||
|
|
|
||
|
|
- **PERF-02 edge-probe row (spec-less fallback):** returned `unclassified` / `unresolved`. FLAGGED, not auto-backstopped and not dropped; surfaced here for human review. Resolved in substance by this plan's `must_haves.truths`, with the first-entry allowance carried as a `verification: backstop` marker because CONTEXT.md D-11 states it as an allowance rather than as an assertable check.
|
||
|
|
- **RESEARCH assumption A3 (carried, unresolved at plan time):** the seven `Server.vue` loads are assumed independent with no ordering dependency. This planner did not verify it either. Task 1 makes settling it a precondition of the conversion and requires a per-loader verdict in the SUMMARY — the assumption is not permitted to pass through silently.
|
||
|
|
- **Open:** whether Home can share wallet-adjacent cache keys with `Web5.vue`'s two existing resources is decided during Task 2 by reading both, and recorded in the SUMMARY. Two entries for one dataset would double the request count this plan is reducing.
|
||
|
|
- **Note:** the unconditional wallet revalidation on re-entry is a deliberate departure from the TTL-gated default. It costs one request per tab entry and buys the guarantee that a money figure is never presented as settled when it is merely cached.
|
||
|
|
</assumptions_and_flagged_items>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
- `cd neode-ui && npm run test` exits 0
|
||
|
|
- `cd neode-ui && npm run type-check` exits 0
|
||
|
|
- `cd neode-ui && npm run build` exits 0
|
||
|
|
- Server and Home revisit RPC counts are zero inside the TTL for their TTL-gated groups, asserted in their test files
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- Revisits to Server and Home paint from cache with no RPC for TTL-gated groups
|
||
|
|
- The wallet always re-checks visibly on re-entry while keeping its previous figure on screen
|
||
|
|
- No financial or identity payload from either tab is written to sessionStorage
|
||
|
|
- Both cold-load fan-outs remain concurrent, and any genuine ordering dependency among the Server loads is preserved and documented
|
||
|
|
- The real-time wallet push path is unchanged
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
Create `.planning/phases/02-ui-performance/02-06-SUMMARY.md` when done. It MUST record:
|
||
|
|
the RESEARCH A3 independence verdict per Server loader; every cache key introduced with
|
||
|
|
its TTL, persist choice and reason; which Home keys are shared with `Web5.vue`; and
|
||
|
|
confirmation that the websocket wallet-push path is intact.
|
||
|
|
</output>
|