Repository navigation
refactor(router): one segment walker; fresh.ts deleted (S9) - #1244
Merged
Merged
Conversation
Delete segment-resolution/fresh.ts. A full match (a document, the background re-render of a stale route record, a build-time prerender) resolves through the revalidating walker with freshPassInput: `client` is FRESH, an empty ClientHeld compared by identity, so every decision short-circuits to "new" with no trace entry or decision event. What the full match did differently stays as explicit FRESH branches in revalidation.ts: synchronous loader kickoffs, awaitBeforeFlush registration, stamp and await, loading(false) settling, the loader error context without isPartial, slot and orphan handler metering and spans, the evicted-slot skip, slot loader order and parallelLoading tag, static hit metering and the void layout component. The build-time request getter, throwOnError and skipLoaders become pass fields (ResolutionPassInput). RouterContext.resolveAllSegments, resolveLoadersOnly and ResolveSegmentOptions are removed; every caller and test moves to the walker (kit makeFreshPass). walker-differential's exception list now names these branches, with unchanged expectations. Adds resolve-all.bench.ts for the document walk, and updates the internal maps, docs, skills and comments that named fresh.ts.
The merged walker awaited a non-function slot value when the slot has no loading(): a Promise-valued static slot (a React 19 ReactNode) was unwrapped during the walk, and a static JSX slot paid one extra microtask. Both walkers before the merge assigned the value as is (typeof handler === "function" ? await ... : handler); restore that on the FRESH and partial passes, and pin it with a unit test per pass.
The fresh walker's unused argument was the field's last reader. Remove the field, its two match-api.ts assignments (the local map still goes to setupLoaderAccess) and the four test fixtures that set it; the background re-render test now pins the new, empty map on the background handler context directly. Docs: replace names this step removed (resolveOrphanLayout, ResolveSegmentOptions.chain / .boundaryEntry / .throwOnError, the fresh path) in feature-map.md, feature-file-map.md and execution-model.md.
49 of 50 tasks
A full match through the one walker (S9) allocated 18% more per resolveAll than fresh.ts and ran about 10% slower on the resolve-all bench. Per chain entry it made an async call to resolveOwnerLoaders and to resolveParallelSegmentsWithRevalidation even when the entry owns no loader or slot, and resolveEntryHandlerWithRevalidation built its decide and run closures and an extra async layer on every call. - Call resolveOwnerLoaders only when the entry owns loaders (ownsLoaders), and resolveParallelSegmentsWithRevalidation only when it declares a slot (declaresSlot), in the chain, orphan and loader-only walks. - Hoist the entry and orphan revalidate() decisions to entryRevalidates and orphanRevalidates, and the route handler run to resolveRouteComponent; layouts run inline. FRESH skips the decision without allocating it. - Build a loader segment in one literal, share the awaitBeforeFlush stamp, and skip the settled-loader array when nothing is awaited. Segments, matched ids, traces and telemetry are unchanged; only the number of microtask hops on the way changes.
The segment resolveEntryHandlerWithRevalidation builds for every chain entry spread three conditional object literals (clientGroup, layoutName, mountPath) and the deferred flag; fresh.ts built a layout's with one. Each spread allocates a temporary and copies its properties: about 80 ns per segment against 12 ns assigned. Assign them after the fixed keys, in the same key order. Also drops the unused routeKey read in resolveOrphanLayoutWithRevalidation.
resolveOwnerLoaders built an object per loader ({ loaderEntry, segmentId,
index }) and mapped that array twice, once for the matched ids and once for
the segments. In a traced run of the resolve-all bench workload its
optimized code deoptimized twice on a wrong map at those array calls and
settled after about 2200 iterations (fresh.ts's resolveLoaders: once, about
1100); the bench's 100 ms warmup and 500 ms measurement cover roughly the
first 2000.
Build the segment ids once, have loadersToRerun return the indices of the
loaders that run, and emit the segments in one loop over the loaders (a
FRESH pass) or those indices. In the same trace, resolveOwnerLoaders
deoptimizes once and settles by about iteration 800. The partial lane asks,
traces and observes the same decisions in the same order.
Brings in S16 (WIRE), S5 (deriveRenderPass), S15 (discovery phases), S14 and S14b (request stages, dispatch), S10b (Vercel store), S6 (ShellCaptureState), #1246 and #1247. Conflicts: - src/server/request-context.ts: S6 removed `_shellCaptureRun`, whose doc comment S9 edited. Kept S6's `_shellCaptureState` field and doc. S9's fact (the capture switch is read by `resolveOwnerLoaders`, not `fresh.ts emitStreaming`) moved to `ShellCaptureState.run` in rsc/shell-capture.ts, which carried the old text. - docs/design/ppr-shell-resume.md: S6's `_shellCaptureState.run` with S9's `resolveOwnerLoaders` settle guard in place of `fresh.ts` `emitStreaming`. - docs/internal/feature-file-map.md: one table hunk (both sides realigned the columns). Each row taken from the side that changed it; the two rows both sides changed are combined: "PPR shell caching (axis 2)" keeps S6's field names and S9's revalidation.ts link in place of fresh.ts; "Internal unit-test kit" lists S6's makeShellCaptureState and S9's makeFreshPass. - docs/internal/feature-map.md: the transition() entry keeps S15's vite/discovery/dev-server-phase.ts and S9's `revalidation.ts` without `fresh.ts`. Semantic fix: - src/server/__tests__/render-pass.bench.tsx (S5): the background re-render's RouterContext stub gave `resolveAllSegments`, which S9 removed; rerenderAndCacheRoute now calls `resolveAllSegmentsWithRevalidation`, so the stub gives that. Without it the "background re-render (stub resolution)" bench reports NaN.
ivogt
added a commit
that referenced
this pull request
Oct 23, 2026
Brings in S10b (#1254), S6 (#1250), S9 (#1244) and S14b (#1252). Conflicts: docs/internal/feature-file-map.md and feature-map.md. Both keep reorg/stack's text plus this branch's two facts: the cache row's CFCacheStore sentence (readL1, readKv, the family descriptors, the read bench) and the "Cloudflare store family reads" bullet, placed after the Vercel store bullets.
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.
devcorner: rango-t138 · session rango-s312
Problem
The router resolved a matched chain's segments with two walkers:
segment-resolution/fresh.ts(945 lines) for a full match (a document, the background re-render of a stale route record, a build-time prerender) andsegment-resolution/revalidation.tsfor a partial match. They held parallel copies of the same orderings (entry loaders, handler first, parallels, orphan layouts, nested routeless layouts, inherited loaders, the cache() and ppr header latches, dedupe), so every fix to one had to be found and repeated in the other. This is step S9 of the internals reorganisation (plan rango-d195 section 3 S9, changed by section 7; target rango-d194 section 3.2). No public API or rendered output changes. Three types that the@rangojs/router/__internalsubpath type-exports (each marked@internal) change shape:RouterContextlosesresolveAllSegmentsandresolveLoadersOnly;ResolutionPassInput.requestbecomesRequest | undefined, and the type gainsskipLoadersandthrowOnError;MatchContextlosesloaderPromises. No app in the repo imports them, and they are not a Breaking changelog entry.Before / after (internal shape)
Before, a document:
After, every match goes through the one walker; a full match passes a FRESH pass:
FRESH(resolution-pass.ts) is an emptyClientHeldcompared by identity: a partial request whose client holds nothing (NOTHING_HELD, an empty_rsc_segments) still asks the revalidate() rules. On a FRESH pass every revalidation decision short-circuits to "new" with no trace entry and norevalidation.decisionevent.The differential's named exceptions, as branches
walker-differential.test.tscompares a FRESH pass with a partial pass whose client holds an empty set. Its exception list now describes these branches; the names and expected lines are unchanged, and the FRESH-output pins (fresh:lines) are unchanged.requestgetter (fresh.ts:787-790)freshPassInputreadscontext.requestguarded; the pass carriesrequest: undefined(ResolutionPassInput.requestisRequest | undefined)throwOnError/ "build-time handler error"pass.throwOnError:resolveWithErrorBoundary(..., pass.throwOnError)inresolveAllSegmentsWithRevalidation, andrecover: !pass.throwOnErrorfor a streamed route or slot handlerpass.skipLoaders, set by the build-time FRESH pass, checked once at the top ofresolveOwnerLoaders(every loader site goes through it)pass.skipLoaders, set byrerenderAndCacheRoutewalk.interceptResultbranch; a FRESH caller passes none (interceptResultandlocalRouteNameare optional now)_handlerRanlike a partial pass;buildMatchResultstrips it, a full match never reads it, and the cache codec never stores itparallelLoadingin the slot loopDivergences the differential does not see (it compares segments only) are kept as explicit FRESH branches too, so the full match stays as it was. They are recorded on the ticket as finding rango-t138-c2:
resolveOwnerLoaders: synchronous kickoffs with no decision;loader(Def, { ssr: false })registration before kickoff,awaitBeforeFlushstamp, default-boundary argument towrapLoaderPromise, and the await of flagged loaders;loading(false)settles every loader outside a shell capture; the loader error context has noisPartial.rango.handlerspan (observeSegmentHandler), with no streamed-Response warning; an evicted slot handler with no static copy emits no slot segment.component: undefinedon FRESH (cache-scope.ts refuses a document record whose layout component is null).resolveAllSegmentsWithRevalidationreports an entry error withoutisPartialon FRESH.Code change
src/router/segment-resolution/fresh.ts. No wrapper exports remain:RouterContext.resolveAllSegments,RouterContext.resolveLoadersOnly,ResolveSegmentOptionsand theirsegment-wrappers.tsbindings are gone.MatchContext.loaderPromises, whose last reader was the fresh walker's unused argument, is gone too (match-api.tsstill hands its local map tosetupLoaderAccess).resolution-pass.ts:FRESH,freshPassInput, and the pass fieldsskipLoadersandthrowOnError.revalidation.ts: the FRESH branches above. The partial lane's decision code moved unchanged intoloadersToRerun,entryRevalidatesandorphanRevalidates, so a FRESH pass runs the handler step without asking them.match-middleware/segment-resolution.ts(one call for full and partial),cache-lookup.ts(the live pass above a cache() HIT and the loaders-only serve, with theresolveLoadersOnlyparameter threading removed),background-revalidation.ts,prerender-match.ts(PrerenderMatchDeps.resolveAllSegmentsWithRevalidation),router.ts.loader-cache.ts,loader-resolution.ts; comments only, naming the new functions), intercept selection, tree-shape files.src/__tests__/kit/resolution-pass.ts(makeFreshPass). RouterContext mocks dropresolveAllSegments/resolveLoadersOnly.prefetch-false-unflagged.rsc-test.tsx's snapshot was not re-recorded.src/router/segment-resolution/__tests__/resolve-all.bench.ts: one FRESH pass per route over 13 routes with the shapes the test-app's documents walk.docs/internal/feature-map.md,docs/internal/feature-file-map.md, and every doc, skill and comment that namedfresh.tsor its functions.Size: non-test source 24 files, +787 / -1549;
fresh.ts,revalidation.tsandresolution-pass.tstogether +639 / -1301. Most other source lines are comment renames.Test plan
walker-differential.test.ts: 42 passed on the base (7b38ae4) and 42 passed on this branch, same exception list, same FRESH pins.revalidation-trace*.test.tsfiles produce identical traces: eachpushRevalidationTraceEntrycall recorded to a file per test file on the base and on this branch; all five pairs are byte-identical (29 entries; 13 + 5 + 6 + 4 + 5 tests pass on both).fresh.tsagainst the FRESH pass on one router, 16 routes x 2 manifest partitions x 3 modes (request, backgroundskipLoaders, build time), plus an evicted-slot case and the live pass above a cache() marker: 99/99 equal on segments (key order included,_handlerRanaside), delivered segments afterbuildMatchResult, perf-metric labels,rango.handlerspans, awaitBeforeFlush registrations, response status, and telemetry events as a multiset. Event order differed on one route (/orphan): the route loader'sloader.endnow precedes the inherited loader'sloader.start(see Notes).packages/rangojs-router,--project=dev --no-depsand--project=production --no-depsonsemantic-matrix,handler-first,loader-behavior,revalidation,parallel-loader-inherit,parallel-new-slot-reval,prerender,prerender-intercept,intercept-whole-request(file filters, confirmed with--list): dev 105 passed, production 95 passed.revalidation.test.tshas no production describe (allowlisted intools/e2e-parity-allowlist.jsonbefore this change);loader-behaviorruns 11 dev and 4 production tests.tests/cloudflare-basic: productionprerender,prerender-ctx,cache-orphan-nested35 passed; devprerender-dev(the dev counterpart:prerender.test.tsis production-only),prerender-ctx,cache-orphan-nested14 passed.pnpm run typecheck(16 tasks);pnpm run test:unit:all: router 564 files, 9347 passed (5 skipped, 6 todo); router RSC 83 files, 1065 passed (3 expected fail); mini, vite-rsc-demo, cloudflare-basic and shared-e2e suites all passed;oxlint --deny-warnings0 findings;oxfmt --checkclean; CI lint scripts (check-e2e-bucketing,check-e2e-parity --strict,check-e2e-console-baseline,check-suspense-contract,check-no-external-store,node --test tools/*.test.mjs51 passed,check-doc-links,check-docs-api,check-skills,generate-skills-catalog --check,generate-route-types --check) andnode tools/check-internals.mjsall OK. No*.gen.tschanges.Practices
oxlint --deny-warningsandoxfmt --checkon the whole repo, plus the CI lint scripts above: no findings.tsc --noUnusedLocals, diffed against the base, found one import this change left unused (HandlerContextinprerender-match.ts); removed.RouterContext.resolveAllSegments/resolveLoadersOnly,ResolveSegmentOptions, theirsegment-wrappers.tsbindings, theloaderPromisesargument the fresh walker never read, theresolveLoadersOnlyparameter threaded through sevencache-lookup.tssites, and theresolveLoadersOnly/resolveAllSegmentsfields of test mocks, andMatchContext.loaderPromises, left unread by the fresh walker's removal (the field, its twomatch-api.tsassignments and four test fixtures). Found and left, as it predates this change:RouterContext.resolveAllSegmentsGeneratorandcollectSegmentsFromGeneratorare declared (optional) and never set or read.test:unit:all, the walker-differential before and after, a trace comparison and a temporary golden comparison, and the targeted e2e above. No consumer-visible behaviour is added; two new unit tests pin the base's static slot behaviour (R3), and the 13 unit files of the deleted functions now run the same assertions against the FRESH pass.fresh.ts, branch by branch; that review found the divergences listed above (finding rango-t138-c2), each kept as a FRESH branch. A second review of the diff (comment rango-t138-c3) compared every function of the deletedfresh.tswith its FRESH branch, the revalidation traces on the base and the head, and the telemetry order; it requested four changes, all made (see "After review rango-t138-c3"). Left as follow-ups: the...WithRevalidationrenames and items 1-3 of finding rango-t138-c2.docs/internal/feature-map.md,docs/internal/feature-file-map.md(pass description, kit helper, and the staleresolveOrphanLayoutandResolveSegmentOptions.*references),docs/internal/execution-model.md, internal and design docs, thehandler-use,pprandcache-guideskills, and source comments namingfresh.tsor its functions.docs/archive/left unchanged: it quotes historical code.After review rango-t138-c3
Two commits on top of the reviewed head; the history before them is unchanged.
refactor(router): hand a static slot value to render unawaited: the merged slot loop awaited a non-function slot value when the slot has noloading()(component = await run()), so a Promise-valued static slot (a React 19 ReactNode) was unwrapped during the walk and a static JSX slot paid one extra microtask. Both walkers before the merge assigned the value as is; the slot loop does again (typeof handler === "function" ? await run() : handler). New tests insegment-resolution-parallel-loading.test.ts, one per pass (FRESH and partial), pin that the slot segment's component is the declared Promise itself. Without the fix both fail (expected 'slot-content' to be Promise{…}); with it, the file passes 10/10.refactor(router): drop the unread MatchContext.loaderPromises: the field and its doc line, the twomatch-api.tsassignments and the fixtures inbackground-revalidation.test.ts,revalidation-trace-match-handlers.test.ts,revalidation-trace-cache-hit.test.tsandmatch-result.test.ts. The background regression test still asserts thatsetupLoaderAccessgets a new, emptyMap, now on the background handler context; without the field the foreground map cannot reach the re-render.feature-map.md(resolveOrphanLayout,ResolveSegmentOptions.chain,.boundaryEntry,.throwOnError, "the fresh path"),feature-file-map.md(ResolveSegmentOptions.throwOnError) andexecution-model.md("fresh and revalidation route handlers"). A repo-wide grep forresolveAllSegments,resolveLoadersOnly,ResolveSegmentOptions,fresh.ts,resolveOrphanLayoutandresolveParallelEntryoutsidedocs/archive/finds none.@rangojs/router/__internaltype changes.Checks at the head:
walker-differential.test.ts42 passed; the fiverevalidation-trace*.test.tsfiles 13 + 5 + 6 + 4 + 5 passed;background-revalidation.test.ts9 andmatch-result.test.ts41 passed; the Hard rule 1 gate and CI lint scripts as listed in the test plan. No e2e: R3 restores the base's behaviour and a unit test pins it.After the timing gate
The committed bench (
resolve-all.bench.ts,resolveAllover 13 routes) ran 9.8% slower than the base (7b38ae4) on a quiet machine, slower in every round. Three commits on top of 8f7d350 close it; the history before them is unchanged. After them, two driver runs under load average 4 measure head/base 1.000 and 0.991.Profile finding (heap sampling profile that keeps collected objects, 1000 iterations of the bench workload; CPU profiles of 4000 iterations; V8
--trace-opt --trace-deoptof 3000 iterations from a cold start):resolveParallelSegmentsWithRevalidation56 KB (the base's slot code allocated 8 KB),resolveEntryHandlerWithRevalidation48 KB plus itsrunclosure 36 KB (the base ran the handler inline inresolveSegment),resolveOwnerLoaders41 KB (the base'sresolveLoaders34 KB).resolveParallelSegmentsWithRevalidation(a promise, a Set, two result arrays and thegetParallelSlotEntriesarrays, for an entry without a slot) and toresolveOwnerLoaders(anownerOfobject and two empty arrays, for an entry without a loader), andresolveEntryHandlerWithRevalidationbuilt itsdecideandrunclosures and one more async layer on every call, FRESH included, which never asksdecide. The entry segment also spread three conditional object literals: about 80 ns per segment, against 12 ns with the keys assigned (measured standalone).resolveOwnerLoadersdeoptimized twice (wrong map at its per-loader object array'smapcalls) and reached stable optimized code after about 2200 iterations, against about 1100 for the base'sresolveLoaders; the bench measures roughly the first 2000.createSegmentWrappers); revalidation inputs on FRESH (revalidationInputOfruns only on a partial pass's held-segment branches); GC survival (--trace-gc-nvp: 80 scavenges and 8 mark-compacts on both sides, promoted bytes within 1%); prefetch-deferral reads on FRESH (skipping them, tried and not kept, moved the instruction count by under 0.5% and left the deopts as they were).What changed (
revalidation.tsonly):perf(router): drop the walker's empty async calls and per-entry closures:ownsLoadersanddeclaresSlotguard the calls toresolveOwnerLoadersandresolveParallelSegmentsWithRevalidation(chain entries, orphan layouts, slot loaders, the loaders-only walk), each equivalent to the callee's own early return. The partial lane's decisions move unchanged toentryRevalidatesandorphanRevalidates, and the route handler run toresolveRouteComponent, so a FRESH pass allocates no closure for them. A loader segment is built in one literal, theawaitBeforeFlushstamp is shared, and no settled-loader array is built when nothing is awaited.perf(router): assign a chain entry segment's optional keys:clientGroup,layoutName,mountPath,_handlerRananddeferredare assigned after the fixed keys, in the same key order.perf(router): build loader segments by index in resolveOwnerLoaders: the segment ids are built once,loadersToRerunreturns the indices of the loaders that run, and the segments come from one loop over the loaders (FRESH) or those indices. In the same trace,resolveOwnerLoadersdeoptimizes once and is stable by about iteration 800.Results at the head:
proc_pid_rusage, 2000 iterations after a 2000-iteration warmup, median of 3 runs): base 6.66 M, 8f7d350 6.99 M (+5.0%), after the first two commits 6.72 M (+1.0%).Segments (key order included), matched ids, revalidation traces and telemetry are unchanged; only the number of microtask hops changes, fewer than at 8f7d350. Checks at the head:
walker-differential.test.ts42 passed; the fiverevalidation-trace*.test.tsfiles 13 + 6 + 5 + 5 + 4 passed, and everypushRevalidationTraceEntrycall recorded per test file at 8f7d350 and at the head is byte-identical (30 entries in four files;revalidation-trace-match-handlers.test.tsmocks the pipeline and records none);segment-resolution-parallel-loading.test.ts10 passed;prefetch-false-unflagged.rsc-test.tsx3 passed, snapshot untouched; the Hard rule 1 gate (pnpm run typecheck16 tasks;pnpm run test:unit:all: router 9347 and RSC 1065 passed, mini, vite-rsc-demo, cloudflare-basic and shared-e2e passed;oxlint --deny-warnings0 findings;oxfmt --checkclean) and the CI lint scripts withcheck-internals, all OK. No e2e: no behaviour changes. The temporary golden comparison was not re-run.Practices for these three commits:
oxlint --deny-warningsandoxfmt --checkon the repo: no findings.OwnedLoadertype had no other users;revalidate(loader-resolution.ts) is still used byloadersToRerun. One read left unused by the change (routeKeyinresolveOrphanLayoutWithRevalidation) was removed.defersLoader, kickoff anddeferredLoaderRunsin the same order per loader, and the partial lane's decision code is unchanged. No findings left.Updated onto reorg/stack
reorg/stackat 2fa88da is merged in (a merge commit; the history before it is unchanged). It brings S16 (WIRE), S5 (deriveRenderPass), S15 (discovery phases), S14 and S14b (request stages,dispatch), S10b (Vercel store), S6 (ShellCaptureState), #1246 and #1247.Conflicts and their resolution:
src/server/request-context.ts: S6 removed_shellCaptureRun, whose doc comment this PR edited. S6's_shellCaptureStatefield and doc are kept. This PR's fact, that the capture switch is read byresolveOwnerLoadersrather thanfresh.tsemitStreaming, now sits onShellCaptureState.runinrsc/shell-capture.ts, which S6 had copied the old text to.docs/design/ppr-shell-resume.md: S6's_shellCaptureState.run, with this PR'sresolveOwnerLoaderssettle guard in place offresh.tsemitStreaming.docs/internal/feature-file-map.md: one table hunk, because both sides realigned the columns. Each row comes from the side that changed it. The two rows both sides changed are combined: "PPR shell caching (axis 2)" keeps S6's field names and this PR'srevalidation.tslink in place offresh.ts, and "Internal unit-test kit" lists S6'smakeShellCaptureStateand this PR'smakeFreshPass.docs/internal/feature-map.md: thetransition()entry keeps S15'svite/discovery/dev-server-phase.tspath and this PR'srevalidation.tswithoutfresh.ts.Semantic conflicts:
src/server/__tests__/render-pass.bench.tsx(new in S5): the background re-render'sRouterContextstub providedresolveAllSegments, which this PR removes.rerenderAndCacheRoutecallsresolveAllSegmentsWithRevalidation, so the stub now provides that. Without the fix, the "background re-render (stub resolution)" bench reports NaN. With it, the bench measures 89,933 hz.rerenderAndCacheRouteruns insiderunWithRequestContext(renderCtx), whererenderCtxis S5'sderiveRenderPass(requestCtx, "background-revalidation", …), exactly as the base pass does. The walker reads the capture switch only throughloader-mask.tsisShellCaptureActive(), which S6 rewrote to_shellCaptureState?.run. No source or test reads_shellCaptureRun,_shellCaptureForVisitor,_shellCaptureLoaderRecords,_shellCaptureLoaderLanesor_routeRecordWindow. Outsidedocs/archive/, no file namesfresh.ts,emitStreaming,resolveLoadersOnlyorResolveSegmentOptions. No added line onreorg/stackuses a name this PR removes.Checks at the merge:
pnpm run typecheck: 16 tasks passed.pnpm run test:unit:all: router 567 files, 9363 passed (5 skipped, 6 todo); router RSC 84 files, 1067 passed (3 expected fail); mini 25 + 3, vite-rsc-demo 27 + 1, cloudflare-basic 90 + 39 and shared-e2e 20 all passed.walker-differential.test.ts, the fiverevalidation-trace*.test.tsfiles,segment-resolution-parallel-loading.test.ts, andprefetch-false-unflagged.rsc-test.tsx, whose snapshot was not re-recorded. Also passing: S3'sderived-context-shape.rsc-test.tsx, and every test file S6 touched (render-pass-capture-state,context-fast-properties,cookie-store,request-headers,shell-capture,loader-mask-capture,await-before-flush,loader-pins,loader-seed-overlay, and the rest). In total, 26 unit files with 718 tests and 4 RSC files with 88 tests.oxlint --deny-warnings: 0 findings.oxfmt --check: clean. The CI lint scripts (check-e2e-bucketing,check-e2e-parity --strict,check-e2e-console-baseline,check-suspense-contract,check-no-external-store,node --test tools/*.test.mjswith 53 passed,check-doc-links,check-docs-api,check-skills,generate-skills-catalog --checkandgenerate-route-types --check) all pass.node tools/check-internals.mjspasses at the baseline, with no raise. No*.gen.tschanges.packages/rangojs-router,--project=dev --project=production --no-deps --grep "semantic-matrix|handler-first|loader-behavior|revalidation|shell-cache|prerender"(confirmed with--list: 600 tests in 49 files, 303 dev and 297 production): 599 passed and 1 flaky. The flaky test was devshell-cache"stale SWR navigation replays the captured handler promise, top-level handles, and Meta". On its first attempt its wall-clock budget (toBeLessThan(1_200)) read 1320 ms after the content assertions had passed. It passed on retry, and its production counterpart passed. Rerun alone in dev with--repeat-each=3 --retries=0, it passed 3/3. It has not been run on the base.Practices for the merge:
oxlint --deny-warningsandoxfmt --checkon the repo: no findings.RouterContextmethods, and the repo-wide grep above covers the old names. Nothing new was left unused.background-revalidation.ts,loader-mask.ts,loader-cache.ts,context.ts,match-api.tsand the shared tests). No findings left.ShellCaptureState.run's comment. No other doc namesfresh.tsor the old shell-capture fields.Notes
resolveEntryHandlerWithRevalidation, andresolveRouteComponentfor a route), wherefresh.tsran it inline; it skips the loader and slot calls for an entry that owns no loader or slot, wherefresh.tsmade an async loader call for every entry. Kickoff order is unchanged (loaders before the handler, handler first, the route loop's kickoffs synchronous), but independent async work interleaves differently at microtask granularity; the golden comparison shows it as loader telemetry event order on one route. A streamed handler that pushes a handle after anawaitreads the live_currentSegmentId, a race the partial lane already has.buildMatchResult(match-result.ts) now strips_handlerRanfrom each FRESH segment that carries it (the non-loader segments), a shallow copy each. The bench does not cover it; one object spread per segment is not expected to show....WithRevalidationnames to keep this diff reviewable; a rename to the target names (resolveAll,resolveLoaders) is a follow-up.