docs(02-05): complete Mesh tab cache + graphics lifecycle plan
This commit is contained in:
parent
abdfa07a77
commit
8563cf4a84
@ -73,7 +73,7 @@ Plans:
|
||||
3. Secondary screens open without a blocking full reload; repeat visits are instant
|
||||
4. The fixes are verified on real node hardware (not just the dev box) — the sluggishness the user reported is gone on-device
|
||||
|
||||
**Plans**: 4/8 plans executed
|
||||
**Plans**: 5/8 plans executed
|
||||
|
||||
Plans:
|
||||
**Wave 1**
|
||||
@ -91,7 +91,7 @@ Plans:
|
||||
|
||||
**Wave 4** *(blocked on Wave 3 completion)*
|
||||
|
||||
- [ ] 02-05-PLAN.md — Mesh: cache the six fetch groups, bound the D3 graph and Leaflet map (PERF-02)
|
||||
- [x] 02-05-PLAN.md — Mesh: cache the six fetch groups, bound the D3 graph and Leaflet map (PERF-02)
|
||||
- [ ] 02-06-PLAN.md — Server and Home: cache the uncached fan-out, guarantee wallet freshness (PERF-02)
|
||||
- [ ] 02-07-PLAN.md — Chat/AIUI: stable embed URL + the two D-14 UX defaults (PERF-02)
|
||||
|
||||
@ -196,7 +196,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Federation & Mesh Hardening | 0/10 | Planned | - |
|
||||
| 2. UI Performance | 4/8 | In Progress| |
|
||||
| 2. UI Performance | 5/8 | In Progress| |
|
||||
| 3. Multinode Verification Pass | 0/TBD | Not started | - |
|
||||
| 4. Lifecycle Perfection & Quadlet Default | 0/TBD | Not started | - |
|
||||
| 5. Registry-Distributed Manifests | 0/TBD | Not started | - |
|
||||
|
||||
@ -5,15 +5,15 @@ milestone_name: milestone
|
||||
current_phase: 02
|
||||
current_phase_name: ui-performance
|
||||
status: executing
|
||||
stopped_at: Completed 02-04-PLAN.md
|
||||
last_updated: "2026-07-30T19:29:23.802Z"
|
||||
stopped_at: Completed 02-05-PLAN.md
|
||||
last_updated: "2026-07-30T20:24:44.908Z"
|
||||
last_activity: 2026-07-30
|
||||
last_activity_desc: Phase 02 execution started
|
||||
progress:
|
||||
total_phases: 8
|
||||
completed_phases: 0
|
||||
total_plans: 18
|
||||
completed_plans: 4
|
||||
completed_plans: 5
|
||||
percent: 0
|
||||
---
|
||||
|
||||
@ -29,11 +29,11 @@ See: .planning/PROJECT.md (updated 2026-07-29)
|
||||
## Current Position
|
||||
|
||||
Phase: 02 (ui-performance) — EXECUTING
|
||||
Plan: 4 of 8
|
||||
Plan: 5 of 8
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-07-30 — Phase 02 execution started
|
||||
|
||||
Progress: [██░░░░░░░░] 22%
|
||||
Progress: [███░░░░░░░] 28%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
@ -56,6 +56,7 @@ Progress: [██░░░░░░░░] 22%
|
||||
| Phase 02 P03 | 45min | 3 tasks | 5 files |
|
||||
| Phase 02 P02 | 105min | 3 tasks | 11 files |
|
||||
| Phase 02 P04 | 150min | 3 tasks | 12 files |
|
||||
| Phase 02 P05 | 50min | 2 tasks | 8 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@ -89,6 +90,9 @@ Decisions are logged in PROJECT.md (10 locked ADRs in the `<decisions>` block +
|
||||
- [Phase ?]: 02-04: onActivated is a documented no-op outside a KeepAlive boundary — every arm function now runs from both onMounted and onActivated (fresh-mount guards on Home/Web5/Mesh/Server avoid doubling first-load RPC cost); caught by CloudPeersRefresh.test.ts
|
||||
- [Phase ?]: 02-04: useCachedResource.ts's onActivated no longer eagerly force-loads a never-fetched immediate:false resource, so tab-gated lazy data (Cloud.vue Paid Files/My Files) isn't force-loaded merely by its view entering the KeepAlive cache
|
||||
- [Phase ?]: 02-04: AIUI blank-screen-and-loading symptom reported at Task 3 checkpoint diagnosed as pre-existing (local mock-backend dev mode sets VITE_AIUI_URL=http://localhost:5173 unconditionally with no AIUI repo checked out) — not a regression, left for 02-07 (Chat/AIUI) to address
|
||||
- [Phase ?]: 02-05: mesh.refreshAll()/transport.fetchStatus() stay uncached at the store level (other callers need guaranteed-fresh reads); the useCachedResource wrapper around each lives in Mesh.vue instead, since Pinia's defineStore(id,setup) runs in a bare effectScope where onActivated() silently no-ops
|
||||
- [Phase ?]: 02-05: FLAGGED - RESEARCH.md's premise that Mesh.vue owns a D3 force simulation is incorrect for this codebase (verified via grep); only NetworkMap.vue/Federation.vue has one. Task 2's D3 truths are vacuously satisfied; only the real Leaflet map lifecycle (MeshMap.vue, added to scope) was implemented
|
||||
- [Phase ?]: 02-05: per-group TTL/persist table - mesh.refresh-all/federation-nodes/self-onion/self-did/contacts all persist:false (identity payload); transport-status persists (aggregate, non-identity); reachability groups get 10s TTL, identity groups 300s
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@ -123,6 +127,6 @@ None yet.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-07-30T19:29:12.322Z
|
||||
Stopped at: Completed 02-04-PLAN.md
|
||||
Last session: 2026-07-30T20:24:44.884Z
|
||||
Stopped at: Completed 02-05-PLAN.md
|
||||
Resume file: None
|
||||
|
||||
228
.planning/phases/02-ui-performance/02-05-SUMMARY.md
Normal file
228
.planning/phases/02-ui-performance/02-05-SUMMARY.md
Normal file
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user