Files
archy/.planning/phases/02-ui-performance/02-08-PLAN.md
T

24 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-ui-performance 08 execute 5
02-03
02-05
02-06
02-07
.planning/phases/02-ui-performance/02-PERF-AFTER.json
.planning/phases/02-ui-performance/02-FINDINGS.md
neode-ui/src/views/dashboard/keepAliveRoutes.ts
false
PERF-01
PERF-02
PERF-03
truths prohibitions artifacts key_links
The same harness that produced the baseline is re-run against archi-dev-box and produces a directly comparable after-artifact
For every surface the findings doc named as slow, the after-artifact shows a lower revisit time and a lower revisit RPC count than the baseline
For every main tab registered for instance caching, the after-artifact's remount probe shows the component instance survived a tab round-trip
Revisiting any main tab already visited this session on archi-dev-box shows no spinner and no blank screen (D-11 pass bar)
Reopening any secondary screen already opened this session on archi-dev-box shows no blocking reload (D-11 pass bar)
The instance-cache cap is set from observed on-device memory rather than from an estimate, and the observation is recorded
The build shipped to the dev pair actually contains this phase's changes, confirmed by grepping the built bundle
The deploy reached the dev pair only — no fleet node and no OTA channel received this build (D-15)
A surface that regressed against its baseline is recorded as a regression rather than averaged away
statement verification
Extended use across many tab visits on archi-dev-box does not reintroduce sluggishness — memory and idle CPU stay flat backstop
MUST NOT present inferred, code-read, or cherry-picked numbers as measured profiling results, and MUST NOT omit a surface from the results because it was hard to measure — an unmeasured surface is recorded as unmeasured, never as improved
MUST NOT achieve perceived speed by removing behavior or hiding state — no suppressing the refresh indicator, no dropping a fetch a surface needs, no disabling a feature to win the metric
MUST NOT push this phase's build beyond the dev pair — no fleet node, no OTA channel, no alpha-tester deploy path
path provides
.planning/phases/02-ui-performance/02-PERF-AFTER.json Post-fix measurements from the same harness and the same target as the baseline
path provides
.planning/phases/02-ui-performance/02-FINDINGS.md A Results section comparing baseline to after, per surface, including any regression
from to via pattern
.planning/phases/02-ui-performance/02-PERF-AFTER.json neode-ui/e2e/perf/surface-perf.spec.ts produced by re-running the plan 02-01 harness unmodified against the same target surface-perf
from to via pattern
.planning/phases/02-ui-performance/02-FINDINGS.md .planning/phases/02-ui-performance/02-PERF-BASELINE.json the Results section pairs each after row with its baseline row 02-PERF-BASELINE
Deploy this phase's frontend to the dev pair, re-measure every surface on archi-dev-box with the same harness that produced the baseline, and walk the D-11 pass bar by hand.

Purpose: PERF-01 closes the loop it opened — the same instrument, the same target, before and after. PERF-02 and PERF-03 are both stated in terms of what the user perceives on real node hardware, and CONTEXT.md D-11 makes archi-dev-box the verification target with an explicit pass bar: no visible spinner or blank on revisit of a tab or secondary screen already visited this session; first visits may still show loading. D-15 keeps this to the dev pair — no OTA, no fleet.

