Skip to content

refactor(routes): inline single-URL pages + clientLoader prefetch#123

Merged
hyldmo merged 8 commits into
mainfrom
hyldmo/route-shim-explanation
May 6, 2026
Merged

refactor(routes): inline single-URL pages + clientLoader prefetch#123
hyldmo merged 8 commits into
mainfrom
hyldmo/route-shim-explanation

Conversation

@hyldmo
Copy link
Copy Markdown
Owner

@hyldmo hyldmo commented May 6, 2026

Summary

Two intertwined changes on the same route files:

  • Route consolidation — pages with one URL move from src/features/*Page.tsx into src/routes/*.tsx as the default export. Multi-URL pages (RecipeEditor, ExerciseDetail, WorkoutTemplate, WorkoutSession, ProgramEditor) stay in features/ and are re-exported by the route files. TimerModeTimerModeView (presentational, consumed by both the timer route and the landing AutoSection).
  • clientLoader prefetch — every data-fetching route exports a typed clientLoader that fires its tRPC queries in parallel with bundle download. New ~/lib/loader.ts helper centralizes the pattern for an eventual tRPC v12 migration.

Plus two preliminary chore: commits — yarn generate parallelizes codegen (typegen + wrangler types + pwa-icons), and public/self-heal.js is mechanically modernized to arrow syntax.

Why these are bundled

The clientLoader work and the route inlining touch the same files. Splitting them would require git add -p chunk-by-chunk on every route file. Commit history separates the codegen and self-heal chores so the route diff stays focused.

clientLoader design notes

  • Singletons in ~/lib/trpcqueryClient, trpcClient, trpcUtils (via createTRPCQueryUtils) replace the per-render useState-bound client. Loaders need module-level access.
  • IDB hydrationpersistQueryClientRestore is awaited before any ensureData call via the idbReady promise. Cold boots no longer fire network requests before the persisted cache restores. persistQueryClientSubscribe runs fire-and-forget for ongoing persistence.
  • .ensureData() over .prefetch() — propagates errors instead of swallowing, no-ops on warm cache.
  • Promise.allSettled + UNAUTHORIZED swallow — composite pages don't cancel sibling queries on one failure; signed-out deep links render the SignedOut branch instead of tripping ErrorBoundary. Other errors propagate to the route's ErrorBoundary (an improvement over today's silent .prefetch() swallowing).
  • Dropped refetchOnMount: 'always'staleTime + mutation invalidations already cover freshness; 'always' was forcing redundant refetches on every navigation that defeated the loader cache hit.

Routes audited

All 17 data-fetching routes covered. recipes.new and workouts.sessions.$sessionId.timer skipped (no fetch). For composite pages (analytics, plans, settings, workouts._index) the loader mirrors every unconditional useQuery at mount, including those nested inside child components like MuscleHeatGrid and ProfileForm. Component-state-driven queries (search inputs, dropdown windows on detail pages) are NOT prefetched — they refetch as today.

Test plan

  • yarn check — lint + typecheck + test all pass
  • yarn build — SPA prerender produces workers/dist/client/index.html
  • Hard-refresh /recipes/<id> (cold cache) — tRPC batch fires before component mount, no spinner flash
  • Navigate //analytics (warm cache) — 6 analytics queries batch in parallel with bundle, all 6 cards populate without per-card spinners
  • Sign out, navigate to /LandingPage renders, no console error, no ErrorBoundary trip
  • Disconnect network, hard-refresh /recipes — IDB cache restores, page renders with stale data
  • Navigate to /workouts (signed in) — HydrateFallback shows briefly, page renders with all 5 queries' data ready

🤖 Generated with Claude Code

hyldmo and others added 5 commits May 6, 2026 13:16
Combine react-router typegen, wrangler types, and pwa-icons into a single
parallel `yarn generate` step. Drops the bespoke `pwa:generate-icons` and
`codegen` scripts. CI now runs `yarn generate` instead of just icons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mechanical refactor — function expressions to arrow functions, string
concatenation to template literals. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two intertwined changes (touching the same route files, so committed together):

**Route consolidation.** Pages with one URL move from src/features/*Page.tsx
into src/routes/*.tsx as their default export. Pages shared across multiple
URLs (RecipeEditor, ExerciseDetail, WorkoutTemplate, WorkoutSession,
ProgramEditor) stay in features/ and are re-exported from the route files.
TimerMode → TimerModeView (presentational, consumed by both the timer route
and the landing AutoSection).

**clientLoader prefetch.** Every data-fetching route exports a typed
clientLoader that fires its tRPC queries in parallel with bundle download
via a new ~/lib/loader prefetchRoute helper.

- Module-level singletons in ~/lib/trpc — queryClient, trpcClient, trpcUtils
  (createTRPCQueryUtils) — replace the per-render useState client.
- IDB rehydration via persistQueryClientRestore awaited inside prefetchRoute,
  with persistQueryClientSubscribe wired up for ongoing persistence. Cold
  boots no longer fire network requests before the persisted cache restores.
- ensureData() (not prefetch) for correct error semantics and warm-cache
  no-ops. Promise.allSettled so one query failing doesn't cancel siblings;
  TRPCClientError UNAUTHORIZED is swallowed so signed-out deep links render
  the SignedOut branch instead of tripping ErrorBoundary.
- Drops refetchOnMount: 'always' — staleTime + mutation invalidations cover
  freshness; 'always' was forcing redundant refetches that defeated the
  loader cache hit.

CLAUDE.md updated to reflect the new routes/ ↔ features/ split.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI doesn't have .dev.vars, so running `wrangler types` there overwrites the
committed worker-configuration.d.ts with an empty Env type and breaks
typecheck (15 errors in ai/ingredients/settings routes).

worker-configuration.d.ts is committed and CI types depend on it (per
CLAUDE.md). Restore the previous behavior: wrangler types is a manual
`yarn codegen` for when wrangler.toml changes; not part of the parallel
generate that runs in CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "worker-configuration.d.ts is committed" gotcha didn't make it explicit
that wrangler types must NEVER run in CI. The previous PR shipped a
`generate:wrangler` inside `yarn generate` (which runs in CI typecheck),
silently breaking every route that reads a secret. Strengthen the rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Preview deployment

URL https://hyldmo-route-shim-explanatio.macromaxxing.pages.dev
Branch hyldmo/route-shim-explanation
Commit 9471112

hyldmo and others added 3 commits May 6, 2026 13:32
Restores wrangler types as part of the parallel `yarn generate`, but
short-circuits with `test -f .dev.vars && ... || true` so it only runs
where the secrets are present. Local yarn check / yarn build now refresh
worker-configuration.d.ts automatically; CI silently skips the step
instead of overwriting the committed file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wrangler 4.30+ added --env-file (workers-sdk#11038) which makes wrangler
types deterministic across machines: point it at a committed template that
lists every secret key with placeholder values, and the generated Env is
identical locally and in CI regardless of which secrets the developer has
in .dev.vars.

Switch generate:wrangler to use the template, gitignore the generated
file, and untrack the previously-committed copy. Local yarn check / build
regenerate it from scratch; CI does the same with no extra setup.

Bonus: workers/.dev.vars.template doubles as documentation of which
secrets a contributor needs to provide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spent a chunk of the previous PR debugging why
\`yarn workspace @macromaxxing/workers wrangler types --env-file ...\`
failed with "node: <path>: not found" even though the same args worked
when invoking wrangler directly. Node 24 parses --env-file before the
script, eats the path, then can't find it. Wrapping the call in a
workspace script avoids the collision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyldmo hyldmo merged commit a93b7a3 into main May 6, 2026
3 checks passed
@hyldmo hyldmo deleted the hyldmo/route-shim-explanation branch May 6, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant