Skip to content

feat: navigation UX — prefetch, pending, redirect()#9

Merged
attitude merged 1 commit into
mainfrom
feat/router-ux
Jul 6, 2026
Merged

feat: navigation UX — prefetch, pending, redirect()#9
attitude merged 1 commit into
mainfrom
feat/router-ux

Conversation

@attitude

@attitude attitude commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

RSC-router polish for Flight navigation:

  • Prefetch on hover (flight.ts): a Map<string, Promise<unknown>> cache. A delegated mouseover listener on <a data-flight-link> (same-origin only) warms the Flight fetch before the click lands; navigate() awaits the cached promise when present, else fetches normally, and clears the entry after use so the next hover re-fetches fresh data.
  • Pending indicator (flight.ts + todo.css): document.documentElement gets data-flight-pending="1" around a navigation (removed in a finally). streamNavigate() sets it at the start and clears it once the shell/first row is rendered — the point the user perceives the transition as done — rather than waiting for every boundary to resolve. todo.css renders it as a slim 3px indeterminate top bar via a :root[data-flight-pending]::before pseudo-element (no extra markup), using --accent and theme-aware by inheriting existing CSS variables.
  • redirect() helper (src/functions.php): redirect(string $url, int $status = 303): never, usable inside a server action. On a Flight/JSON request (Flight::wants() or Content-Type: application/json) it emits {"__redirect": "..."} as JSON; otherwise it sends a real Location header + status. The todo island's callAction() in TodoApp.tsx now checks the action response for __redirect and does a hard window.location.href navigation if present, leaving the normal { todos } path untouched.
  • Docs (docs/docs/navigation-ux.md, sidebar_position: 13): bridges PHP/React devs on prefetch, pending state, and redirect(), with the redirect() usage snippet from the task and a note that both prefetch and pending are progressive (no effect without JS).

No existing exports, functions, or routes were removed or altered; only additive changes to src/functions.php, flight.ts, TodoApp.tsx, todo.css, and the new docs page.

Verification

  • composer test — 39 tests, 94 assertions, all pass (1 pre-existing skip, unrelated to this change).
  • composer analyse (phpstan level 5) — no errors.
  • pnpm --dir examples/todo buildtsc --noEmit && vite build clean; public/dist/.vite/manifest.json still maps src/client/main.tsx.
  • Smoke test via php -S 127.0.0.1:8087 -t examples/todo/public examples/todo/public/router.php:
    • curl -s http://127.0.0.1:8087/ contains data-client="TodoApp", Loading todos…, template data-b=.
    • curl -s -H 'X-Flight: 1' http://127.0.0.1:8087/stats returns the serialized Flight JSON tree.
  • pnpm --dir docs build — exits 0, static build generated successfully.

Test plan

  • composer test passes
  • composer analyse clean
  • pnpm --dir examples/todo build clean (tsc strict + vite)
  • Manifest still references src/client/main.tsx
  • Smoke-test greps for data-client="TodoApp", Loading todos…, template data-b= still pass
  • Flight JSON endpoint (X-Flight: 1) still responds correctly
  • pnpm --dir docs build exits 0

Adds hover prefetch and a top pending bar to Flight navigation, plus a
redirect() helper for server actions that responds with a JSON envelope
on Flight/JSON requests and a real HTTP redirect otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@attitude attitude merged commit c4a7908 into main Jul 6, 2026
3 checks passed
@attitude attitude deleted the feat/router-ux branch July 6, 2026 23:43
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