feat: navigation UX — prefetch, pending, redirect()#9
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RSC-router polish for Flight navigation:
flight.ts): aMap<string, Promise<unknown>>cache. A delegatedmouseoverlistener 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.flight.ts+todo.css):document.documentElementgetsdata-flight-pending="1"around a navigation (removed in afinally).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.cssrenders it as a slim 3px indeterminate top bar via a:root[data-flight-pending]::beforepseudo-element (no extra markup), using--accentand 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()orContent-Type: application/json) it emits{"__redirect": "..."}as JSON; otherwise it sends a realLocationheader + status. The todo island'scallAction()inTodoApp.tsxnow checks the action response for__redirectand does a hardwindow.location.hrefnavigation if present, leaving the normal{ todos }path untouched.docs/docs/navigation-ux.md,sidebar_position: 13): bridges PHP/React devs on prefetch, pending state, andredirect(), with theredirect()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 build—tsc --noEmit && vite buildclean;public/dist/.vite/manifest.jsonstill mapssrc/client/main.tsx.php -S 127.0.0.1:8087 -t examples/todo/public examples/todo/public/router.php:curl -s http://127.0.0.1:8087/containsdata-client="TodoApp",Loading todos…,template data-b=.curl -s -H 'X-Flight: 1' http://127.0.0.1:8087/statsreturns the serialized Flight JSON tree.pnpm --dir docs build— exits 0, static build generated successfully.Test plan
composer testpassescomposer analysecleanpnpm --dir examples/todo buildclean (tsc strict + vite)src/client/main.tsxdata-client="TodoApp",Loading todos…,template data-b=still passX-Flight: 1) still responds correctlypnpm --dir docs buildexits 0