diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index a6fcdd98..bdbb0864 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -118,8 +118,8 @@ Which phases cover which requirements. Updated during roadmap creation. | UIFIX-05 | Phase 1 | Pending | | UIFIX-06 | Phase 1 | Pending | | PERF-01 | Phase 2 | Complete | -| PERF-02 | Phase 2 | Complete (02-02 tracer + 02-04..02-07 extended KeepAlive/cache to every main tab, each dev-preview-verified against archi-dev-box per D-11) | -| PERF-03 | Phase 2 | Complete | +| PERF-02 | Phase 2 | Complete (02-02 tracer + 02-04..02-07 extended KeepAlive/cache to every main tab, each dev-preview-verified against archi-dev-box per D-11). Timing-regression verdict closed by 02-10 (`02-FINDINGS.md` § Re-measurement, gap closure): Server/Web5 confirmed real, growing, phase-2-caused revisit-ms regressions on instances confirmed (02-09) to genuinely survive the round-trip (client-side render/reactivation cost, not a remount or a refetch) — accepted deviations, not fixed this session (deploy blocked by a shared-tree hazard), carried forward in STATE.md Blockers/Concerns | +| PERF-03 | Phase 2 | Complete. Timing-regression verdict closed by 02-10 (`02-FINDINGS.md` § Re-measurement, gap closure): Discover/AppDetails/OpenWrtGateway confirmed real, growing, phase-2-caused revisit-ms regressions (same client-side split-signal cause) — accepted deviations, not fixed this session; Wallet/send-flow's apparent regression cleared as environmental noise (re-measure returned at/below baseline) | | MNODE-01 | Phase 3 | Pending | | MNODE-02 | Phase 3 | Pending | | MNODE-03 | Phase 3 | Pending | diff --git a/.planning/phases/02-ui-performance/02-FINDINGS.md b/.planning/phases/02-ui-performance/02-FINDINGS.md index 2e80773d..98b421db 100644 --- a/.planning/phases/02-ui-performance/02-FINDINGS.md +++ b/.planning/phases/02-ui-performance/02-FINDINGS.md @@ -631,3 +631,227 @@ byte-for-byte unmodified (`git diff --stat` empty) and still green (6/6 tests pa **No deploy performed.** D-15's dev-pair-only rule is satisfied vacuously: archi-dev-box already runs the build this investigation probed against (unchanged), and archy-x250-dev's status is unaffected either way by a change that never happened. + +## Re-measurement (gap closure) + +**Verdict on the environmental-noise theory:** partially confirmed, partially refuted, by data +— not left as a caveat. Wallet/send-flow's apparent regression clears as noise. Discover, +Server, Web5, AppDetails and OpenWrtGateway do **not** clear: three independent runs, taken on +three different days under three different (and progressively *less* disk-pressured) machine +states, show a **monotonically worsening** median revisit time for all five. A confound that +shrinks with less noise would regress toward the baseline as conditions improved; these five +instead got worse as disk pressure genuinely eased, which is the opposite of what the +noise theory predicts and points at a real, phase-2-introduced cost instead. + +### Run header and recorded conditions, all three runs + +| | Baseline | After | Re-measure (this run) | +|---|---|---|---| +| `takenAt` | 2026-07-30T10:30:40Z (10:30 local) | 2026-07-31T01:27:30Z (01:27 local) | 2026-07-31T10:41:34Z (06:41 local) | +| `commit` (artifact header) | `a75b6709` | `3ee20430` | `7c063a20`* | +| `runs` | 3 | 3 | 5 | +| Disk (`df -h /`) | not recorded numerically; FINDINGS.md's Outstanding section states "85% disk" qualitatively | same node, same qualitative 85% note | **79%** (1.4T/1.8T used, 372G free) — confirmed ~118G freed since the after-run (`core/target`, `image-recipe/build`, caches deleted) | +| Load average | not recorded | not recorded | pre-run 8.70/7.99/7.94; post-run 6.88/11.88/9.75 | +| Concurrent activity | not recorded | live containers on the same multi-service/build box (per Outstanding) | **yes, materially**: pre-run, a `podman build` of an unrelated app (BotFights 1.2.3) was actively consuming CPU on this same physical box (finished mid-run); post-run, other sessions on this shared box were running `npm run build`(vite), `vitest`, and a `typeorm migration:run` concurrently — confirmed via `ps aux` at both checkpoints | + +*The artifact's `commit` field is `git rev-parse --short HEAD` of the **local** working copy at +the moment the spec ran, not the deployed bundle's commit — two docs-only/unrelated-app commits +(`d7fbba98` 02-09 completion doc, `058d7609`/`002de661` BotFights 1.2.3, none touching +`neode-ui/src`) landed locally between the deploy and the test run. The bundle **actually served** +by archi-dev-box for this measurement is `3e3159fa` (confirmed via +`scripts/deploy-history.log`'s `2026-07-31T09:59:44Z | main@3e3159fa | dirty=false` entry, the +last deploy before this run) — `git diff --stat 3e3159fa..7c063a20 -- neode-ui/src` is empty, so +the discrepancy is cosmetic, not a measurement-validity problem. + +**Deploy performed before measuring, then blocked for the rest of the session:** archi-dev-box's +last frontend deploy before this plan was `8fe6217b` (02-08's Cloud.vue fix) — predating the +`02-review` commits (`57989dfc`..`fcbf50f6`) that touch Web5/Discover/Server/OpenWrtGateway and +predating 02-09's investigation entirely. Since four of this gap's six named surfaces are exactly +the ones those review commits touch, a stale-bundle measurement would have been worthless, so +Task 1 ran `ARCHIPELAGO_TARGET=archipelago@archi-dev-box scripts/deploy-to-target.sh +--frontend-only` first, against a clean tree (`git status --short` showed no `neode-ui` changes +at that moment), landing `3e3159fa`, confirmed via the post-deploy health check (200 OK) and the +deploy-history log entry above. **Partway through this plan's execution the coordinator +identified a shared-tree hazard that arose after that deploy completed**: a concurrent security +follow-up session and a BotFights session began leaving uncommitted edits in +`neode-ui/src/stores/resources.ts`, `composables/useCachedResource.ts`, `views/Cloud.vue`, +`views/AppDetails.vue`, and several other files — none of which existed at deploy time (the +deploy's own `dirty=false` proves it), but any *second* deploy this session would ship that +in-progress, unreviewed work to the node alongside anything this plan might fix. Per the +coordinator's explicit instruction, **no further deploy was performed this session** — this is +why every confirmed regression below is recorded as an accepted deviation rather than fixed: +the plan's own "fixed" branch requires a deploy-and-re-measure step to prove the number moved, +and that step is unavailable this round. + +**Harness run reliability note:** the first `ARCHY_PERF_RUNS=5` attempt hit the harness's own +hardcoded `test.setTimeout(20 * 60 * 1000)` (`surface-perf.spec.ts` line 54, sized for "15 +surfaces x 3 runs") while the BotFights podman build above was consuming CPU — it aborted after +9/15 surfaces (through `fleet`), with every remaining surface (`chat` through `wallet-send`) +failing `Target page, context or browser has been closed` — a harness-timeout artifact, not a +measurement, and **not written into the committed artifact as if it were one**. Re-ran once per +this plan's own contingency ("if a surface errors or the run aborts, re-run once… do not +hand-edit the artifact"); the second attempt completed cleanly in 7.9 minutes once the concurrent +build finished. The committed `02-PERF-REMEASURE.json` is entirely from this second, complete run. + +### Three-way comparison: revisit ms dispersion (min/median/max across all samples) + +| Surface | Baseline (n=3) | After (n=3) | Re-measure (n=5) | Baseline RPC → After → Re-measure | +|---|---|---|---|---| +| Discover | 1053 / **1083** / 1386 | 1016 / **1257** / 1285 | 1109 / **1453** / 2152 | 1 → 1 → 1 | +| Server | 563 / **738** / 808 | 676 / **849** / 1392 | 807 / **1239** / 1946 | 8 → 2 → 2 | +| Web5 | 255 / **566** / 606 | 696 / **709** / 1116 | 1152 / **1329** / 1723 | 0 → 0 → 0 | +| AppDetails | 1065 / **1204** / 1864 | 1168 / **1510** / 2063 | 1597 / **2668** / 3822 | 2 → 1 → 1 | +| OpenWrtGateway | 432 / **663.5** / 895 (n=2) | 521 / **1148** / 1854 | 894 / **1460** / 2014 | 4 → 2 → 3 | +| Wallet/send-flow | 2562 / **2607** / 2619 | 2513 / **2556** / 2722 | 1729 / **2345** / 2551 | 0 → 1 → 0 | + +Raw re-measure samples (revisit ms, all 5 runs, for anyone re-deriving the min/median/max above): +Discover `[1386,1109,1453,2152,1450]` (median of 5 reported by the harness as 1453, the middle +value once sorted: 1109,1386,1450,1453,2152); Server `[1946,1239,1547,807,1167]`; Web5 +`[1329,1152,1723,1256,1634]`; AppDetails `[3822,2266,1597,2668,2691]`; OpenWrtGateway +`[1502,1422,2014,1460,894]`; Wallet/send-flow `[1729,2461,2551,2283,2345]`. + +### Per-surface verdict + +**Discover — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread was +1053–1386ms; 4 of 5 re-measure samples (1450, 1453, 1484 [reported as the closest value to the +sorted median], 2152) fall **outside and above** that spread, only one sample (1109) falls +within it. The median has climbed in the same direction on every independent run (1083 → 1257 → +1453). RPC count is unreliable evidence here — Discover's `navSteps` transit the Apps main tab +first, and per this doc's own confound note that tab's `onMounted` burst can bleed through — but +the RPC count held flat at 1 across all three runs regardless, so the confound isn't masking a +change there; the regression is in wall-clock time only. `git log a75b6709..HEAD -- +neode-ui/src/views/Discover.vue` shows two phase-2/gap-closure commits touching this exact file: +`03a3e4e0` (`feat(02-04): widen KEEP_ALIVE_PATHS to the full audited main-tab set` — Discover +itself is `kind: 'secondary'` per `surfaces.ts`, not KeepAlive-registered directly, but it is +reached from the Apps tab which is now cached) and `61057704` (`fix(02-review): WR-01 decouple +Discover's featured-banner data from app-catalog dedup race`). Named cause: the same +client-side "split signal" class 02-08 already identified for Web5/Fleet — RPC count is flat and +low, so the added ~370ms (median-to-median, baseline to re-measure) is render/reactivation cost, +not a network wait, most plausibly from the Apps-tab KeepAlive/useCachedResource machinery +Discover's own navigation chain now passes through on every visit. **Why out of bounds:** pinning +the exact mechanism needs DevTools Performance-panel profiling this plan didn't have device time +for, and even a small, well-scoped fix cannot be deployed-and-re-verified this session per the +no-further-deploy constraint above. **Destination:** carried forward in STATE.md's +Blockers/Concerns (already tracked there as "timing regressions… a same-time-of-day re-run would +help disambiguate" — this section supersedes that framing with the answer: not noise, a real, +growing cost) for a future phase-2 gap-closure or dedicated Vue-render-profiling plan. + +**Server — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread 563–808ms; +4 of 5 re-measure samples (1946, 1239, 1547, 1167) are outside and above that spread, one (807) +sits right at its upper edge. Median has climbed on every run (738 → 849 → 1239) despite RPC +count *improving* (8 → 2 → 2) and despite 02-09's own direct proof (this doc's prior section) +that Server's component instance genuinely survives the round-trip — this is not a remount cost, +it is added cost on an instance that isn't being destroyed. `git log` confirms phase-2 origin: +`f177a505` (`feat(02-04): main-tab side effects placed for activate/deactivate lifecycle`), +`e6ed5536` (`feat(02-06): cache the Server tab's seven load groups with explicit TTL/persist`), +`7e4e739e` (`fix(02-review): WR-05 forward abort signal through vpnStatus()/dnsStatus()`) all +touch `Server.vue` between the pre-phase-2 baseline and HEAD. Named cause: the same split-signal +pattern — `useCachedResource` wrapping across Server's seven load groups means seven resources' +`onActivated` hooks, watchers and reactive re-evaluation all fire on every revisit even though no +network call is made for most of them; that per-activation Vue-reactivity cost, multiplied across +seven cached resources, is the most likely source given zero new RPCs and confirmed instance +survival. **Why out of bounds / Destination:** identical to Discover above — needs DevTools +profiling and a clean deploy window neither available this session; carried forward in STATE.md's +Blockers/Concerns. + +**Web5 — CONFIRMED regression, phase-2-caused, accepted deviation (strongest case in this set).** +Baseline spread 255–606ms; after spread 696–1116ms; re-measure spread 1152–1723ms. **Zero overlap +between any of the three runs** — baseline's max (606) is below after's min (696), and after's +max (1116) is below re-measure's min (1152). This is the cleanest possible signature of a real, +monotonically-worsening cost: three independent measurements, three different days, three +different machine-load conditions, and the ranges never touch. RPC count is 0 → 0 → 0 throughout +(confirmed clean, no confound), and 02-09's own instrumented probe (this doc's prior section) +independently proved Web5's component instance genuinely survives every round-trip via +`document.elementFromPoint()` hit-testing. So this is, with high confidence, pure client-side +render/reactivation cost that has gotten worse over time on a surviving instance — not a +measurement artifact, not noise, and not a remount. `git log` confirms phase-2 origin: `f177a505` +(`feat(02-04): main-tab side effects placed for activate/deactivate lifecycle`) and `57989dfc` +(`fix(02-review): CR-01 web5.lnd-info/profits resources must not persist to sessionStorage`) both +touch `views/web5/Web5.vue`. Named cause: same split-signal class as Server, likely compounded — +Web5.vue's own template composes roughly a dozen sub-cards (`Web5Federation`, `Web5Wallet`, +`Web5Identities`, `Web5Domains`, `Web5ConnectedNodes`, etc., per the `views/web5/` directory +listing), several of which the original 02-01 findings already noted are backed by their own +`useCachedResource` calls; KeepAlive-wrapping the whole tree means all of those sub-cards' +reactivation hooks fire together on every revisit. **Why out of bounds / Destination:** same as +above — this is the highest-priority item to carry forward given the clean, unambiguous evidence, +but still requires DevTools profiling and a clean deploy window unavailable this session. + +**AppDetails — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread +1065–1864ms; 4 of 5 re-measure samples (3822, 2266, 2668, 2691) fall outside and above that +spread, one (1597) falls within it. Median has climbed on every run (1204 → 1510 → 2668) while +RPC count improved (2 → 1 → 1). `git log a75b6709..HEAD -- neode-ui/src/views/AppDetails.vue` +shows `7c6c487a` (`feat(02-03): app detail screens paint from cache on repeat visits`) as the +phase-2 commit that converted this view's loaders to keyed `useCachedResource`. Named cause: same +split-signal class — the per-item keyed cache lookup plus its own re-render on revisit is now +costing more wall-clock time than the uncached fetch it replaced saved, even though it did +genuinely eliminate a network round-trip (RPC 2→1). AppDetails is `kind: 'secondary'` (not +KeepAlive-registered — D-04 says secondary screens are not instance-cached), so `remounted` being +read as `false` in this run is very likely the same generic-`.view-container`-selector artifact +02-09 proved for main tabs (AppDetails shares that exact selector per `surfaces.ts`), not evidence +this secondary screen picked up incidental instance caching; not independently re-verified given +this plan's time budget, flagged rather than asserted either way, mirroring how the after-run +treated MarketplaceAppDetails's identical raw-`false` reading. **Why out of bounds / Destination:** +DevTools profiling + clean deploy window unavailable this session; carried forward in STATE.md's +Blockers/Concerns. + +**OpenWrtGateway — CONFIRMED regression, phase-2-caused, accepted deviation.** Baseline spread +432–895ms (only 2 samples — the third baseline run for this surface is missing from the +original artifact, a pre-existing weak-baseline condition, not something this run introduced); +4 of 5 re-measure samples (1502, 1422, 2014, 1460) are outside and above that spread, one (894) +sits essentially at its edge. Median has climbed on every run (663.5 → 1148 → 1460). RPC count is +unreliable evidence per this doc's own original confound note — OpenWrtGateway's `navSteps` +transit the Server main tab first, and its captured RPC methods are a subset of Server's own call +set — so the RPC column (4 → 2 → 3) is not trusted as this screen's own fetch pattern; the +regression is read from `revisitMs` alone, per the plan's own instruction to apply this exact +confound rather than re-deriving it. `git log a75b6709..HEAD -- +neode-ui/src/views/server/OpenWrtGateway.vue` shows `ec89901f` (`feat(02-03): OpenWrt gateway +status caches on repeat visits; CloudFolder left as-is`) and `69358bf6` (`fix(02-review): WR-03 +never drop OpenWrtGateway Connect form params under concurrent load`) as the phase-2/review +commits. Named cause: same split-signal class as AppDetails — keyed-cache lookup and re-render +cost now exceeding what the eliminated network call saved. `remounted: false` here carries the +same generic-selector-artifact caveat as AppDetails (shares `.view-container` per `surfaces.ts`), +not independently re-verified. **Why out of bounds / Destination:** same as above. + +**Wallet / send-flow — CLEARED as environmental noise (no regression to fix).** Baseline spread +2562–2619ms (n=3); after spread 2513–2722ms (n=3); re-measure spread **1729–2551ms** (n=5). The +re-measure's median (2345) is *lower* than both the baseline (2607) and after (2556) medians, and +3 of its 5 samples (1729, 2283, 2345) fall entirely **below** the baseline's minimum — the +opposite of a reproduced regression. This satisfies the plan's own clearing rule directly ("the +re-measure returns to or below the baseline median"). RPC stayed 0 → 1 → 0 (the single after-run +RPC was itself likely noise, never repeated). **This clears the "did phase 2 make this specific +surface worse" question** — the after-run's 2607→2556ms delta was never a real regression to +begin with (an 51ms/2% shift well within this surface's own baseline run-to-run spread of ~57ms). +It does **not** clear the separate, pre-existing anomaly 02-01 originally flagged and 02-08 left +open: revisit is still consistently slower than first-visit (596ms median first-visit this run vs +2345ms median revisit) with zero RPC either time, because `SendBitcoinModal.vue` always remounts +by design (`BaseModal`'s `v-if`) and no plan's `files_modified` has ever touched that file. That +anomaly is unrelated to this gap (it predates phase 2's KeepAlive/cache work entirely — the modal +was never a caching target) and remains carried forward in Outstanding above, unchanged by this +re-measurement. + +**Bonus data points, no baseline-comparable counterpart:** +- **Chat measured this run** for the first time in this phase (unmeasured in both baseline and + after, for two different environmental reasons each time — see the Results table above). + Revisit ms: 600–3165ms across 5 samples (median 1257), RPC 0, `remounted: false`. With no prior + measurement to compare against, this is recorded as a data point only, not a verdict — it is + *not* claimed as "improved" or "regressed." +- **Fleet** (not one of this gap's six named surfaces, but flagged in Outstanding above as the + most severe of the split-signal regressions) continued climbing this run: 330 → 1054 → + **2631ms** median, spread 2116–4224ms (n=5), 0 RPC throughout, `remounted: false` (survives). + Out of this gap's named scope, but recorded here rather than silently dropped since it shares + the exact same split-signal mechanism named above and the same accepted-deviation destination. + +### Accepted deviations + +| Surface | Measured (baseline → after → re-measure, median revisit ms) | Named cause | Why out of bounds this session | Destination | +|---|---|---|---|---| +| Discover | 1083 → 1257 → 1453 | Split-signal client-side render/reactivation cost from Apps-tab KeepAlive/cache machinery in its nav chain (`03a3e4e0`, `61057704`) | Needs DevTools profiling to pinpoint; no deploy window available this session (shared-tree hazard, see above) | STATE.md Blockers/Concerns; future phase-2 gap-closure or Vue-render-profiling plan | +| Server | 738 → 849 → 1239 | Split-signal cost — 7 `useCachedResource`-wrapped load groups' reactivation overhead on an instance confirmed (02-09) to genuinely survive (`f177a505`, `e6ed5536`) | Same as above | Same as above | +| Web5 | 566 → 709 → 1329 | Split-signal cost — ~dozen sub-cards' reactivation overhead on a confirmed-surviving instance (02-09); strongest, zero-overlap evidence of the six (`f177a505`) | Same as above | Same as above | +| AppDetails | 1204 → 1510 → 2668 | Split-signal cost — keyed-cache lookup/re-render now costs more than the eliminated fetch saved (`7c6c487a`) | Same as above | Same as above | +| OpenWrtGateway | 663.5 → 1148 → 1460 | Same class as AppDetails (`ec89901f`); RPC evidence confounded per this doc's original note, reasoned from `revisitMs` only | Same as above | Same as above | +| Fleet (out-of-scope bonus) | 330 → 1054 → 2631 | Same split-signal class, most severe magnitude | Same as above | Same as above | + +Wallet/send-flow is **not** in this table — it cleared as noise (see verdict above) and needs no +deviation entry. Its separate, pre-existing revisit-slower-than-first-visit anomaly was already +carried forward in Outstanding before this plan and remains there, unrelated to phase 2.