Output: a committed after-artifact, a per-surface before/after comparison including any regression, an instance-cache cap set from observed memory, and a human-confirmed pass bar on the node.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-ui-performance/02-CONTEXT.md @.planning/phases/02-ui-performance/02-FINDINGS.md @.planning/phases/02-ui-performance/02-02-SUMMARY.md @.planning/phases/02-ui-performance/02-03-SUMMARY.md @.planning/phases/02-ui-performance/02-04-SUMMARY.md @.planning/phases/02-ui-performance/02-05-SUMMARY.md @.planning/phases/02-ui-performance/02-06-SUMMARY.md @.planning/phases/02-ui-performance/02-07-SUMMARY.md @CLAUDE.md Task 1: Build and deploy the frontend to the dev pair only archi-dev-box resolves and answers over HTTP from this machine, and `scripts/deploy-config.sh` exists (it is gitignored; `scripts/deploy-config.example` documents it) so the deploy script can authenticate neode-ui/src/views/dashboard/keepAliveRoutes.ts - `CLAUDE.md` — the build note: `neode-ui/` builds to `web/dist/neode-ui/`, and the built bundle must be grepped for new strings before shipping because the build can silently no-op. Also the commit-and-push-every-unit-of-work rule. - `scripts/deploy-to-target.sh` lines 1-30 — the usage block. `--frontend-only` skips the Rust build and container rebuilds; `--live` targets the default host; `--both` fans out to additional hosts; `--tailscale` reaches the alpha-tester nodes. - `scripts/deploy-config.example` — what `deploy-config.sh` must contain. - `.planning/PROJECT.md` — the deploy-to-the-dev-pair-before-any-OTA rule. - `.planning/phases/02-ui-performance/02-CONTEXT.md` — D-15 restricts this phase to the dev pair with no OTA, and D-11 names archi-dev-box as the verification target. - `.planning/phases/02-ui-performance/02-0{2,3,4,5,6,7}-SUMMARY.md` — the list of new symbols and cache keys to grep the built bundle for. Run the full check suite from `neode-ui/` first: `npm run type-check`, then `npm run test`, then `npm run build`. All three must be green before anything ships.
Then confirm the build is real, not a silent no-op. CLAUDE.md warns about exactly this.
Grep `web/dist/neode-ui/` for a representative string introduced by each plan in this
phase — the `shouldKeepAlive` classifier, the `RefreshIndicator`, a cache key such as
`app-catalog` or `app-details:`, and the D-14 flag names recorded in
`02-AIUI-D14.md`. Collect the exact strings from the plan SUMMARYs rather than
guessing them. If any is absent, the build did not take — clean and rebuild before
deploying, and record what happened.

Deploy the frontend to the dev pair with `scripts/deploy-to-target.sh
--frontend-only`, targeted at the dev pair hosts only. Read the script's usage block
and its host configuration to identify which flag combination reaches exactly the dev
pair. Do not use `--tailscale` or `--tailscale-node` — those reach alpha-tester fleet
nodes. Do not trigger any OTA or release path. D-15 is explicit and this plan's third
prohibition restates it. Record the exact command run and the hosts it touched.

After deploying, tune the instance-cache cap. `KEEP_ALIVE_MAX` has been 6 since plan
02-02 on the reasoning that it is smaller than the main-tab count so the long tail
evicts, which was never validated against real hardware. On archi-dev-box, open the
UI, cycle through every main tab twice including Mesh, and read the browser's memory
usage before and after. If resident memory grows in a way that would matter on a
low-power fleet node, lower the cap; if it is comfortably flat and evictions are
causing visible reloads of tabs the user is actively cycling, raise it. Change the
constant only if the observation calls for it, commit the change, and record the
measurement either way — an unchanged 6 with a recorded memory reading is a valid and
preferable outcome to an unexamined 6.

Commit and push each unit of work as it lands, per CLAUDE.md, staging explicitly by
path.
cd neode-ui && npm run type-check && npm run test && npm run build && for s in shouldKeepAlive RefreshIndicator app-catalog; do grep -rqs "$s" ../web/dist/neode-ui/ || { echo "MISSING FROM BUNDLE: $s"; exit 1; }; done; echo BUNDLE_OK - `npm run type-check`, `npm run test` and `npm run build` all exit 0 - `web/dist/neode-ui/` contains `shouldKeepAlive`, `RefreshIndicator` and at least one cache key introduced by this phase - The deploy command actually run is recorded verbatim in the SUMMARY, along with every host it touched - No alpha-tester or fleet host appears in that host list; no OTA or release path was invoked - `KEEP_ALIVE_MAX`'s value at the end of this task is recorded together with the on-device memory reading that justifies it - Every change is committed and pushed, staged by explicit path A verified-real build is running on the dev pair and nowhere else, and the instance-cache cap is set from an observed memory reading rather than an estimate. Task 2: Re-measure on archi-dev-box and write the before/after comparison The dev-pair deploy from Task 1 is live — the archi-dev-box UI serves the new bundle (grep the served asset for `shouldKeepAlive`, not just the local `web/dist` copy) .planning/phases/02-ui-performance/02-PERF-AFTER.json, .planning/phases/02-ui-performance/02-FINDINGS.md - `neode-ui/e2e/perf/surface-perf.spec.ts`, `neode-ui/e2e/perf/measure.ts`, `neode-ui/e2e/perf/surfaces.ts` — the harness from plan 02-01 and its `ARCHY_BASE_URL` / `ARCHY_PERF_OUT` contract - `.planning/phases/02-ui-performance/02-PERF-BASELINE.json` — the run header records the exact target and sample count the after-run must match - `.planning/phases/02-ui-performance/02-FINDINGS.md` — the per-surface table and ranked fix order this task extends with results - `.planning/phases/02-ui-performance/02-04-SUMMARY.md` — which main-tab paths ended up registered for instance caching, so the remount-probe expectation is known per surface - `.planning/phases/02-ui-performance/02-03-SUMMARY.md` — which secondary screens were converted and which were reported as gaps Re-run the plan 02-01 harness unmodified against archi-dev-box, with the same `ARCHY_BASE_URL` and the same sample count recorded in the baseline's run header, and `ARCHY_PERF_OUT` pointed at `.planning/phases/02-ui-performance/02-PERF-AFTER.json`. Do not edit the harness to make numbers look better; if a selector genuinely broke because a view's markup changed, fix the selector, re-run BOTH the baseline target and the after target so the pair stays comparable, and say so in the run header.
Append a `## Results` section to `02-FINDINGS.md` with a table pairing each surface's
baseline and after rows: Surface, Baseline revisit ms, After revisit ms, Baseline
revisit RPC count, After revisit RPC count, Baseline remounted, After remounted,
Verdict. `Verdict` takes one of `improved`, `unchanged`, `regressed` or `unmeasured`.

