Repository navigation
Transaction improvements - #4
Merged
Merged
Conversation
…treaming Implements Next.js-like loading() support for route definitions. When a route defines a loading component, async handlers are wrapped in React Suspense with the loading component as the fallback, enabling RSC streaming. Key changes: - Added loading() builder method to route definitions (route-definition.ts) - Modified router to wrap async handlers in Suspense when loading is defined (router.ts) - OutletProvider passes loading component to context (outlet-context.ts) - Outlet component wraps content in Suspense when loading exists (client.tsx) - Added ProductDetailSkeleton example in shop demo The loading skeleton appears immediately while the route content streams in, providing a better user experience for slow async operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Merges origin/main which adds errorBoundary() and notFoundBoundary() helpers into this branch which has loading() support for RSC streaming. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Added pruneSegments() method to NavigationStore that removes segments not in the current page's matched list. Called after both partial and full updates to ensure only current page segments are retained. Previously, storedSegments Map accumulated indefinitely across navigations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Navigation transaction now captures segmentIds, currentUrl, and path at start and restores them on failure. storedSegments is not rolled back as it's just a cache. This ensures consistency between URL and segment state when navigation fails after segment updates but before commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
ivogt
added a commit
that referenced
this pull request
Nov 30, 2025
* feat: add loading() support for route definitions with RSC Suspense streaming Implements Next.js-like loading() support for route definitions. When a route defines a loading component, async handlers are wrapped in React Suspense with the loading component as the fallback, enabling RSC streaming. Key changes: - Added loading() builder method to route definitions (route-definition.ts) - Modified router to wrap async handlers in Suspense when loading is defined (router.ts) - OutletProvider passes loading component to context (outlet-context.ts) - Outlet component wraps content in Suspense when loading exists (client.tsx) - Added ProductDetailSkeleton example in shop demo The loading skeleton appears immediately while the route content streams in, providing a better user experience for slow async operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fixed streaming with loading component * updates and fixes pheeew * fix: prune old segments after navigation to prevent memory leaks Added pruneSegments() method to NavigationStore that removes segments not in the current page's matched list. Called after both partial and full updates to ensure only current page segments are retained. Previously, storedSegments Map accumulated indefinitely across navigations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * feat: add preconnect optimization on user activity to improve navigation speed * fix: add segment state rollback to navigation transaction Navigation transaction now captures segmentIds, currentUrl, and path at start and restores them on failure. storedSegments is not rolled back as it's just a cache. This ensures consistency between URL and segment state when navigation fails after segment updates but before commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
2 tasks done
ivogt
added a commit
that referenced
this pull request
Jun 13, 2026
… decisions, #570 cleanup (#573) * test(router): dual-mode e2e for prerender+loading()+passthrough (M16) and redirect under loading() (M9) Adds the dev + production e2e fixtures the M16/M9 unit-tested fixes still lacked (CLAUDE.md mandates dual-mode coverage). Deferred from the stabilization PR; addresses the two main gaps in #567. - prerender-loading: Prerender + loading() + ctx.passthrough(). The loading() boundary defers the build handler so the passthrough sentinel arrives Promise-wrapped; detectPrerenderPassthrough awaits it. getParams lists [baked, skip]: baked is prerendered, skip passes through to the live handler. Production asserts the manifest holds baked but NOT skip (M16 regression guard: count === 1; without the fix skip would bake a corrupt artifact). - loading-redirect: a synchronous redirect() return under loading() short-circuits to a real HTTP redirect. Verified for hard + soft navigation in both modes. Regenerated router.named-routes.gen.ts for the two new includes (urls.gen.ts is legacy/unreferenced and no longer auto-generated, so left untouched). Lower-priority #567 gaps (build-time intercept _currentSegmentId handle drop, server-action-bridge pre-dispatch over-invalidation, document-cache private+s-maxage) need fixes first and are deferred; #567 stays open for them. * chore(router): safe cleanup batch from review (#570) Internal-only, behavior-preserving cleanups; grep-verified safe and gate-green (typecheck, full unit suite, lint, format). - Remove write-only MiddlewareEntry.mountPrefix (read nowhere; scoping folds into fullPattern before the entry is built). Dropped 3 production assignments plus the test fixtures. - Dedupe cookie helpers: delete the test-only middleware-cookies.ts (byte-for-byte dup of request-context's parseCookiesFromHeader/serializeCookieValue), export the canonical copies from request-context for the unit tests, drop the middleware.ts re-export. - Delete the zero-importer src/handles/index.ts barrel (Meta/MetaTags/Breadcrumbs are exported directly from the client/index entries); repoint collect-handle.test. - Drop the MiddlewareCollectableEntry re-export from middleware.ts; its one importer now imports from middleware-types. - Extract hasContextVars() into context-var.ts and use it in the two prerender entry points that duplicated the keys-or-symbols predicate. - renderStaticSegment: throw a TypeError when a Static handler returns a Response (was silently serialized as a corrupt build artifact); the fresh/revalidation paths already throw via handleHandlerResult. Adds a unit test. - ctx.reverse() middleware fallback: widen the signature to (name, params?, search?) to match ScopedReverseFunction and make the error message accurate. - docs: tree-structure.md inTransitionScope also covers error/notFound segments. Deferred, still open in #570: the optionalParams all-or-nothing removal and the loaderPromises param removal both sit on matrix-critical matching/segment-resolution paths and warrant a semantic-matrix run rather than a cleanup sweep. The document-cache "timing-gap caveat" cited at document-cache.ts:45-53 does not exist there (those lines are Cache-Control parsing), so that doc item is a no-op. * feat(router): pre-1.0 public-API surface decisions from review (#569 part 1) Type/naming/export-surface decisions (no behavior change). See #569. - #1 router.use(pattern, mw): add a pattern-generic overload so `.use("/users/:id", (ctx) => ...)` types `ctx.params.id` via ExtractParams. Bare `.use(mw)` stays generic. Added on both Rango and RangoInternal. Pins it with a type-level test (router-use-types.test.ts). - #2 useAction: export a stable public `ActionState` (and `ActionLifecycleState`) from ./client so consumers can name the hook's return type; TrackedActionState stays the @internal impl name (ActionState aliases it). - #4 buildEnv: move off the shared base onto per-preset interfaces. Cloudflare keeps `"auto"` (wrangler getPlatformProxy); Node now rejects `"auto"` at the type level (Exclude<BuildEnvOption,"auto">) instead of only at build time. - #5 poke plugin: rename name "vite-plugin-poke" -> "@rangojs/router:poke" so it matches the @rangojs/router:* family for consumers ordering/filtering by name. - #6 ./build: move realm-crossing pipeline internals (buildRouteTrie, buildPerRouterTrie, TrieNode, TrieLeaf, collectFallbackClientRefs) to a new ./build/internal subpath; ./build keeps the documented public API (generateManifest*, route-type generation, createScanFilter, hashParams). - #8 mergeHandlerUse: document the intentional validation asymmetry (explicit use() items bypass the mount-site allow-list); chosen over enforcing it. Deferred to part 2 (behavior changes): #3 prefetch options, #7 scanFilter wiring, #9 profile-registry trim, #10 redirect-in-action idiom + examples. * feat(router): prefetch source-scope option + redirect-in-action ergonomics (#569 part 2a) - #3 router.prefetch(url, { key: ":source" }): add an options arg so imperative prefetch can source-scope its cache entry, at parity with <Link prefetchKey=":source">. Wired through to prefetchDirect. - #10 redirect() in actions: drop the `as any` on non-form actions by typing them Promise<Response> (saveAndRedirect, actionSimpleRedirect). The form-bound peReturnRedirect keeps a documented intrinsic cast (React's form-action type is () => void | Promise<void>, which can't express a Response return); consumers should prefer `throw redirect(...)`. Strengthened the server-actions skill. Deferred to part 2b: #7 (complete scanFilter — needs a new public plugin option) and #9 (trim profile-registry — runtime cache-resolution refactor). * feat(router): complete scanFilter discovery option + trim profile registry (#569 part 2b) - #7 complete the scanFilter feature. Adds a public rango() option `discovery: { include, exclude }` (glob, root-relative; mirrors the CLI's --include/--exclude). The consume side was already wired (findRouterFiles(state.projectRoot, state.scanFilter) at 3 sites); this threads the option through PluginOptions and compiles it via createScanFilter into state.scanFilter in configResolved, once projectRoot is known. Filters which files route discovery / static type-gen scan. Tests in generate-route-types.test.ts (include restricts, exclude removes). - #9 trim the profile registry. Removed the global mutable `_profiles`, `setCacheProfiles`, and `getCacheProfile` — all dead: `getCacheProfile` had no production reader (its own JSDoc said so), and `setCacheProfiles` only populated the unread global. Kept the load-bearing `resolveCacheProfiles`, which createRouter() already uses to thread a per-request profile map (the "use cache: <profile>" path reads it request-scoped via ctx._cacheProfiles). Dropped the router.ts setCacheProfiles() call; repointed the tests to resolveCacheProfiles (and reframed the "global replace" test as per-router map independence). #569 is now fully implemented (all 10 decisions). * fix(router): keep build internals on ./build (revert #569.6) + throw in return-form redirect actions (#569.10) CI on #573 caught a regression from #569 part 1: the Vite discovery pipeline imports buildRouteTrie / buildPerRouterTrie / collectFallbackClientRefs at build time via runner.import("@rangojs/router/build") (discover-routers.ts), and the RSC runner does not resolve a ./build/internal subpath in that realm. Moving them left collectFallbackClientRefs undefined, so the router-level default error / notFound boundary was never collected into the app-fallback chunk (mini.test.ts clientChunks: "mini-default-error must be in app-fallback"). Revert #569.6: restore buildRouteTrie/buildPerRouterTrie/collectFallbackClientRefs + TrieNode/TrieLeaf on ./build; delete ./build/internal + its package.json export. #569.6 is re-deferred — internalizing these needs runner subpath resolution or a different seam (noted in build/index.ts). #569.10 revision: per owner, the return-form redirect test actions (saveAndRedirect, actionSimpleRedirect) now `throw redirect()` instead of being typed Promise<Response>. The form-bound peReturnRedirect keeps its documented intrinsic cast. Verified: mini clientChunks 6/6, location-state e2e 47/47 (dev+prod), typecheck, 3866 unit, lint, format.
9 tasks done
9 tasks done
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.
No description provided.