171 lines
8.0 KiB
Markdown
171 lines
8.0 KiB
Markdown
# Phase 2: UI Performance - Context
|
|||
|
|
|
||
|
|
**Gathered:** 2026-07-30
|
||
|
|
**Status:** Ready for planning
|
||
|
|
|
||
|
|
<domain>
|
||
|
|
## 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.
|
||
|
|
|
||
|
|
</domain>
|
||
|
|
|
||
|
|
<decisions>
|
||
|
|
## 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.
|
||
|
|
|
||
|
|
</decisions>
|
||
|
|
|
||
|
|
<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>
|
||
|
|
|
||
|
|
<specifics>
|
||
|
|
## 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.
|
||
|
|
|
||
|
|
</specifics>
|
||
|
|
|
||
|
|
<deferred>
|
||
|
|
## 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.)
|
||
|
|
|
||
|
|
</deferred>
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Phase: 2-UI Performance*
|
||
|
|
*Context gathered: 2026-07-30*
|