Three rules govern that table and none of them may be softened:
- A surface that got worse is recorded as `regressed` with its numbers. Do not average
  it into an aggregate, do not re-run until it looks better, do not drop it.
- A surface that could not be measured is `unmeasured` with the reason. It is never
  recorded as `improved`.
- Every number comes from the artifacts. No number is estimated, inferred from reading
  the code, or taken from the best of several runs.

Add a `## Outstanding` subsection listing anything still open: surfaces still
classified `unmeasured`, any secondary screen plan 02-03 reported as a gap rather than
converting, any `regressed` verdict, and any assumption from the plan set's
`Assumptions & Flagged Items` blocks that execution did not settle. This list is what
`/gsd-verify-work` and any follow-up gap-closure planning read.

Redact before committing, as in plan 02-01: onion addresses, DIDs, pubkeys, wallet
figures, peer hostnames and file names do not go into the artifacts. RPC method names
and timings do.
node -e "const p='/home/archipelago/Projects/archy/.planning/phases/02-ui-performance/'; const a=require(p+'02-PERF-AFTER.json'), b=require(p+'02-PERF-BASELINE.json'); const ar=a.results??a, br=b.results??b; if(!Array.isArray(ar)||ar.length!==br.length){console.error('row count mismatch',ar.length,br.length);process.exit(1)} console.log('rows',ar.length)" && grep -qF '## Results' /home/archipelago/Projects/archy/.planning/phases/02-ui-performance/02-FINDINGS.md && grep -qF '## Outstanding' /home/archipelago/Projects/archy/.planning/phases/02-ui-performance/02-FINDINGS.md && echo OK - `.planning/phases/02-ui-performance/02-PERF-AFTER.json` exists, parses, and has the same row count as the baseline - Its run header records the same `baseUrl` and `runs` as the baseline run header, or explains any difference - `02-FINDINGS.md` contains `## Results` with one row per surface and `## Outstanding` - Every Results row's Verdict is one of `improved`, `unchanged`, `regressed`, `unmeasured` - Every surface the findings originally named as slow has a numeric after value, or a recorded reason it is `unmeasured` - Every main-tab path registered in `KEEP_ALIVE_PATHS` has `After remounted` false - `## Outstanding` lists every regressed surface, every unmeasured surface, every gap reported by plan 02-03, and every unsettled flagged assumption - Both artifacts are committed The same instrument on the same target says, in committed numbers, what this phase actually changed per surface — including anything that got worse or could not be measured. Task 3: D-11 pass bar on archi-dev-box — is the sluggishness gone on-device The whole phase, running on archi-dev-box: main tabs instance-cached with capped eviction and background revalidation, secondary screens cached per item, the Mesh graph and map held for the session, Server and Home fetches cached with the wallet always re-checking on re-entry, and the AIUI panel persisting across tab switches with its two D-14 defaults. Automated before/after numbers are already committed in `02-FINDINGS.md` `## Results`. This is the D-11 pass bar. The bar is: no visible spinner and no blank screen when revisiting a tab or secondary screen already visited this session. First visits may still show loading — that is allowed.
1. Open archi-dev-box's UI directly on the node (not the local dev preview) so you are
   exercising the deployed build on real hardware.
