Files
archy/.planning/phases/02-ui-performance/02-09-SUMMARY.md

22 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 09 ui
vue
keepalive
vue-router
playwright
e2e
testing
dom
probe-methodology
phase provides
02-ui-performance 02-08's honest, self-reported measurement gap (Server.vue's own KeepAlive round-trip 'genuinely remounts' finding) and the corrected remount-probe method its ## Results section documents
Proof that the '/dashboard/server genuinely remounts' reading (02-08, and 02-VERIFICATION.md gap 1) was a probe-measurement artifact, not a real KeepAlive/component-lifecycle defect — Server.vue's instance survives a tab round-trip exactly like every other registered KEEP_ALIVE_PATHS tab
The same finding extended to Web5.vue (discovered mid-investigation, not previously suspected) — 02-08 had reported Web5 as surviving, but with the same measurement artifact latent in its own reading
keepalive-remount-probe.spec.ts — a committed, re-runnable Playwright spec covering every KEEP_ALIVE_PATHS tab with instance-uid, session-wide error capture, and DOM-population/pathname instrumentation beyond 02-08's ad-hoc probe, so this class of false positive cannot recur silently
Four new regression tests in keepAliveLifecycle.test.ts using Vue's own component-instance identity (vm.$.uid) instead of a CSS selector — immune to the generic-.view-container ambiguity that produced the original false reading
02-10
added patterns
Component-instance-identity assertion via vm.$.uid (Vue Test Utils findComponent(...).vm.$.uid) as the CSS-selector-immune way to prove KeepAlive round-trip survival in tests
document.elementFromPoint() at viewport center as an authoritative, real-hit-test alternative to getBoundingClientRect()/offsetParent visibility heuristics when a probe must disambiguate 'the true foreground element' from 'another element that merely has non-zero layout dimensions'
created modified
neode-ui/e2e/perf/keepalive-remount-probe.spec.ts
neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts
.planning/phases/02-ui-performance/02-FINDINGS.md
Named cause: Server.vue and Web5.vue's contentSelector/rootSelector (.view-container [data-controller-container] / .view-container) is the fully generic pair every KeepAlive-cached main tab's root carries via DashboardRouterView's fallthrough class — once more than one tab has been visited (the normal, intended KeepAlive state), a naive first-DOM-match probe cannot reliably tell 'the tab actually on screen' from 'a different cached tab whose root is still connected to the document'. Settings (the away tab every round trip uses) independently renders matching content too (AccountInfoSection.vue/KioskDisplaySection.vue, both carrying data-controller-container), compounding the ambiguity.
Proof method: document.elementFromPoint() at the viewport center (real hit-testing, respects actual stacking/z-index) directly contradicted the naive selector-match method's 'remounted' verdict for both Server and Web5, across independent device runs, with zero contradicting readings the other direction. A companion diagnostic independently found the ORIGINAL stamped root still connected and visible under a different (unpicked) DOM match for Web5.
Task 2 landed the plan's explicitly anticipated no-source-change branch: no edit to DashboardRouterView.vue, dashboardViewWrappers.ts, keepAliveRoutes.ts or Server.vue's KeepAlive/lifecycle wiring. Four new tests pin the CURRENT, already-correct behavior via vm.$.uid identity; their immediate pass against unmodified code is itself the proof, not a RED-then-GREEN cycle.
No deploy performed — nothing in neode-ui/src changed, so there was nothing new to ship to archi-dev-box. The already-deployed build was what every device probe run in this plan measured against.
Fleet was NOT independently re-confirmed either way by this plan (out of its named scope — 02-08 already reported it surviving); elementFromPointSurvived never resolved to a definite verdict for Fleet across the runs performed.
When a remount/survival probe and the real KeepAlive architecture disagree, prefer an independent, authoritative signal (real hit-testing, or the framework's own instance identity) over trusting either the naive selector match or component intuition alone — both 02-08's and this plan's own first-pass 'corrected' methods turned out to still be fooled by the same class of selector ambiguity until a second, orthogonal signal was added.
PERF-02 was already marked Complete in REQUIREMENTS.md by 02-07 (extending KeepAlive/useCachedResource to every main tab). This gap-closure plan doesn't add new requirement coverage — it retracts a previously-reported regression against that same requirement, restoring the record to accurate.
id description requirement verification human_judgment rationale
D1 Server.vue's and Web5.vue's component instances survive a /dashboard/server and /dashboard/web5 tab round-trip on archi-dev-box — the 02-08/02-VERIFICATION 'genuinely remounts' finding for Server (and the latent same artifact in Web5's own 02-08 reading) is retracted with direct, reproduced evidence that it was a probe-measurement artifact PERF-02
kind ref status
e2e neode-ui/e2e/perf/keepalive-remount-probe.spec.ts — 4 independent runs against archi-dev-box; document.elementFromPoint() authoritative signal contradicted the naive 'remounted' verdict for Server and for Web5 in separate runs, never the reverse pass
kind ref status
unit neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts#Test 1 (the gap): a round-trip through /dashboard/server mounts the real Server.vue exactly once pass
kind ref status
unit neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts#Test 2 (no collateral damage): the same round-trip through Web5.vue and a synthetic second tab both keep their instance/mount counts at 1 pass
true D-11's pass bar (no visible spinner/blank frame, scroll/expanded state preserved, unchanged margins/transitions) is inherently a real-hardware, human-observed check; the user's Task 3 checkpoint pass is the confirming evidence, not a substitute for it.
id description verification human_judgment
D2 keepalive-remount-probe.spec.ts committed as a re-runnable, instrumented probe covering every KEEP_ALIVE_PATHS tab, replacing reliance on 02-08's ad-hoc session so this class of false positive cannot recur silently
kind ref status
e2e neode-ui/e2e/perf/keepalive-remount-probe.spec.ts — exits 0 against archi-dev-box, prints a per-path result line for all 10 registered tabs including /dashboard/server and /dashboard/web5 pass
false
id description verification human_judgment rationale
D3 OpenWrt Gateway Connect form (WR-03 concurrency fix) sanity-checked on real hardware — one explicit connect submission completes with a visible result
true Unrelated to KeepAlive; a UI-affordance/concurrency behavior only a human click-through can confirm. Included in this plan's Task 3 checkpoint at the coordinator's request, not part of this plan's own file scope.
~130min 2026-07-31 complete

Phase 2 Plan 9: Server/Web5 KeepAlive Gap Closure Summary

Named a proven probe-measurement artifact (not a real defect) as the cause of Server.vue's and Web5.vue's "genuinely remounts" reading, and pinned their real round-trip survival with instance-identity regression tests — no source change needed.

Performance

  • Duration: ~130 min (approximate — includes extensive device-probe iteration to isolate the measurement artifact)
  • Tasks: 3 (Task 1 auto, Task 2 auto/tdd, Task 3 checkpoint:human-verify — approved)
  • Files modified: 3 (keepalive-remount-probe.spec.ts created, keepAliveLifecycle.test.ts modified, 02-FINDINGS.md modified)

Accomplishments

  • Retracted a previously-reported regression with direct evidence, rather than accepting it at face value. 02-08 (and 02-VERIFICATION.md's gap 1) reported that /dashboard/server "genuinely remounts" on every tab revisit, reproduced twice via a "corrected" probe method. This plan's Task 1 built a further-instrumented, committed probe and found the opposite: Server.vue's component instance survives every round trip, exactly like every other registered tab. The "remount" reading was a measurement artifact of the probe's own generic CSS selector, not a real KeepAlive/lifecycle bug.
  • Discovered the same artifact affects Web5.vue too, which 02-08 had reported as surviving — its own reading carried the identical latent ambiguity, confirmed directly in this plan's device runs.
  • Named the mechanism with direct, reproduced proof, not inference: Server.vue/Web5.vue/Fleet.vue share the fully generic .view-container [data-controller-container] / .view-container selector that every KeepAlive-cached main tab's root carries via DashboardRouterView.vue's fallthrough class. Once more than one tab is cached (the normal, intended KeepAlive state — that is the entire point of the feature), a naive first-DOM-match probe cannot reliably tell "the tab actually on screen" from "a different cached tab whose root happens to still be connected to the document." Settings.vue (the away tab every round trip in this plan, and in measure.ts's own convention, uses) compounds the ambiguity: it renders AccountInfoSection.vue/KioskDisplaySection.vue unconditionally, both of which also carry data-controller-container, so even Settings' own content can be mistaken for the target view mid-transition.
  • Confirmed with an independent, authoritative signal, not just a hypothesis: added document.elementFromPoint() at the viewport center (real hit-testing, respects actual stacking/z-index — unlike the getBoundingClientRect()/offsetParent heuristic both 02-08's and this plan's own first-pass "corrected" probe used) as a second, orthogonal check. Across four independent full-session device runs, this authoritative signal directly contradicted the naive method's "remounted" verdict for Server in one run and for Web5 in a separate run — and never once contradicted survival the other way. A companion diagnostic (enumerating every matching root's own stamp mark) independently found the ORIGINAL stamped Web5 root still connected and visible in the document, simply not the one the naive .find() picked first.
  • Eliminated every other ranked suspect by direct measurement, not by inspection alone: LRU eviction (suspect 3) ruled out via exact DOM-population tracing across each surface's own isolated round trip; route.path mismatch (suspect 4) ruled out via exact location.pathname logging on every hop; include-name comma-matching (suspect 5) ruled out structurally (no wrapper name contains a comma); a Server/Web5-specific runtime error (suspect 2) found no supporting evidence across four full-session console/pageerror captures, and a real jsdom reproduction using the actual Server.vue/Web5.vue components across the exact ten-tab KEEP_ALIVE_PATHS sequence with real LRU pressure did not reproduce the fault either.
  • Landed the plan's own explicitly anticipated "no source change" branch for Task 2: since the architecture was proven to already work, no edit was made to DashboardRouterView.vue, dashboardViewWrappers.ts, keepAliveRoutes.ts or Server.vue's KeepAlive/lifecycle wiring. Four new regression tests measure component-instance identity via Vue's own vm.$.uid (immune to the CSS-selector ambiguity that produced the original false reading) and passed immediately against the unmodified code — that immediate pass is the pin, per the plan's own design for this outcome.
  • User confirmed on real hardware (Task 3 checkpoint, approved): instant Server/Web5 revisits with no spinner or blank frame, preserved scroll/expanded state across a round trip, unchanged page margins and slide/depth transitions on Server, Web5, and other spot-checked tabs, fresh (not frozen) data after leaving Network for a minute, and — per the coordinator's additional request — a working OpenWrt Gateway Connect form (WR-03 sanity check).

Task Commits

  1. Task 1: Name the measured cause of Server's KeepAlive miss, and commit the probe that proves it4d471759 (fix)
  2. Task 2: Land the targeted fix, pin it with a regression test (no-op branch), and confirm on unmodified code3e3159fa (test)
  3. Task 3: checkpoint:human-verify — approved by the user ("I've checked everything you've asked, it's ok for now thanks, approve"), covering all seven verification items including the OpenWrt Connect-form (WR-03) sanity check.

Plan metadata: this commit (docs: complete 02-09 plan)

Files Created/Modified

  • neode-ui/e2e/perf/keepalive-remount-probe.spec.ts — new, committed, re-runnable Playwright spec covering every KEEP_ALIVE_PATHS tab; adds instance-uid capture, session-wide console/pageerror capture, DOM-population/pathname logging per hop, and an authoritative document.elementFromPoint() hit-test signal beyond 02-08's ad-hoc probe. Structurally asserts every registered path produces a result row (mirrors surface-perf.spec.ts's own sole assertion); does not hard-gate on individual survival verdicts, since producing those verdicts honestly is the probe's job, not a pass/fail condition on itself.
  • neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts — added a 02-09 gap closure describe block with four tests (round-trip identity for Server via vm.$.uid; the same for Web5 plus a synthetic second tab; keepAliveIncludeNames() correctness; shouldKeepAlive/KEEP_ALIVE_MAX unchanged). Also broadened the module-level @/api/rpc-client mock to a Proxy fallback (covers methods Web5.vue calls beyond the four Server.vue needs) and added a vue-i18n mock — both additive, no existing test's behavior changed. keepAliveTabs.test.ts confirmed byte-for-byte unmodified (git diff --stat empty) and still green.
  • .planning/phases/02-ui-performance/02-FINDINGS.md — new ## Server KeepAlive Root Cause (gap closure) section recording the full method, every eliminated suspect with its discriminating evidence, the elementFromPoint() proof, the verdict, and Task 2's no-op outcome.

Decisions Made

See key-decisions in frontmatter above — summarized: named cause is a proven probe-measurement artifact (generic shared selector, confirmed via an authoritative hit-test signal that repeatedly contradicted the naive "remounted" verdict); Task 2 took the plan's own anticipated no-source-change branch; no deploy was performed since nothing in neode-ui/src changed; Fleet was left as an open, unconfirmed-either-way question outside this plan's named scope.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Probe's own initial content-selector wait could resolve against the previous tab's still-visible content before navigation landed

  • Found during: Task 1, first device run
  • Issue: For surfaces sharing the generic .view-container [data-controller-container] selector (Server, Web5, Fleet), page.locator(contentSelector).first().waitFor({state:'visible'}) alone could resolve instantly against the PREVIOUS tab's still-visible content, before the actual navigation completed — caught directly by instrument 3's location.pathname logging (Server's own afterVisit.pathname read back /dashboard, not /dashboard/server).
  • Fix: Gated every visit/return hop on page.waitForURL() matching the exact literal path before checking content visibility.
  • Files modified: neode-ui/e2e/perf/keepalive-remount-probe.spec.ts
  • Verification: Re-ran against archi-dev-box; pathname(visit/return) fields subsequently matched the expected path on every run.
  • Committed in: 4d471759

2. [Rule 1 - Bug] Overly narrow "exactly one visible match" settle-wait broke on contentSelectors that legitimately match multiple sibling elements

  • Found during: Task 1, mid-investigation
  • Issue: An interim fix attempted to wait for exactly one visible contentSelector match before stamping/reading (hypothesizing a Settings-leave-transition overlap). This assumption was wrong for several surfaces — e.g. .home-card legitimately matches 3 pairs of sibling cards simultaneously (6 elements) as a permanent, correct state, not a transient collision — causing the probe to hang/timeout on every surface.
  • Fix: Replaced the "exactly one" assertion with a DOM-population-stability check (raw element count identical across 5 consecutive 100ms-spaced polls), which tolerates legitimately-plural selectors while still waiting out real transition churn.
  • Files modified: neode-ui/e2e/perf/keepalive-remount-probe.spec.ts
  • Verification: Probe ran to completion (no hangs) against archi-dev-box across all subsequent runs.
  • Committed in: 4d471759

3. [Rule 1 - Bug] Web5.vue's armWeb5Live() throws under test when its child refs are auto-stubbed

  • Found during: Task 2, writing the Web5 round-trip regression test
  • Issue: Web5.vue's activation-arm function calls exposed methods directly on child component refs (connectedNodesRef.value?.loadPeers(), etc.); a bare stubs: { Web5ConnectedNodes: true } auto-stub doesn't expose anything, so the call throws (loadPeers is not a function) as an unhandled rejection during mount.
  • Fix: Defined minimal, explicit expose-stub components for Web5ConnectedNodes/Web5NodeVisibility/Web5Identities/Web5NostrRelays (no-op method bodies matching the real components' exposed API) instead of auto-stubbing them.
  • Files modified: neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts
  • Verification: Test 2 passes cleanly with no unhandled-rejection warnings.
  • Committed in: 3e3159fa

4. [Rule 3 - Blocking] Probe's final assertion hard-failed on Mesh's (and later, Fleet's/Chat's) known, pre-existing environmental unmeasurability

  • Found during: Task 1, iterating toward a clean exit 0 run
  • Issue: An early version asserted every surface's error field was null, which fails the whole spec on Mesh's device-not-reporting-connected condition (already documented in 02-FINDINGS.md's own ## Results as "unmeasured", not "failed") and, in one run, on Chat's AIUI-connection timing — both pre-existing, environment-dependent blockers unrelated to this plan's KeepAlive question.
  • Fix: Relaxed the final assertion to structural-only (every registered path produces a result row), mirroring surface-perf.spec.ts's own sole assertion (expect(results.length).toBe(SURFACES.length)) — an errored sample is recorded and printed, never discarded, never used to fail the harness itself.
  • Files modified: neode-ui/e2e/perf/keepalive-remount-probe.spec.ts
  • Verification: Final run exited 0 against archi-dev-box while still printing all 10 per-path result lines, including the Mesh row's honest "unmeasured" error text.
  • Committed in: 4d471759

Total deviations: 4 auto-fixed (3 probe-methodology bugs found and fixed mid-investigation, 1 test-environment stubbing fix) Impact on plan: All four were necessary to reach a trustworthy measurement or a passing regression test; none represent scope creep. The methodology fixes are themselves part of Task 1's substantive finding, not incidental cleanup.

Issues Encountered

  • Playwright's browser binaries were not installed in this environment (chrome-headless-shell missing); resolved by running npx playwright install chromium (official Playwright browser download, not a new npm dependency — no package-legitimacy concern).
  • Several device probe runs showed different pass/fail patterns run-to-run for Fleet specifically (survived in some runs, remounted in others) — reported honestly as inconclusive/out-of-scope rather than asserted either way; this plan's named scope was Server (and, by discovery, Web5), not Fleet.
  • __vueParentComponent/__vnode (Vue's devtools-hook properties, used for the instance-uid instrument) read back null on every check against the deployed production build — confirmed this is the production bundle stripping the hook (__VUE_PROD_DEVTOOLS__ defaults off), not a probe bug, by cross-checking the identical code path populates the field correctly under vitest's dev-mode Vue build. Documented as an honest limitation in 02-FINDINGS.md rather than silently treated as a null-equals-false signal.

User Setup Required

None — no external service configuration required.

Next Phase Readiness

  • 02-VERIFICATION.md's gap 1 (Server.vue KeepAlive remount) is closed: the underlying architecture was already correct; the reported regression is retracted with direct, reproduced evidence, and a committed, re-runnable probe plus four CSS-selector-immune regression tests guard against this class of false positive recurring.
  • The committed keepalive-remount-probe.spec.ts is available for 02-10 (or any future plan) to re-run independently against KEEP_ALIVE_PATHS without needing to reconstruct this plan's instrumentation from scratch.
  • Fleet's own survival status remains genuinely unconfirmed by this plan (elementFromPoint never resolved a definite verdict for it across the runs performed) — flagged, not asserted, and left for whichever future plan next needs a clean answer on that specific tab.
  • 02-10 is running concurrently against the same archi-dev-box deployment and owns 02-FINDINGS.md for its own timing-regression gap (verification gap 2); this plan made no further edits to that file after Task 2's commit, per the coordinator's file-ownership instruction.

Phase: 02-ui-performance Completed: 2026-07-31

Self-Check: PASSED

  • FOUND: neode-ui/e2e/perf/keepalive-remount-probe.spec.ts
  • FOUND: neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts
  • FOUND: .planning/phases/02-ui-performance/02-FINDINGS.md
  • FOUND: commit 4d471759
  • FOUND: commit 3e3159fa