docs(02-05): complete Mesh tab cache + graphics lifecycle plan
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
---
|
||||
phase: 02-ui-performance
|
||||
plan: 05
|
||||
subsystem: ui
|
||||
tags: [vue, keepalive, useCachedResource, leaflet, pinia, mesh]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 02-ui-performance/02-04
|
||||
provides: "Mesh.vue's activate/deactivate lifecycle audit (armMeshLive/teardownMeshLiveEffects, dual onMounted+onActivated registration idiom, /dashboard/mesh registered in KEEP_ALIVE_PATHS)"
|
||||
- phase: 02-ui-performance/02-02
|
||||
provides: "useCachedResource composable (TTL/persist/onActivated revalidation), RefreshIndicator.vue, the Cloud.vue reference pattern for immediate:false + manual Promise.allSettled kick"
|
||||
provides:
|
||||
- "Mesh.vue's six previously-uncached tab-entry fetch groups (mesh.refreshAll, transport.fetchStatus, refreshFederationNodes, refreshSelfOnion, refreshSelfDid, refreshContacts) each behind a keyed useCachedResource entry with an explicit TTL and persist decision"
|
||||
- "RefreshIndicator wired into the Mesh header, driven by whether any of the six groups is refreshing"
|
||||
- "MeshMap.vue's Leaflet map instance quiesced/repaired across the Mesh tab's activate/deactivate cycle (armMapVisibility/disarmMapVisibility)"
|
||||
- "A documented, tested finding that no D3 force simulation exists in Mesh.vue's tree — RESEARCH.md's premise was incorrect for this codebase"
|
||||
affects: [02-06, 02-07, 02-08]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "useCachedResource hosted at the component call site (Mesh.vue) even when it wraps a store action, when the store action has other callers needing a guaranteed-fresh (uncached) read — Pinia's defineStore(id, setup) runs in a bare effectScope, not a component instance, so the composable's internal onActivated() would silently no-op (dev warning) if called at store scope"
|
||||
- "refreshMeshGroupIfStale(res) gate — mirrors Cloud.vue's loadCounts() idiom (entry.data === null || isStale.value) for immediate:false resources force-refreshed inside a single Promise.allSettled array, keeping a multi-group fan-out concurrent instead of serialized (T-02-16)"
|
||||
- "Side-effect-only cached resources: fetchers that wrap an existing store/view function which already sets its own reactive refs as a side effect, resolving to a sentinel timestamp (Date.now()) rather than the real payload — used purely to gate/dedupe RPCs and expose loadState for a shared RefreshIndicator, not to hold data"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- neode-ui/src/views/__tests__/meshTabCache.test.ts
|
||||
- neode-ui/src/components/__tests__/meshMapLifecycle.test.ts
|
||||
modified:
|
||||
- neode-ui/src/views/Mesh.vue
|
||||
- neode-ui/src/stores/mesh.ts
|
||||
- neode-ui/src/stores/transport.ts
|
||||
- neode-ui/src/api/rpc-client.ts
|
||||
- neode-ui/src/components/MeshMap.vue
|
||||
- neode-ui/src/views/mesh/mesh-styles.css
|
||||
|
||||
key-decisions:
|
||||
- "mesh.refreshAll()/transport.fetchStatus() themselves are left untouched (still uncached, always-fresh) because they have other callers needing a guaranteed-fresh read: clearAllMesh() (must re-read post-clear state) and Web5SendReceiveModals.vue's pre-send balance/mesh-only check (money-critical, must never read from a TTL-gated cache). The useCachedResource() wrapper around each lives in Mesh.vue and calls the store's existing action as its fetcher, rather than living inside stores/mesh.ts/transport.ts."
|
||||
- "FLAGGED: RESEARCH.md's premise that Mesh.vue owns a live D3 force simulation is factually wrong for this codebase — grep for d3/forceSimulation/simulation across neode-ui/src found nothing in Mesh.vue's or MeshMap.vue's tree. The only D3 force simulation belongs to NetworkMap.vue (Federation.vue's graph, out of scope). Task 2's D3-specific truths are vacuously satisfied; only the real Leaflet map lifecycle (MeshMap.vue) was fixed."
|
||||
- "MeshMap.vue was added to files_modified beyond the plan's literal list, because the Leaflet map instance and its listeners/ResizeObserver live there, not in Mesh.vue — fixing the map's activate/deactivate correctness structurally requires editing where the instance lives (Rule 3 auto-fix, minimal/in-spirit, non-architectural)."
|
||||
- "meshMapLifecycle.test.ts is a separate file from meshTabCache.test.ts (not appended) because its vi.mock('@/stores/mesh')/vi.mock('leaflet') hoist file-wide and would clobber meshTabCache.test.ts's need for the real mesh/transport stores — same class of conflict 02-02 hit and resolved by splitting MarketplaceRefresh.test.ts out of keepAliveTabs.test.ts."
|
||||
- "Per-group TTL/persist decisions (see table below), following D-06 discretion and the T-02-01 persist prohibition."
|
||||
|
||||
patterns-established:
|
||||
- "refreshMeshGroupIfStale(res) — the generalized form of Cloud.vue's loadCounts() staleness gate, applied uniformly across a set of immediate:false resources kicked from a single Promise.allSettled array"
|
||||
- "Fetchers for side-effect-only cached resources resolve to Date.now() rather than null, so useCachedResource's entry.data !== null / isStale gating works correctly even when the real payload is stored elsewhere (in existing store refs), not in the resource's own entry.data"
|
||||
|
||||
requirements-completed: []
|
||||
requirements-note: "PERF-02 is NOT marked complete — it also spans 02-06 and 02-07 (Server/Home data layer, Chat/AIUI), which still extend the KeepAlive/cache architecture to the remaining main tabs, per the precedent set by 02-02/02-03/02-04's own summaries."
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "All six Mesh tab-entry fetch groups (mesh.refresh-all, transport.status, federation-nodes, self-onion, self-did, contacts) are cached with explicit TTLs; a revisit inside TTL issues zero RPC across all six, a cold load still fires all six concurrently, and a rejected group never blocks the other five"
|
||||
requirement: PERF-02
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "neode-ui/src/views/__tests__/meshTabCache.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "Peer/reachability/transport data (10s TTL) revalidates on a stale revisit while this node's own DID/onion (300s TTL) stays cached; every group carrying peer or self identity data is persist:false, only aggregate transport status persists; every fetcher backing the six groups passes dedup:true"
|
||||
requirement: PERF-02
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "neode-ui/src/views/__tests__/meshTabCache.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "RefreshIndicator renders in the Mesh header, visible while any of the six groups revalidates (including reachability) so a resumed tab never presents a frozen reachability state as current (T-02-13)"
|
||||
requirement: PERF-02
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "neode-ui/src/views/__tests__/meshTabCache.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "MeshMap.vue's Leaflet map is quiesced/repaired across the Mesh tab's activate/deactivate cycle: exactly one map instance is constructed across repeated visits, its size is invalidated on reactivation, and its window resize listener/ResizeObserver are removed on deactivate and re-added exactly once on activate"
|
||||
requirement: PERF-02
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "neode-ui/src/components/__tests__/meshMapLifecycle.test.ts"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D5
|
||||
description: "No visual/animation regression — full suite (752 tests incl. the structural keepAliveTabs.test.ts DOM-shape pin) stays green, type-check and build are clean, and the built bundle contains the new resource keys/strings"
|
||||
requirement: PERF-02
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "npm run test (92 files / 752 tests)"
|
||||
status: pass
|
||||
- kind: other
|
||||
ref: "npm run type-check && npm run build; grep for mesh.transport-status/mesh.refresh-all/etc in web/dist/neode-ui/assets/Mesh-*.js"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 50min
|
||||
completed: 2026-07-30
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 02 Plan 05: Mesh Tab Cache + Graphics Lifecycle Summary
|
||||
|
||||
**Mesh's six fetch groups (status/peers/messages/deadman/blockheaders, transport status, federation nodes, self onion, self DID, contacts) each behind a keyed `useCachedResource` with per-group TTL/persist decisions and a shared `RefreshIndicator`, plus the Leaflet map's listener/ResizeObserver lifecycle bounded across tab deactivation — no D3 simulation was found to exist in Mesh.vue's tree, contrary to RESEARCH.md's premise**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~50 min
|
||||
- **Completed:** 2026-07-30T20:17:44Z
|
||||
- **Tasks:** 2 (Task 1 auto/tdd, Task 2 auto/tdd)
|
||||
- **Files modified:** 8 (2 new test files, 6 modified)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- **Six fetch groups cached** behind `useCachedResource`, all hosted in `Mesh.vue` (see key-decisions for why, not in `stores/mesh.ts`/`stores/transport.ts`), each `immediate: false` and force-refreshed only when stale via `refreshMeshGroupIfStale()` inside a single `Promise.allSettled` array in `armMeshLive`:
|
||||
|
||||
| Key | Fetcher wraps | TTL | Persist | Reason |
|
||||
|---|---|---|---|---|
|
||||
| `mesh.refresh-all` | `mesh.refreshAll()` (status/peers/messages/deadman/block-headers) | 10,000ms | `false` | Peer reachability is the liveness-critical surface T-02-13 forbids freezing; carries peer DIDs/pubkeys (identity payload) |
|
||||
| `mesh.transport-status` | `transport.fetchStatus()` | 10,000ms | `true` | Live transport-availability state, but no peer identity payload — explicitly the one group the plan names as safe to persist |
|
||||
| `mesh.federation-nodes` | `refreshFederationNodes()` | 30,000ms | `false` | D-06 default (not liveness-critical); carries DID/pubkey/onion |
|
||||
| `mesh.self-onion` | `refreshSelfOnion()` | 300,000ms | `false` | This node's own onion address is effectively static; identity payload |
|
||||
| `mesh.self-did` | `refreshSelfDid()` | 300,000ms | `false` | This node's own DID is effectively static; identity payload |
|
||||
| `mesh.contacts` | `refreshContacts()` | 30,000ms | `false` | User-set aliases/contact records — identity payload per T-02-01 |
|
||||
|
||||
- `armMeshLive`'s six-way `Promise.all([...])` fan-out replaced with `Promise.allSettled(meshCachedGroups.map(refreshMeshGroupIfStale))` — proven concurrent in test (all six underlying RPCs have already fired by the very next synchronous line after mount, before any `await`), and proven non-blocking (one group's store action rejecting still lets the other five complete and the post-fan-out `.then()` — `refreshOutboxCount` + deep-link matching — still runs).
|
||||
- `dedup: true` added to every RPC call backing the six groups: `mesh.status`, `mesh.peers`, `mesh.messages`, `mesh.deadman-status`, `mesh.block-headers` (mesh.ts), `transport.status` (transport.ts), and the convenience methods `getNodeDid`, `getTorAddress`, `meshContactsList`, `federationListNodes` (rpc-client.ts).
|
||||
- `RefreshIndicator` added to the Mesh header (new `.mesh-title-row` flex wrapper around the existing `<h1>`, no change to any pre-existing selector/animation), driven by `meshRefreshIndicatorState` — `'refreshing'` whenever any of the six groups is in `loadState === 'refreshing'`.
|
||||
- **MeshMap.vue's Leaflet lifecycle**: `onMounted`'s window-resize-listener + `ResizeObserver` setup refactored into idempotent `armMapVisibility()`/`disarmMapVisibility()`, dual-registered on `onMounted`+`onActivated` (with a fresh-mount guard) and torn down on `onDeactivated`, mirroring Mesh.vue's own `armMeshLive`/`teardownMeshLiveEffects` idiom from 02-04. The Leaflet instance itself is never destroyed/recreated by this (`initMap()`'s own guard already makes construction idempotent); reactivation calls `map.invalidateSize()` via `nextTick` so a map laid out off screen re-tiles at its real size.
|
||||
- **Flagged premise mismatch, documented and tested**: RESEARCH.md's Task 2 premise ("Mesh is... a live D3 force simulation and a Leaflet map") does not hold — a full grep across `neode-ui/src` for `d3`/`forceSimulation`/`simulation` found zero hits in `Mesh.vue`'s or `MeshMap.vue`'s component tree. The only D3 force simulation in the codebase belongs to `NetworkMap.vue` (used by `Federation.vue`, a different view entirely, out of this plan's scope). Task 2's D3-specific `must_haves` truths ("the D3 force simulation stops...", "restarts only if data changed...", "cancels any pending animation-frame callback...") are therefore vacuously true (there is nothing to leak) — only the real Leaflet-map-specific truths were implemented and tested.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Cache the six Mesh fetch groups without serializing them** - `31389bcc` (feat, tdd)
|
||||
2. **Task 2: Bound the D3 simulation and Leaflet map across deactivation** - `abdfa07a` (feat, tdd — D3 portion vacuous per the flagged finding above; Leaflet portion real)
|
||||
|
||||
**Plan metadata:** (this commit)
|
||||
|
||||
_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/02-02/02-03/02-04 history)._
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `neode-ui/src/views/Mesh.vue` — six `useCachedResource` entries, `meshCachedGroups`/`refreshMeshGroupIfStale`/`meshRefreshIndicatorState`, `armMeshLive`'s fan-out converted to `Promise.allSettled`, `RefreshIndicator` added to the header
|
||||
- `neode-ui/src/stores/mesh.ts` — `dedup: true` on `fetchStatus`/`fetchPeers`/`fetchMessages`/`fetchDeadmanStatus`/`fetchBlockHeaders`'s RPC calls
|
||||
- `neode-ui/src/stores/transport.ts` — `dedup: true` on `fetchStatus`/`fetchPeers`'s RPC calls
|
||||
- `neode-ui/src/api/rpc-client.ts` — `dedup: true` on `getNodeDid`, `getTorAddress`, `meshContactsList`, `federationListNodes`
|
||||
- `neode-ui/src/components/MeshMap.vue` — `armMapVisibility()`/`disarmMapVisibility()` idempotent pair, dual-registered `onMounted`/`onActivated`, `onDeactivated` teardown, `nextTick`-scheduled `invalidateSize()` on reactivation
|
||||
- `neode-ui/src/views/mesh/mesh-styles.css` — new `.mesh-title-row` rule (flex wrapper for the title + RefreshIndicator; no existing selector touched)
|
||||
- `neode-ui/src/views/__tests__/meshTabCache.test.ts` — new; 8 tests covering Task 1's five behaviors plus dedup/persist/indicator-wiring assertions
|
||||
- `neode-ui/src/components/__tests__/meshMapLifecycle.test.ts` — new; 4 tests covering Task 2's real (Leaflet-only) behaviors
|
||||
|
||||
## Decisions Made
|
||||
|
||||
See `key-decisions` in frontmatter for the full list. Highlights:
|
||||
|
||||
- **Cache placement (mesh.refreshAll/transport.fetchStatus)**: the `useCachedResource()` call itself lives in `Mesh.vue`, not inside `stores/mesh.ts`/`stores/transport.ts`, even though it wraps those stores' own actions. Reason, verified against Vue's source (`node_modules/@vue/runtime-core/dist/runtime-core.cjs.js`'s `injectHook`): Pinia's `defineStore(id, setup)` runs its setup function inside a bare `effectScope()`, not a real component instance (`currentInstance` is `null`), so `onActivated()` called from inside a Pinia store setup is a documented Vue no-op (dev warning only, never throws) — it would compile but would never actually revalidate anything on tab reactivation. Mesh.vue is the one call site that legitimately owns the KeepAlive/component lifecycle. The fetchers still literally re-invoke `mesh.refreshAll()`/`transport.fetchStatus()` unchanged, so those store actions' other callers (`clearAllMesh()`, and `Web5SendReceiveModals.vue`'s pre-send mesh-only check, which must never read a TTL-gated cache before moving money) keep their existing guaranteed-fresh behavior untouched.
|
||||
- **MeshMap.vue added to files_modified** beyond the plan's literal list (`Mesh.vue`, `mesh.ts`, `transport.ts`, `meshTabCache.test.ts`) — the Leaflet map instance, its window listener, and its `ResizeObserver` all live in `MeshMap.vue`, a child component `<MeshMap v-if="showMapPanel">` inside Mesh.vue's template. Fixing the map's activate/deactivate correctness structurally requires editing where the instance lives; this is a minimal, in-spirit, non-architectural addition (Rule 3 auto-fix — same class of judgment call 02-02 made when it added `dashboardViewWrappers.ts` outside its own original file list).
|
||||
- **Test file split**: `meshMapLifecycle.test.ts` is a new, separate file rather than appended to `meshTabCache.test.ts`, because its `vi.mock('@/stores/mesh', ...)` (a minimal plain-object stub) and `vi.mock('leaflet', ...)` are hoisted to the top of whichever file they're declared in by vitest/esbuild, and would clobber `meshTabCache.test.ts`'s need for the **real** `mesh`/`transport`/`resources` Pinia stores (needed so the six-group cache/dedup/persist logic under test is genuinely exercised, not stubbed away). This mirrors the exact precedent 02-02 set with `MarketplaceRefresh.test.ts` for the same class of `vi.mock`-hoisting conflict.
|
||||
- **Persist/TTL table**: see Accomplishments above — every group carrying this node's own DID/onion or any peer identity payload (peers, federation nodes, contacts/aliases) is `persist: false`; only `transport.status` (an aggregate, no identity fields) persists, matching the plan's own explicit carve-out.
|
||||
- **FLAGGED (not auto-backstopped)**: the D3 force-simulation premise from RESEARCH.md is factually incorrect for the current codebase state. This was verified by grepping the entire `neode-ui/src` tree (not just `Mesh.vue`) for `d3`, `forceSimulation`, and `simulation` — the only hits belong to `src/components/federation/NetworkMap.vue`, imported exclusively by `Federation.vue`. Mesh.vue's only graphics context is the Leaflet map (`MeshMap.vue`). This is surfaced here for human review per the assumptions_and_flagged_items convention rather than silently reinterpreting the task; the real, testable Leaflet-lifecycle work (which the plan also required) was implemented and covered in full.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 3 - Blocking] MeshMap.vue added to files_modified to fix the Leaflet map's lifecycle**
|
||||
- **Found during:** Task 2, tracing where the Leaflet instance/listener/observer actually live
|
||||
- **Issue:** The plan's `files_modified` for this task only lists `Mesh.vue` and `meshTabCache.test.ts`, but the Leaflet map, its window resize listener, and its `ResizeObserver` all live in the child component `MeshMap.vue` (`<MeshMap v-if="showMapPanel">` in Mesh.vue's template) — Mesh.vue itself has no direct graphics-context code to edit.
|
||||
- **Fix:** Extended scope to `neode-ui/src/components/MeshMap.vue` (added `armMapVisibility`/`disarmMapVisibility`, dual `onMounted`/`onActivated` registration, `onDeactivated` teardown) and a new companion test file `neode-ui/src/components/__tests__/meshMapLifecycle.test.ts`.
|
||||
- **Files modified:** `neode-ui/src/components/MeshMap.vue`, `neode-ui/src/components/__tests__/meshMapLifecycle.test.ts` (new)
|
||||
- **Verification:** 4 new tests pass (one map instance across 3 cycles, invalidateSize on activate, listener add/remove counts, ResizeObserver connect/disconnect counts); full suite, type-check, build all green.
|
||||
- **Committed in:** `abdfa07a` (Task 2 commit)
|
||||
|
||||
**2. [Rule 1 - Bug in own test authoring, caught before commit] meshMapLifecycle.test.ts's listener-count test needed a mount-time baseline**
|
||||
- **Found during:** Task 2, first test run
|
||||
- **Issue:** `armMapVisibility()`'s idempotent remove-then-add idiom means `window.removeEventListener('resize', ...)` is also called once during the very first mount (not just on deactivate) — the test's naive assertion (`expect(removeSpy...).toBe(1)` after the first deactivate) was off by one.
|
||||
- **Fix:** Baseline both `addSpy`/`removeSpy` counts immediately after mount, then assert relative increments across the deactivate/activate cycle.
|
||||
- **Files modified:** `neode-ui/src/components/__tests__/meshMapLifecycle.test.ts`
|
||||
- **Verification:** Test passes; the underlying implementation was correct all along, only the test's assumption was wrong.
|
||||
- **Committed in:** `abdfa07a` (Task 2 commit)
|
||||
|
||||
**3. [Rule 1 - Bug in own test authoring, caught before commit] meshTabCache.test.ts's dedup:true assertion needed to scope to the six groups' own methods**
|
||||
- **Found during:** Task 1, first test run
|
||||
- **Issue:** `refreshOutboxCount()` (called from the fan-out's `.then()`, not one of the six cached groups) issues an `rpcClient.call({method: 'mesh.outbox'})` without `dedup: true` — a blanket "every captured rpcClient.call has dedup:true" assertion incorrectly failed on this unrelated, out-of-scope call.
|
||||
- **Fix:** Restricted the assertion to the six groups' own RPC methods (`mesh.status`, `mesh.peers`, `mesh.messages`, `mesh.deadman-status`, `mesh.block-headers`, `transport.status`).
|
||||
- **Files modified:** `neode-ui/src/views/__tests__/meshTabCache.test.ts`
|
||||
- **Verification:** Test passes; confirms all six groups' fetchers (and no others) are asserted against dedup:true.
|
||||
- **Committed in:** `31389bcc` (Task 1 commit)
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 3 (1 Rule 3 scope extension necessary to fulfill the task's literal requirement; 2 Rule 1 fixes to the test file's own assertions, caught and corrected before either commit landed — no production-code bugs found)
|
||||
**Impact on plan:** The MeshMap.vue extension is the only deviation with lasting scope impact, and it is narrowly targeted (lifecycle hooks only, no restructuring, no visual change) and fully test-covered. No scope creep beyond what Task 2's literal must_haves required.
|
||||
|
||||
## 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 through the existing store/view functions; nothing renders hardcoded empty/mock data.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None beyond what the plan's own `<threat_model>` already anticipated (T-02-01, T-02-03, T-02-13, T-02-16) — no new network endpoints, auth paths, or trust-boundary-crossing surface was introduced by this plan.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- The D3-force-simulation premise mismatch (see Decisions Made) — resolved by verifying via grep and treating the affected truths as vacuously satisfied, with the finding surfaced prominently here for human review rather than silently reinterpreting the task's scope.
|
||||
- Two test-authoring bugs in the new test files themselves (both fixed before either commit — see Deviations 2 and 3 above); no production-code bugs were found during this plan.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Mesh's six fetch groups and its Leaflet map lifecycle are now on the same cached/activate-deactivate architecture as every other main tab (02-02/02-04's tracer + lifecycle-audit foundation extended to the heaviest remaining tab).
|
||||
- The `refreshMeshGroupIfStale`/`Promise.allSettled` pattern (and the "useCachedResource must be hosted at a real component, not inside a Pinia store setup" finding) is available for 02-06 (Server/Home) if either store owns a fetch action with other callers needing a guaranteed-fresh read.
|
||||
- **For 02-08 (on-device verification)**: heap-growth across many Mesh tab cycles is a property no jsdom unit test can settle — `meshMapLifecycle.test.ts` proves exactly one Leaflet map instance is constructed across repeated activate/deactivate cycles in a synthetic harness, but real browser memory tooling on archi-dev-box is where the D-03 bounded-memory claim gets its final check, per the plan's own `verification: backstop` marker.
|
||||
- **Flag carried forward**: if a future audit finds Mesh.vue (or any other main tab) genuinely does need a D3-based visualization (e.g., if `HopVizModal.vue`'s message-hop graphic is later rebuilt with D3), re-open this finding — the current absence was verified for the codebase state as of this plan's execution, not asserted as a permanent architectural constraint.
|
||||
- No blockers for 02-06/02-07.
|
||||
|
||||
---
|
||||
*Phase: 02-ui-performance*
|
||||
*Completed: 2026-07-30*
|
||||
|
||||
## Self-Check: PASSED
|
||||
Reference in New Issue
Block a user