2. First pass — visit every main tab once, in order: Home, Apps, App store, Cloud,
   Mesh, Server, Web5, Fleet, Chat, Settings. Loading here is expected.
3. Second pass — revisit each of those tabs in a different order. Expected on every
   one: content appears immediately, no spinner, no blank frame, scroll position and
   in-page state preserved, no intro animation replay.
4. Open at least three secondary screens: an app's detail page, a cloud folder, and
   one more of your choosing. Go back to the parent tab and reopen each. Expected on
   reopen: content appears immediately with no blocking reload.
5. The app store specifically — this is the surface you reported as worst. Switch into
   it and out of it several times. Expected: it should feel immediate every time after
   the first.
6. Home wallet — note the balance, go away for a minute, come back. Expected: the
   figure is there instantly and visibly re-checks (a small indicator, then the
   current number). It must not sit frozen.
7. Mesh — enter, let the graph settle, leave, return. Expected: the graph is where you
   left it, the map draws correctly, and the layout does not re-animate from scratch.
8. Chat — the AIUI panel should already be expanded, and should still be loaded after
   switching away and back.
9. Extended use — keep using the node normally for several minutes, cycling tabs.
   Expected: it stays fast. If sluggishness creeps back after extended use, say so and
   name the tabs you had visited — that points at the instance-cache cap.
10. Compare against how it felt before this phase. The question that decides this
    checkpoint: is the sluggishness you reported gone on this device?
Type "approved" if the pass bar is met, or describe what you saw: which step, which surface, what happened instead.

<threat_model>

Trust Boundaries

Boundary Description
developer workstation → dev-pair nodes A build crosses onto running hardware via the deploy script
dev pair → fleet / OTA channel The boundary D-15 forbids crossing in this phase
archi-dev-box measurements → committed artifacts Node data crosses into a pushed repository

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-02-21 Elevation of Privilege scripts/deploy-to-target.sh reaching fleet or alpha-tester nodes high mitigate Task 1 requires --frontend-only targeted at the dev pair, explicitly forbids --tailscale and --tailscale-node, forbids any OTA or release path, and requires the exact command and host list to be recorded in the SUMMARY for audit
T-02-06 Information Disclosure 02-PERF-AFTER.json and 02-FINDINGS.md committed to a pushed repo medium mitigate Task 2 carries the same redaction rule as plan 02-01: RPC method names and timings only, no addresses, identities, balances or file names
T-02-22 Repudiation An unverified build shipped and later believed to contain this phase's changes medium mitigate Task 1 greps the built bundle for a representative string from every plan in the phase before deploying, per the CLAUDE.md silent-no-op warning, and Task 2's precondition re-greps the asset actually served by the node
T-02-03 Denial of Service Instance-cache memory on low-power hardware medium mitigate Task 1 sets KEEP_ALIVE_MAX from an on-device memory reading and records the reading; Task 3 step 9 exercises extended use to surface any residual growth
T-02-SC Tampering npm/pip/cargo installs high mitigate No package-manager installs are in scope; this plan builds and deploys existing code. A task that finds it needs a new dependency stops and routes through the Package Legitimacy Gate with a blocking human checkpoint before installing
</threat_model>

<artifacts_this_phase_produces>

Artifacts this phase produces

Created or changed by this plan — new API, not drift:

  • .planning/phases/02-ui-performance/02-PERF-AFTER.json
  • .planning/phases/02-ui-performance/02-FINDINGS.md — gains ## Results and ## Outstanding
  • KEEP_ALIVE_MAX — value finalised from an on-device memory reading

