@rangojs/router 0.0.0-experimental.145 → 0.0.0-experimental.146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/rango.js CHANGED
@@ -1394,7 +1394,7 @@ var init_generate_route_types = __esm({
1394
1394
  function getVirtualVersionContent(version) {
1395
1395
  return `export const VERSION = ${JSON.stringify(version)};`;
1396
1396
  }
1397
- var VIRTUAL_ENTRY_BROWSER, VIRTUAL_ENTRY_SSR, VIRTUAL_IDS;
1397
+ var VIRTUAL_ENTRY_BROWSER, VIRTUAL_IDS;
1398
1398
  var init_virtual_entries = __esm({
1399
1399
  "src/vite/plugins/virtual-entries.ts"() {
1400
1400
  "use strict";
@@ -1434,45 +1434,6 @@ async function initializeApp() {
1434
1434
  }
1435
1435
 
1436
1436
  initializeApp().catch(console.error);
1437
- `.trim();
1438
- VIRTUAL_ENTRY_SSR = `
1439
- import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
1440
- import { renderToReadableStream, resume } from "react-dom/server.edge";
1441
- import { prerender } from "react-dom/static.edge";
1442
- import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
1443
- import {
1444
- createSSRHandler,
1445
- createShellCaptureHandler,
1446
- createShellResumeHandler,
1447
- } from "@rangojs/router/ssr";
1448
-
1449
- export const renderHTML = createSSRHandler({
1450
- createFromReadableStream,
1451
- renderToReadableStream,
1452
- injectRSCPayload,
1453
- loadBootstrapScriptContent: () =>
1454
- import.meta.viteRsc.loadBootstrapScriptContent("index"),
1455
- });
1456
-
1457
- export const captureShellHTML = createShellCaptureHandler({
1458
- createFromReadableStream,
1459
- renderToReadableStream,
1460
- injectRSCPayload,
1461
- prerender,
1462
- resume,
1463
- loadBootstrapScriptContent: () =>
1464
- import.meta.viteRsc.loadBootstrapScriptContent("index"),
1465
- });
1466
-
1467
- export const resumeShellHTML = createShellResumeHandler({
1468
- createFromReadableStream,
1469
- renderToReadableStream,
1470
- injectRSCPayload,
1471
- prerender,
1472
- resume,
1473
- loadBootstrapScriptContent: () =>
1474
- import.meta.viteRsc.loadBootstrapScriptContent("index"),
1475
- });
1476
1437
  `.trim();
1477
1438
  VIRTUAL_IDS = {
1478
1439
  browser: "virtual:rsc-router/entry.browser.js",
@@ -2210,21 +2210,35 @@ async function initializeApp() {
2210
2210
 
2211
2211
  initializeApp().catch(console.error);
2212
2212
  `.trim();
2213
- var VIRTUAL_ENTRY_SSR = `
2214
- import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
2213
+ function getVirtualEntrySSR(headScripts = "preinit") {
2214
+ const preinit = headScripts !== "preload";
2215
+ const depsImportNames = preinit ? "createFromReadableStream,\n setOnClientReference," : "createFromReadableStream,";
2216
+ const ssrImportNames = preinit ? "\n installClientReferencePreinit," : "";
2217
+ const install = preinit ? `
2218
+ // Upgrade client-reference modulepreload hints to executing module scripts in
2219
+ // the document head, for every render pass (live SSR, shell capture, resume).
2220
+ // See src/ssr/preinit-client-references.ts for the full rationale.
2221
+ installClientReferencePreinit(setOnClientReference);
2222
+ ` : "";
2223
+ const hs = JSON.stringify(headScripts);
2224
+ return `
2225
+ import {
2226
+ ${depsImportNames}
2227
+ } from "@rangojs/router/internal/deps/ssr";
2215
2228
  import { renderToReadableStream, resume } from "react-dom/server.edge";
2216
2229
  import { prerender } from "react-dom/static.edge";
2217
2230
  import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
2218
2231
  import {
2219
2232
  createSSRHandler,
2220
2233
  createShellCaptureHandler,
2221
- createShellResumeHandler,
2234
+ createShellResumeHandler,${ssrImportNames}
2222
2235
  } from "@rangojs/router/ssr";
2223
-
2236
+ ${install}
2224
2237
  export const renderHTML = createSSRHandler({
2225
2238
  createFromReadableStream,
2226
2239
  renderToReadableStream,
2227
2240
  injectRSCPayload,
2241
+ headScripts: ${hs},
2228
2242
  loadBootstrapScriptContent: () =>
2229
2243
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2230
2244
  });
@@ -2235,6 +2249,7 @@ export const captureShellHTML = createShellCaptureHandler({
2235
2249
  injectRSCPayload,
2236
2250
  prerender,
2237
2251
  resume,
2252
+ headScripts: ${hs},
2238
2253
  loadBootstrapScriptContent: () =>
2239
2254
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2240
2255
  });
@@ -2245,10 +2260,12 @@ export const resumeShellHTML = createShellResumeHandler({
2245
2260
  injectRSCPayload,
2246
2261
  prerender,
2247
2262
  resume,
2263
+ headScripts: ${hs},
2248
2264
  loadBootstrapScriptContent: () =>
2249
2265
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2250
2266
  });
2251
2267
  `.trim();
2268
+ }
2252
2269
  var RSC_ENTRY_BOOTSTRAP_IMPORTS = [
2253
2270
  "virtual:rsc-router/routes-manifest",
2254
2271
  "virtual:rsc-router/loader-manifest"
@@ -2393,7 +2410,7 @@ import { resolve } from "node:path";
2393
2410
  // package.json
2394
2411
  var package_default = {
2395
2412
  name: "@rangojs/router",
2396
- version: "0.0.0-experimental.145",
2413
+ version: "0.0.0-experimental.146",
2397
2414
  description: "Django-inspired RSC router with composable URL patterns",
2398
2415
  keywords: [
2399
2416
  "react",
@@ -4017,13 +4034,13 @@ function normalizeHostRouterEntry(rawInput, root, exists) {
4017
4034
  }
4018
4035
  return exists(resolve4(root, raw)) ? "./" + raw : raw;
4019
4036
  }
4020
- function createVirtualEntriesPlugin(entries, routerPathRef) {
4037
+ function createVirtualEntriesPlugin(entries, routerPathRef, options) {
4021
4038
  const virtualModules = {};
4022
4039
  if (entries.client === VIRTUAL_IDS.browser) {
4023
4040
  virtualModules[VIRTUAL_IDS.browser] = VIRTUAL_ENTRY_BROWSER;
4024
4041
  }
4025
4042
  if (entries.ssr === VIRTUAL_IDS.ssr) {
4026
- virtualModules[VIRTUAL_IDS.ssr] = VIRTUAL_ENTRY_SSR;
4043
+ virtualModules[VIRTUAL_IDS.ssr] = getVirtualEntrySSR(options?.headScripts);
4027
4044
  }
4028
4045
  const knownIds = new Set(Object.keys(virtualModules));
4029
4046
  if (entries.rsc === VIRTUAL_IDS.rsc) {
@@ -7893,7 +7910,11 @@ async function rango(options) {
7893
7910
  }
7894
7911
  }
7895
7912
  });
7896
- plugins.push(createVirtualEntriesPlugin(finalEntries));
7913
+ plugins.push(
7914
+ createVirtualEntriesPlugin(finalEntries, void 0, {
7915
+ headScripts: resolvedOptions.headScripts
7916
+ })
7917
+ );
7897
7918
  plugins.push(performanceTracksPlugin());
7898
7919
  plugins.push(
7899
7920
  rsc({
@@ -8073,7 +8094,11 @@ If this is a multi-app host router, export a createHostRouter() instance and set
8073
8094
  }
8074
8095
  }
8075
8096
  });
8076
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
8097
+ plugins.push(
8098
+ createVirtualEntriesPlugin(finalEntries, routerRef, {
8099
+ headScripts: resolvedOptions.headScripts
8100
+ })
8101
+ );
8077
8102
  plugins.push(performanceTracksPlugin());
8078
8103
  plugins.push(
8079
8104
  rsc({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.145",
3
+ "version": "0.0.0-experimental.146",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -452,7 +452,11 @@ export class CacheScope {
452
452
  }
453
453
 
454
454
  // Collect handle data for non-loader segments only
455
- const handles = captureHandles(nonLoaderSegments, handleStore);
455
+ const handles = captureHandles(
456
+ nonLoaderSegments,
457
+ handleStore,
458
+ requestCtx._shellCaptureLoaderHandleValues,
459
+ );
456
460
 
457
461
  try {
458
462
  if (INTERNAL_RANGO_DEBUG) {
@@ -512,3 +516,28 @@ export function createCacheScope(
512
516
  if (!config) return parent; // No config, inherit parent
513
517
  return new CacheScope(config.options, parent);
514
518
  }
519
+
520
+ /**
521
+ * Shell fast path: when the route tree derived NO cache scope and the current
522
+ * request context carries the `_shellImplicitCache` marker (a shell capture,
523
+ * or a HIT tail serving an eligible entry), substitute an implicit doc-level
524
+ * scope so withCacheLookup/withCacheStore treat the WHOLE matched route as a
525
+ * cache() boundary — the shell entry IS a cache() of the handler layer, with
526
+ * loaders as the live carve-outs (resolveFreshLoadersAndYield).
527
+ *
528
+ * An existing scope — including an explicit cache(false) opt-out — always
529
+ * wins: the consumer's cache() semantics (their ttl/swr/store/condition) are
530
+ * never overridden, and cache(false) keeps the tail on the full handler
531
+ * re-run path.
532
+ */
533
+ export function resolveShellImplicitCacheScope(
534
+ scope: CacheScope | null,
535
+ ): CacheScope | null {
536
+ if (scope) return scope;
537
+ const marker = getRequestContext()?._shellImplicitCache;
538
+ if (!marker) return null;
539
+ return new CacheScope(
540
+ { ttl: marker.ttl, swr: marker.swr, store: marker.store },
541
+ null,
542
+ );
543
+ }
@@ -83,14 +83,35 @@ export async function decodeHandleValue<T>(encoded: string): Promise<T | null> {
83
83
  /**
84
84
  * Capture handle data for a set of segments from the handle store.
85
85
  * Used when caching segments to preserve their handle data.
86
+ *
87
+ * `exclude` (shell captures: RequestContext._shellCaptureLoaderHandleValues)
88
+ * drops DSL-loader-scoped push values from the CACHE WRITE only: loaders
89
+ * re-run fresh on every HIT, so replaying their captured values would
90
+ * duplicate the fresh push — and their masked nested promises would stall the
91
+ * Flight handle encode to its timeout. Threaded as an explicit argument so
92
+ * every other getDataForSegment consumer (the render-barrier snapshot,
93
+ * prerender) provably sees every push.
86
94
  */
87
95
  export function captureHandles(
88
96
  segments: ResolvedSegment[],
89
97
  handleStore: HandleStore,
98
+ exclude?: WeakSet<object>,
90
99
  ): Record<string, SegmentHandleData> {
91
100
  const handles: Record<string, SegmentHandleData> = {};
92
101
  for (const seg of segments) {
93
- handles[seg.id] = handleStore.getDataForSegment(seg.id);
102
+ const data = handleStore.getDataForSegment(seg.id);
103
+ if (!exclude) {
104
+ handles[seg.id] = data;
105
+ continue;
106
+ }
107
+ const filtered: SegmentHandleData = {};
108
+ for (const [handleName, values] of Object.entries(data)) {
109
+ const kept = values.filter(
110
+ (v) => typeof v !== "object" || v === null || !exclude.has(v),
111
+ );
112
+ if (kept.length > 0) filtered[handleName] = kept;
113
+ }
114
+ handles[seg.id] = filtered;
94
115
  }
95
116
  return handles;
96
117
  }
@@ -110,6 +110,19 @@ export class RecordingShellStore<
110
110
  this.writes.push(p);
111
111
  }
112
112
 
113
+ /**
114
+ * Record a segment-family write into the snapshot WITHOUT touching the inner
115
+ * store. The shell fast path's implicit doc-cache scope writes through this
116
+ * (via {@link SnapshotOnlySegmentStore}): the recorded doc entry must ride
117
+ * ONLY inside the shell entry — a passthrough write would leave a doc-keyed
118
+ * entry in the real store that the NEXT capture's lookup would hit, replaying
119
+ * the previous generation's segments instead of re-running handlers (breaking
120
+ * SWR recapture freshness).
121
+ */
122
+ recordSegmentWrite(key: string, data: CachedEntryData): void {
123
+ this.record("segment", key, data);
124
+ }
125
+
113
126
  /**
114
127
  * Await the tracked deferred writes so their records are present before drain.
115
128
  * Drains ITERATIVELY: a write task can schedule a NESTED write (the ring-3
@@ -245,6 +258,40 @@ export function getRecordingStore<TEnv>(
245
258
  return store instanceof RecordingShellStore ? store : undefined;
246
259
  }
247
260
 
261
+ /**
262
+ * The store the shell fast path's IMPLICIT doc-cache scope resolves during a
263
+ * capture: reads pass through the recording store (a real-store hit is
264
+ * recorded, exactly like any capture read), but segment WRITES are recorded
265
+ * into the snapshot only — see {@link RecordingShellStore.recordSegmentWrite}
266
+ * for why passthrough would break SWR recapture. Routes with their OWN
267
+ * cache() config never see this store (their scope resolves the app-level
268
+ * recording store and keeps today's record-and-write behavior).
269
+ */
270
+ export class SnapshotOnlySegmentStore<
271
+ TEnv = unknown,
272
+ > implements SegmentCacheStore<TEnv> {
273
+ constructor(private readonly recording: RecordingShellStore<TEnv>) {}
274
+
275
+ get defaults(): SegmentCacheStore<TEnv>["defaults"] {
276
+ return this.recording.defaults;
277
+ }
278
+ get keyGenerator(): SegmentCacheStore<TEnv>["keyGenerator"] {
279
+ return this.recording.keyGenerator;
280
+ }
281
+
282
+ async get(key: string): Promise<CacheGetResult | null> {
283
+ return this.recording.get(key);
284
+ }
285
+
286
+ async set(key: string, data: CachedEntryData): Promise<void> {
287
+ this.recording.recordSegmentWrite(key, data);
288
+ }
289
+
290
+ async delete(key: string): Promise<boolean> {
291
+ return this.recording.delete(key);
292
+ }
293
+ }
294
+
248
295
  /**
249
296
  * Materialize the loader-family seed from a shell snapshot for a HIT's tail
250
297
  * render: Flight-deserialize each recorded (promise-elided) bake-lane
@@ -280,6 +280,19 @@ export interface ShellCacheEntry {
280
280
  * heals it. See docs/design/ppr-shell-resume.md ("the capture data snapshot").
281
281
  */
282
282
  snapshot?: ShellSnapshotRecord[];
283
+ /**
284
+ * True when the capture's HANDLER layer declared per-request liveness: a
285
+ * handle pushed OUTSIDE a DSL loader scope carried a nested thenable (the
286
+ * capture mask turns it into a never-filling hole), such a push was still
287
+ * pending when the entry was written, or a handler-invoked loader
288
+ * (ctx.use(loader) from a handler body — the consumption lane, #672)
289
+ * executed during the capture. The serve tail then must NOT take the
290
+ * handler-free fast path (the implicit doc-cache hit): only a handler
291
+ * re-run can mint that hole's live promise or refresh that consumed value.
292
+ * DSL-loader pushes never set this — loaders re-run fresh on every HIT, so
293
+ * their holes always fill.
294
+ */
295
+ handlerLiveHoles?: boolean;
283
296
  /** Epoch ms when the shell was captured. */
284
297
  createdAt: number;
285
298
  }
package/src/deps/ssr.ts CHANGED
@@ -1 +1,4 @@
1
- export { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
1
+ export {
2
+ createFromReadableStream,
3
+ setOnClientReference,
4
+ } from "@vitejs/plugin-rsc/ssr";
@@ -430,6 +430,22 @@ function createLoaderExecutor<TEnv>(
430
430
  // nested deps inherit isDslLoader=false only when the CHAIN started in a
431
431
  // handler; a chain started by the segment funnel stays DSL (the loader
432
432
  // scope ALS survives the body's awaits).
433
+ // Shell fast path eligibility: a HANDLER-invoked loader executing during a
434
+ // capture is handler-layer dynamism — on a handler-free (replayed) HIT it
435
+ // would never re-run, freezing its consumption-lane value (#672's "fresh
436
+ // per serve" slot shape). Mark the capture; the entry declines the fast
437
+ // path and keeps the full tail. DSL loaders re-run on every HIT and never
438
+ // set this.
439
+ if (!isDslLoader) {
440
+ const captureCtx = _getRequestContext();
441
+ if (
442
+ captureCtx?._shellCaptureRun &&
443
+ captureCtx._shellCaptureHandleLiveness
444
+ ) {
445
+ captureCtx._shellCaptureHandleLiveness.handlerInvokedLoader = true;
446
+ }
447
+ }
448
+
433
449
  const promise = observePhase(PHASES.loader(loader.$$id), () =>
434
450
  Promise.resolve(
435
451
  runInsideLoaderBodyScope(
@@ -1,4 +1,8 @@
1
- import { CacheScope, createCacheScope } from "../cache/cache-scope.js";
1
+ import {
2
+ CacheScope,
3
+ createCacheScope,
4
+ resolveShellImplicitCacheScope,
5
+ } from "../cache/cache-scope.js";
2
6
  import { RouteNotFoundError } from "../errors";
3
7
  import {
4
8
  createErrorInfo,
@@ -180,7 +184,10 @@ export async function createMatchContextForFull<TEnv>(
180
184
  },
181
185
  isSameRouteNavigation: false,
182
186
  interceptResult: null,
183
- cacheScope: snapshot.cacheScope,
187
+ // Shell fast path: a capture or an eligible HIT tail may substitute an
188
+ // implicit doc-level scope (marker-gated; a route-derived scope wins).
189
+ // Full matches only — partial navigations never serve a shell.
190
+ cacheScope: resolveShellImplicitCacheScope(snapshot.cacheScope),
184
191
  isIntercept: false,
185
192
  actionContext: undefined,
186
193
  isAction: false,
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
+ import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
2
3
  import { sanitizeError } from "../errors";
3
4
  import type { ErrorInfo, ErrorPhase, MatchResult } from "../types";
4
5
  import type {
@@ -291,7 +292,13 @@ export function createMatchHandlers<TEnv = any>(
291
292
  });
292
293
  emitter.start();
293
294
 
295
+ const ctxBuildStart = INTERNAL_RANGO_DEBUG ? performance.now() : 0;
294
296
  const result = await createMatchContextForFull(request, env);
297
+ if (INTERNAL_RANGO_DEBUG) {
298
+ console.log(
299
+ `[Server][match] context built +${Math.round(performance.now() - ctxBuildStart)}ms (abs ${Math.round(performance.now())})`,
300
+ );
301
+ }
295
302
 
296
303
  if ("type" in result && result.type === "redirect") {
297
304
  emitter.end(0, false);
@@ -310,7 +317,13 @@ export function createMatchHandlers<TEnv = any>(
310
317
  try {
311
318
  const state = createPipelineState();
312
319
  const pipeline = createMatchPartialPipeline(ctx, state);
320
+ const pipeStart = INTERNAL_RANGO_DEBUG ? performance.now() : 0;
313
321
  const matchResult = await collectMatchResult(pipeline, ctx, state);
322
+ if (INTERNAL_RANGO_DEBUG) {
323
+ console.log(
324
+ `[Server][match] pipeline collected +${Math.round(performance.now() - pipeStart)}ms (abs ${Math.round(performance.now())})`,
325
+ );
326
+ }
314
327
  if (hasTelemetry || cacheSignalEnabled) {
315
328
  const signalSegments = buildSignal(ctx.routeKey, state);
316
329
  recordSignalIfEnabled(signalSegments);
@@ -51,11 +51,27 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
51
51
  * rule, semantic-matrix PPR3) shares the raw container and must keep real
52
52
  * values. Cycles are preserved as cycles in the copy.
53
53
  */
54
+ /**
55
+ * Optional single-walk report for maskNestedContainerThenables: `thenable`
56
+ * flips true when the walk masked at least one thenable. The shell fast path
57
+ * reads it at handle-push time — a pushed container with a nested thenable
58
+ * declares per-request data, and a shell entry whose HANDLER layer made such
59
+ * a declaration cannot be served handler-free (the hole would never fill).
60
+ * Same single-pass shape as elideLoaderContainer's `hasHole`.
61
+ */
62
+ export interface MaskReport {
63
+ thenable: boolean;
64
+ }
65
+
54
66
  export function maskNestedContainerThenables(
55
67
  value: unknown,
56
68
  seen: Map<object, unknown> = new Map(),
69
+ report?: MaskReport,
57
70
  ): unknown {
58
- if (isThenable(value)) return createMaskedLoaderPromise();
71
+ if (isThenable(value)) {
72
+ if (report) report.thenable = true;
73
+ return createMaskedLoaderPromise();
74
+ }
59
75
 
60
76
  if (Array.isArray(value)) {
61
77
  const cached = seen.get(value);
@@ -63,7 +79,7 @@ export function maskNestedContainerThenables(
63
79
  const out: unknown[] = new Array(value.length);
64
80
  seen.set(value, out);
65
81
  for (let i = 0; i < value.length; i++) {
66
- out[i] = maskNestedContainerThenables(value[i], seen);
82
+ out[i] = maskNestedContainerThenables(value[i], seen, report);
67
83
  }
68
84
  return out;
69
85
  }
@@ -74,7 +90,7 @@ export function maskNestedContainerThenables(
74
90
  const out: Record<string, unknown> = {};
75
91
  seen.set(value, out);
76
92
  for (const key of Object.keys(value)) {
77
- out[key] = maskNestedContainerThenables(value[key], seen);
93
+ out[key] = maskNestedContainerThenables(value[key], seen, report);
78
94
  }
79
95
  return out;
80
96
  }
@@ -493,7 +493,13 @@ function serveShellHit(
493
493
  const renderTail = async (
494
494
  activeCtx: RequestContext<any>,
495
495
  ): Promise<ReadableStream<Uint8Array> | { redirect: string }> => {
496
+ const matchStart = INTERNAL_RANGO_DEBUG ? performance.now() : 0;
496
497
  const match = await ctx.router.match(request, { env });
498
+ if (INTERNAL_RANGO_DEBUG) {
499
+ console.log(
500
+ `[Server][ppr] shell HIT: tail match done +${Math.round(performance.now() - matchStart)}ms (abs ${Math.round(performance.now())}, started ${Math.round(matchStart)})`,
501
+ );
502
+ }
497
503
  if (match.redirect) return { redirect: match.redirect };
498
504
  setRequestContextParams(match.params, match.routeName);
499
505
  const payload = buildFullPayload(match, ctx, url, activeCtx, handleStore);
@@ -530,7 +536,7 @@ function serveShellHit(
530
536
  if (!first) {
531
537
  first = true;
532
538
  console.log(
533
- `[Server][ppr] flight render: first chunk +${Math.round(performance.now() - tapStart)}ms`,
539
+ `[Server][ppr] flight render: first chunk +${Math.round(performance.now() - tapStart)}ms (abs ${Math.round(performance.now())})`,
534
540
  );
535
541
  }
536
542
  controller.enqueue(chunk);
@@ -571,8 +577,38 @@ function serveShellHit(
571
577
  // decode into a seed Map for the resolveLoaderData overlay, so the
572
578
  // payload's baked container bytes match the frozen prelude while the
573
579
  // hole-marker paths keep the fresh run's live nested promises.
580
+ const seedStart = INTERNAL_RANGO_DEBUG ? performance.now() : 0;
574
581
  const loaderSeed = await buildShellLoaderSeed(entry.snapshot);
582
+ if (INTERNAL_RANGO_DEBUG) {
583
+ console.log(
584
+ `[Server][ppr] shell HIT: loader seed built +${Math.round(performance.now() - seedStart)}ms (abs ${Math.round(performance.now())})`,
585
+ );
586
+ }
575
587
  if (loaderSeed) seededCtx._shellLoaderSeed = loaderSeed;
588
+ // Shell fast path (serve side): when the capture recorded the implicit
589
+ // doc segment record and the handler layer declared no liveness, arm the
590
+ // implicit scope on the seeded context — the tail match's cache lookup
591
+ // then HITs the SeededShellStore's doc entry and the whole handler layer
592
+ // is REPLAYED, not re-executed (loaders still run fresh via
593
+ // resolveFreshLoadersAndYield; per-request payload metadata is rebuilt
594
+ // by buildFullPayload as always). A route with handler-live holes, a
595
+ // route-derived cache scope, or a missing/corrupt record degrades to
596
+ // the full tail (handler re-run — today's behavior) automatically.
597
+ if (!entry.handlerLiveHoles) {
598
+ seededCtx._shellImplicitCache = {
599
+ ttl: descriptor.ttl,
600
+ swr: descriptor.swr,
601
+ };
602
+ if (INTERNAL_RANGO_DEBUG) {
603
+ console.log(
604
+ `[Server][ppr] shell HIT: fast path armed (implicit doc cache) (abs ${Math.round(performance.now())})`,
605
+ );
606
+ }
607
+ } else if (INTERNAL_RANGO_DEBUG) {
608
+ console.log(
609
+ `[Server][ppr] shell HIT: fast path declined — handler-live holes; tail re-runs handlers (abs ${Math.round(performance.now())})`,
610
+ );
611
+ }
576
612
  return runWithRequestContext(seededCtx, () => renderTail(seededCtx));
577
613
  }
578
614
  return renderTail(reqCtx);
@@ -592,9 +628,15 @@ function serveShellHit(
592
628
  }
593
629
  try {
594
630
  const tail = await tailPromise;
631
+ if (INTERNAL_RANGO_DEBUG) {
632
+ console.log(
633
+ `[Server][ppr] shell HIT: tail stream handed over +${Math.round(performance.now() - serveStart)}ms (abs ${Math.round(performance.now())})`,
634
+ );
635
+ }
595
636
  if (tail instanceof ReadableStream) {
596
637
  const reader = tail.getReader();
597
638
  let firstTailChunk = true;
639
+ let tailBytes = 0;
598
640
  try {
599
641
  for (;;) {
600
642
  const { done, value } = await reader.read();
@@ -602,14 +644,24 @@ function serveShellHit(
602
644
  if (INTERNAL_RANGO_DEBUG && firstTailChunk) {
603
645
  firstTailChunk = false;
604
646
  console.log(
605
- `[Server][ppr] shell HIT: first tail chunk on the wire +${Math.round(performance.now() - serveStart)}ms`,
647
+ `[Server][ppr] shell HIT: first tail chunk on the wire +${Math.round(performance.now() - serveStart)}ms (abs ${Math.round(performance.now())})`,
606
648
  );
607
649
  }
650
+ if (INTERNAL_RANGO_DEBUG) tailBytes += value.length;
608
651
  controller.enqueue(value);
609
652
  }
610
653
  } finally {
611
654
  reader.releaseLock();
612
655
  }
656
+ // Bounds the post-header work Server-Timing structurally cannot see:
657
+ // the HIT commits headers at the flush, so ALL live-tail time (match,
658
+ // loaders, Flight, resume) happens inside the response body. This
659
+ // line plus the [Server][segments] build logs narrate that window.
660
+ if (INTERNAL_RANGO_DEBUG) {
661
+ console.log(
662
+ `[Server][ppr] shell HIT: tail complete +${Math.round(performance.now() - serveStart)}ms (${tailBytes}b)`,
663
+ );
664
+ }
613
665
  } else {
614
666
  // Defensive, near-unreachable: a redirecting match cannot have captured
615
667
  // a shell (capture bails on redirects), so a HIT on a redirecting URL