# Audit and optimize web performance ## Goal Comprehensively audit and refactor the web frontend data interaction layer while preserving the existing UI. The refactor should reduce repeated HTTP requests, hide US-backend latency where safe, centralize cache/invalidation behavior, and make authenticated page transitions feel faster in both perceived and actual request-count terms. ## What I already know * Backend is hosted in the US, so users far away experience noticeable request latency. * User has observed repeated HTTP requests and avoidable data-loading waits. * Web stack is Astro 6 + React 19 + React Router DOM under `web/`. * Auth state lives in `web/src/lib/auth.ts`; business APIs live in `web/src/lib/api.ts`. * Existing `getPointsBalance()` has a 1-minute in-memory TTL cache, but many other stable reads do not. * Early code scan found repeated `getUserProfile()` calls across `AppShell`, `LoginForm`, `SettingsPage`, `GeneralSettingsPage`, and `ProfileDetailPage`. * `AppShell` already loads profile globally and exposes `UserSettingsContext`; some pages still refetch profile instead of using that context. * Auth refresh now has single-flight behavior from the previous fix, which helps avoid concurrent refresh waste. ## Assumptions * Performance improvements should preserve backend source of truth and auth safety. * Cached authenticated data must be invalidated after writes that change it. * We should prefer a local, typed data-client/resource layer over adding a large state/query dependency unless audit shows the custom approach is too risky. * Optimizations should be observable through request count reduction and improved perceived loading behavior. ## Requirements * Audit all authenticated web pages for duplicate or avoidable requests. * Define and implement a frontend data layer with caching, in-flight dedupe, stale-while-revalidate, prefetch, and explicit invalidation. * Reuse AppShell-loaded profile/settings where possible instead of refetching. * Add cache invalidation for profile/settings/points changes. * Keep UI presentation unchanged at rest; only data source and loading/refresh behavior should change. * Avoid stale or unsafe auth behavior: 401 and refresh failures must still clear auth and redirect. * Improve perceived performance with cached data, route-level reuse, and fewer full-page loading waits. * Document verification with before/after request counts for key flows. ## Acceptance Criteria * [x] Request audit lists endpoints, call sites, duplicate patterns, and priority. * [x] Refactor plan defines target data architecture, resource cache policies, invalidation rules, and phased rollout. * [x] Data client/resource layer is implemented without changing the visible UI design. * [x] Dashboard → settings/profile/general/divination navigation avoids duplicate profile requests where safe. * [x] Points balance requests are deduped in-flight and cached/invalidated intentionally. * [x] Stable package/config-style reads are cached or intentionally left uncached with rationale. * [x] Authenticated pages keep correct behavior after writes and after 401 responses. * [ ] Browser verification captures representative flows under mobile and desktop viewports. * [x] `git diff --check` passes; build/typecheck status documented. ## Definition of Done * PRD updated with final implementation notes. * Targeted code changes committed. * Performance audit and refactor plan recorded in the task directory. * Any reusable conventions captured in `.trellis/spec/web/index.md` or relevant spec. ## Out of Scope * Backend endpoint redesign. * CDN/edge deployment changes. * Offline mode. * Large dependency adoption unless explicitly approved after audit. * Visual redesign of existing pages. ## Technical Notes * Follow `.trellis/spec/web/index.md`. * All API paths remain in `web/src/lib/api-routes.ts`. * Components should call typed API helpers from `web/src/lib/api.ts`, not inline `fetch('/api/...')`. * Target refactor plan: `.trellis/tasks/05-10-audit-and-optimize-web-performance/refactor-plan.md`. * Request audit: `.trellis/tasks/05-10-audit-and-optimize-web-performance/request-audit.md`. * Current build is blocked by existing Astro adapter configuration (`NoAdapterInstalled`) and should be documented unless fixed in a separate task. ## Implementation Notes - 2026-05-10 * Added `web/src/lib/data-client.ts`, a typed in-memory query cache with TTL, in-flight request dedupe, `peek`, `set`, prefix `invalidate`, `prefetch`, `subscribe`, and `clearAll`. * Added `web/src/lib/resources.ts` for profile, points, packages, history list/thread/summary, notifications list, and unread-count cache policy plus React resource hooks. * Moved points TTL behavior out of `api.ts`; `getPointsBalance()` is now transport-only and points caching lives in the resource layer. * AppShell now loads profile through the profile resource, subscribes to profile cache changes, clears data cache via `clearAuth()`, prefetches cheap dashboard data after auth, and prefetches route resources on nav hover/focus. * Settings, general settings, profile detail, dashboard, store, history list, notifications, manual/auto divination, divination processing, result, and follow-up pages now reuse resource caches instead of owning duplicate GET lifecycles. * Mutations patch or invalidate shared cache: profile/settings/avatar set profile; notification read/all-read patch list and unread count; divination/follow-up completion invalidates points and history. * Quality check fixed two post-implementation issues: invalidated active resources now refetch instead of staying empty/stale, and `useHistoryThread(undefined)` no longer emits a doomed request when result pages rely on router/session state. * Verification: `npm exec astro sync` passed; `git diff --check` passed; `npm run build` remains blocked by existing Astro `NoAdapterInstalled`; temporary `tsc --noEmit` check reports existing `DashboardApp.tsx` translation-map typing errors after refactor-specific type issues were fixed. * Browser verification note: the in-app browser was switched to a visible 390x844 mobile viewport with the dev server on `127.0.0.1:4322`, but the automation surface only exposed the in-app browser toolbar during this run, so request-count capture still needs a follow-up pass with a usable page automation surface.