Full phase inventory (for the source-grounding pass): neode-ui/e2e/perf/surfaces.ts (SURFACES, Surface), neode-ui/e2e/perf/measure.ts (measureSurface, SurfaceMeasurement, RpcCall), neode-ui/e2e/perf/surface-perf.spec.ts, neode-ui/src/views/dashboard/keepAliveRoutes.ts (shouldKeepAlive, KEEP_ALIVE_PATHS, KEEP_ALIVE_MAX), neode-ui/src/views/dashboard/DashboardRouterView.vue (isFullBleedRoute, wrapperClass, wrapperStyle), neode-ui/src/components/RefreshIndicator.vue, resources.clearAll(), TAB_ORDER (promoted to an export of useRouteTransitions.ts), neode-ui/src/composables/__tests__/useCachedResource.test.ts, neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts, neode-ui/src/views/dashboard/__tests__/keepAliveLifecycle.test.ts, neode-ui/src/views/__tests__/{secondaryScreenCache,meshTabCache,serverTabCache,homeTabCache,chatAiuiEmbed}.test.ts, neode-ui/src/stores/__tests__/resourcesClear.test.ts, .planning/phases/02-ui-performance/{02-FINDINGS.md,02-PERF-BASELINE.json,02-PERF-AFTER.json,02-AIUI-D14.md}, cache keys app-catalog, bitcoin.prune-status, app-details:<dataset>:<id> and the per-group Mesh, Server and Home keys recorded in their plan SUMMARYs, environment variable ARCHY_PERF_OUT, and the two AIUI embed query parameters named in 02-AIUI-D14.md. </artifacts_this_phase_produces>

<assumptions_and_flagged_items>

Assumptions & Flagged Items

Edge-coverage probe rows (spec-less fallback — all three unclassified/unresolved)

Requirement Probe status Disposition here
PERF-01 unclassified / unresolved FLAGGED, not auto-backstopped, not dropped. Resolved in substance by this plan's before/after comparison truths and by plan 02-01's measurement truths. The probe row itself stays unresolved and is surfaced for human review.
PERF-02 unclassified / unresolved FLAGGED. Resolved in substance by the D-11 pass-bar truths here and by the per-tab truths in plans 02-02, 02-04, 02-05, 02-06 and 02-07.
PERF-03 unclassified / unresolved FLAGGED. Resolved in substance by the secondary-screen pass-bar truth here and by the per-item cache truths in plan 02-03.

The extended-use stability truth is carried as a verification: backstop marker: it is a perceptual and long-running property that neither the unit suite nor a single harness run can confirm, so it abstains to human review rather than passing silently.

Carried assumptions this plan is the last chance to settle

  • FA-D (KEEP_ALIVE_MAX): carried at 6 through plans 02-02 to 02-07 on reasoning alone. Task 1 replaces the estimate with an on-device memory reading. If the reading is not taken, the assumption stays open and belongs in ## Outstanding.
  • FA-B (ContainerAppDetails.vue): plan 02-01 records the reachability verdict and plan 02-03 excludes the file. If the verdict is that it is dead code, note in ## Outstanding that removing it is a candidate for a follow-up cleanup, not part of this phase.
  • FA-E (AIUI source): if plan 02-07's Task 2 precondition halted, D-14 is incomplete and belongs in ## Outstanding as a blocked locked decision — not as a deferred one.
  • Scope gaps from plan 02-03: any secondary screen the findings named that plan 02-03 reported rather than converted is listed in ## Outstanding with its measured cause. </assumptions_and_flagged_items>
- `cd neode-ui && npm run type-check && npm run test && npm run build` all exit 0 - The built bundle contains this phase's new symbols - The deploy touched dev-pair hosts only, recorded verbatim in the SUMMARY - `02-PERF-AFTER.json` has the same row count as `02-PERF-BASELINE.json` - `02-FINDINGS.md` contains `## Results` and `## Outstanding` - The D-11 pass-bar checkpoint is approved on archi-dev-box

<success_criteria>

  • Every surface the findings named as slow has a committed after-number from the same instrument and the same target
  • Every registered main tab shows a surviving instance in the after-artifact's remount probe
  • Revisits to tabs and secondary screens on archi-dev-box show no spinner and no blank screen
  • Any regression or unmeasured surface is recorded as such, not smoothed over
  • The instance-cache cap is justified by an observed memory reading
  • The build reached the dev pair and nothing else </success_criteria>
Create `.planning/phases/02-ui-performance/02-08-SUMMARY.md` when done. It MUST record: the exact deploy command and every host it touched; the on-device memory reading and the final `KEEP_ALIVE_MAX`; the per-surface before/after verdicts; the full `## Outstanding` list; and the human verdict on the D-11 pass bar in the user's own words.