Files

8.0 KiB

Phase 2: UI Performance - Context

Gathered: 2026-07-30 Status: Ready for planning

## Phase Boundary

Make the existing neode-ui Vue SPA feel fast: main-tab switches render immediately from cached state with background refresh (PERF-02), secondary screens open without blocking reloads and are instant on repeat visits (PERF-03), and every fix is preceded by profiling that names the measured cause (PERF-01). Verified on real node hardware (archi-dev-box), not just the dev workstation. Plus two folded-in AIUI UX defaults (see D-14) — no other AIUI/content-federation work belongs here.

## Implementation Decisions

Caching Approach (PERF-02)

  • D-01: Main tabs use BOTH <KeepAlive> around the RouterView (component instances, scroll position, and in-page state survive tab switches) AND useCachedResource stale-while-revalidate for their data. Today no <KeepAlive> exists anywhere — every tab switch fully remounts the view.
  • D-02: Which views get converted is decided by profiling (D-09), not blanket conversion — "fixes are targeted, not guessed" (PERF-01). Already-fast views are left alone.
  • D-03: Heavy views (Mesh with its D3 graph + Leaflet map) ARE kept alive, but KeepAlive's max instance cap is set so the oldest unused views evict — bounded memory on low-power nodes.
  • D-04: Secondary screens (AppDetails, server sub-pages, etc.) get useCachedResource keyed per item (e.g. app-details:${appId}) but NO KeepAlive — unbounded item counts would bloat an instance cache. Repeat opens paint instantly from the data cache.

Refresh / Staleness UX

  • D-05: Background refresh on a cached view shows a SUBTLE indicator (small spinner/shimmer in the header/tab area driven by loadState === 'refreshing') — not invisible, not stale-age badges.
  • D-06: Default TTL 30s (the hook's default); per-view tuning is Claude's discretion (shorter for fast-moving data like mesh peers/sync status, longer for near-static data like the app catalog).
  • D-07: Background refresh failures are silent keep-last-value — cached data stays on screen, retry on next focus/TTL; no toast. Errors surface only on explicit user refresh.
  • D-08: sessionStorage persistence ON (instant paint after reload) except for large payloads (file lists, media), which stay memory-only — the hook's persist: false.

Profiling & Verification (PERF-01, PERF-03)

  • D-09: Profiling starting set = ALL main surfaces: Apps/app store (+ AppDetails), Mesh, Wallet (+ send flows), Cloud/Files, Server, Network, and Web5. User confirmed all of these feel slow, "often app store".
  • D-10: Profiling produces a COMMITTED findings doc in the phase directory: each slow surface → measured cause (remount storm / serial RPC waterfall / uncached fetch) → intended fix. Written and committed BEFORE fixes land.
  • D-11: On-device verification target is archi-dev-box. Pass bar: NO visible spinner/blank on revisit of a tab or secondary screen already visited this session; first visits may still show loading.

Fix Scope

  • D-12: Backend (core/) changes are ADDITIVE ONLY: new aggregate/batch RPC endpoints and cheap response-shaping are allowed when profiling names a backend cause; NO refactors of existing handlers and NOTHING touching the orchestrator (keeps the lifecycle gate out of play).
  • D-13: Serial fetch waterfalls in views are fixed client-side by parallelizing (Promise.all) plus rpc-client request dedup. Aggregate endpoints (per D-12) only where a screen genuinely needs 3+ dependent calls.
  • D-14: Folded-in AIUI UX defaults (small, in-scope ride-alongs): (a) the AIUI chat starts EXPANDED, (b) on mobile AIUI starts on the CHAT view, not the context view (current start-on-context is wrong). Everything else AIUI-related is deferred (see Deferred Ideas).
  • D-15: Deploy discipline: dev pair only this phase — no OTA. Fleet rollout rides the normal release train later.

Claude's Discretion

  • Per-view TTL values (D-06).
  • KeepAlive max cap value and eviction tuning (D-03).
  • Choice between client-side parallelization and a new aggregate endpoint per screen, within D-12/D-13 bounds.
  • Exact placement/styling of the subtle refresh indicator (D-05), consistent with the existing design system.

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

Frontend caching / data layer

  • neode-ui/src/composables/useCachedResource.ts — The existing SWR hook (memory → sessionStorage → fetch, sticky-ready, refreshing state, focus revalidation, abort-on-unmount). The pattern to EXTEND, not reinvent.
  • neode-ui/src/stores/resources.ts — Shared resource store backing the hook.
  • neode-ui/src/api/rpc-client.ts — RPC client (retry/backoff, dedup option, abort signals) — waterfall fixes build on its dedup + signal support.
  • neode-ui/src/App.vue — Root RouterView (line ~8) where KeepAlive wrapping lands; note the splash/isReady gating around first reveal.

Project-level

  • .planning/codebase/CONVENTIONS.md — Vue/TS conventions (script setup, composable patterns, store patterns).
  • .planning/codebase/STRUCTURE.md — Where views/components/stores/composables live.
  • CLAUDE.md — Commit/push discipline, build notes (grep built bundle before shipping), invariants.

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • useCachedResource + resources store: production-quality SWR already used by 8 views (Monitoring, Cloud, Server, Federation, Credentials, Web5, FIPS cards) — main tabs just don't use it yet.
  • rpc-client.ts dedup: true option: shares in-flight requests across concurrent callers — key for waterfall/parallelization fixes.

Established Patterns

  • 44 routes are already lazy-loaded (import() in router/index.ts) — route-level code splitting is NOT the problem.
  • Views fire multiple onMounted fetches (Cloud 4, Server/Mesh/ContainerAppDetails 3…) — the likely refetch-on-remount storm PERF-01 will quantify.
  • No <KeepAlive> anywhere in the codebase today.

Integration Points

  • App.vue RouterView is the single mount point for KeepAlive.
  • Pinia stores (stores/*.ts) hold global state; per-view fetch logic migrates into useCachedResource keys rather than new ad-hoc stores.

</code_context>

## Specific Ideas
  • UI changes verified on the :8100 dev preview against archi-dev BEFORE deploy (carried forward from Phase 1 discipline).
  • Frontend build gotcha (CLAUDE.md): grep web/dist/neode-ui for new strings after npm run build — the build can silently no-op.
  • Pass bar phrasing from the user: revisited surfaces paint content immediately; the sluggishness reported on-device must be gone on archi-dev-box.
## Deferred Ideas

Captured verbatim from the user during this discussion — new capabilities that belong in their own phase (candidate: "AIUI & Content Federation"; add via /gsd-phase add):

  • AIUI permissioned node access: the AI UI talks to the node safely when permissioned, without leaking data, using the same command surface as Pine and everything else enableable in settings.
  • AIUI content deep-dive: deep dive into the content AIUI finds and serves — "make it perfect".
  • AIUI peer media: show peer videos and files in AIUI.
  • IndeeHub cross-node content source with payments: peer files/videos made available on IndeeHub by other nodes for films — an "archipelago content source" so any IndeeHub install plugs into every node's uploaded/served IndeeHub content, with payments; same for music files and everything else.
  • AIUI Nostr integration polish: make the Nostr integration in AIUI more beautiful.

(The two small AIUI UX defaults — chat starts expanded; mobile starts on chat — were folded INTO this phase as D-14 and are not deferred.)


Phase: 2-UI Performance Context gathered: 2026-07-30