- prefetch route-tab links so tab clicks reuse a cached payload instead
of paying an uncached server round trip each time
- add loading.tsx boundaries (dashboard root and each section) so clicks
paint within a frame; section-level files keep the header and tabs
mounted while only the content area swaps
- link the sidebar Account item and the checkout page straight to
/dashboard/account/billing. The /dashboard/account redirect stub stays
for old links, but no internal link pays the extra hop anymore. The
stub also dropped query params, which silently ate the plan-change
success toast.
- set QueryClient defaults (staleTime 60s, no focus refetch, retry 1);
mutations already invalidate their keys, so the user's own changes
stay instant. Device messages and webhook deliveries get a 15s
staleTime since they change from outside the tab.
- replace the axios getCachedSession TTL cache with a token seeded from
the server session in Providers and kept in sync by a session bridge.
Requests attach the token synchronously; /api/auth/session is only a
deduped fallback, instead of a refetch every 2 minutes with a
thundering herd on expiry.
- swap the billing card's 16px loading spinner for a card-shaped
skeleton so the tab no longer looks blank while loading
Tests: interceptor seeding/dedupe/signed-out behavior, provider
defaults and token seeding, nav active-state matching, tab prefetch,
billing loading state, plus e2e coverage for direct-to-billing
navigation and an at-most-one-session-call budget guard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new shared RouteTabs primitive (components/shared/route-tabs.tsx): a
link-based segmented control; tabs are real routes so the active tab
survives refresh and deep links are shareable; aria-current on the active
link and the active pill scrolls into view on mobile
- messaging/layout.tsx renders the section header + tabs; views become
subroutes: / (Send, focused composer), /bulk, /history (now full width,
previously cramped in a half column), /api-guide (the 419-line guide gets
its own URL instead of half the page)
- delete the obsolete client-side tab wrapper (components)/messaging.tsx and
dead main-dashboard.tsx
- messaging e2e rewritten for route tabs, including a direct-load test of
/dashboard/messaging/history as the refresh-survival guarantee
Build, 25 unit tests, and 12 e2e green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>