@rangojs/router 0.0.0-experimental.18 → 0.0.0-experimental.19

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.
Files changed (177) hide show
  1. package/README.md +46 -8
  2. package/dist/bin/rango.js +105 -18
  3. package/dist/vite/index.js +227 -93
  4. package/package.json +15 -14
  5. package/skills/hooks/SKILL.md +1 -1
  6. package/skills/intercept/SKILL.md +79 -0
  7. package/skills/layout/SKILL.md +62 -2
  8. package/skills/loader/SKILL.md +94 -1
  9. package/skills/middleware/SKILL.md +81 -0
  10. package/skills/parallel/SKILL.md +57 -2
  11. package/skills/prerender/SKILL.md +187 -17
  12. package/skills/route/SKILL.md +42 -1
  13. package/skills/router-setup/SKILL.md +77 -0
  14. package/src/__internal.ts +1 -1
  15. package/src/bin/rango.ts +38 -19
  16. package/src/browser/action-coordinator.ts +97 -0
  17. package/src/browser/event-controller.ts +25 -27
  18. package/src/browser/history-state.ts +80 -0
  19. package/src/browser/intercept-utils.ts +1 -1
  20. package/src/browser/link-interceptor.ts +0 -3
  21. package/src/browser/merge-segment-loaders.ts +9 -2
  22. package/src/browser/navigation-bridge.ts +46 -13
  23. package/src/browser/navigation-client.ts +32 -61
  24. package/src/browser/navigation-store.ts +1 -31
  25. package/src/browser/navigation-transaction.ts +46 -207
  26. package/src/browser/partial-update.ts +102 -150
  27. package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
  28. package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
  29. package/src/browser/prefetch/policy.ts +42 -0
  30. package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
  31. package/src/browser/react/Link.tsx +28 -23
  32. package/src/browser/react/NavigationProvider.tsx +9 -1
  33. package/src/browser/react/index.ts +2 -6
  34. package/src/browser/react/location-state-shared.ts +1 -1
  35. package/src/browser/react/location-state.ts +2 -0
  36. package/src/browser/react/nonce-context.ts +23 -0
  37. package/src/browser/react/use-action.ts +9 -1
  38. package/src/browser/react/use-handle.ts +3 -25
  39. package/src/browser/react/use-params.ts +2 -4
  40. package/src/browser/react/use-pathname.ts +2 -3
  41. package/src/browser/react/use-router.ts +1 -1
  42. package/src/browser/react/use-search-params.ts +2 -1
  43. package/src/browser/react/use-segments.ts +7 -60
  44. package/src/browser/response-adapter.ts +73 -0
  45. package/src/browser/rsc-router.tsx +29 -23
  46. package/src/browser/scroll-restoration.ts +10 -7
  47. package/src/browser/server-action-bridge.ts +115 -96
  48. package/src/browser/types.ts +1 -31
  49. package/src/browser/validate-redirect-origin.ts +29 -0
  50. package/src/build/generate-manifest.ts +5 -0
  51. package/src/build/generate-route-types.ts +2 -0
  52. package/src/build/route-types/codegen.ts +13 -4
  53. package/src/build/route-types/include-resolution.ts +13 -0
  54. package/src/build/route-types/per-module-writer.ts +15 -3
  55. package/src/build/route-types/router-processing.ts +45 -3
  56. package/src/build/runtime-discovery.ts +13 -1
  57. package/src/cache/background-task.ts +34 -0
  58. package/src/cache/cache-key-utils.ts +44 -0
  59. package/src/cache/cache-policy.ts +125 -0
  60. package/src/cache/cache-runtime.ts +132 -96
  61. package/src/cache/cache-scope.ts +71 -73
  62. package/src/cache/cf/cf-cache-store.ts +9 -4
  63. package/src/cache/document-cache.ts +72 -47
  64. package/src/cache/handle-capture.ts +81 -0
  65. package/src/cache/memory-segment-store.ts +18 -7
  66. package/src/cache/profile-registry.ts +43 -8
  67. package/src/cache/read-through-swr.ts +134 -0
  68. package/src/cache/segment-codec.ts +101 -112
  69. package/src/cache/taint.ts +26 -0
  70. package/src/client.tsx +53 -30
  71. package/src/errors.ts +6 -1
  72. package/src/handle.ts +1 -1
  73. package/src/handles/MetaTags.tsx +5 -2
  74. package/src/host/cookie-handler.ts +8 -3
  75. package/src/host/router.ts +14 -1
  76. package/src/href-client.ts +3 -1
  77. package/src/index.rsc.ts +33 -1
  78. package/src/index.ts +27 -0
  79. package/src/loader.rsc.ts +12 -4
  80. package/src/loader.ts +8 -0
  81. package/src/prerender/store.ts +4 -3
  82. package/src/prerender.ts +76 -18
  83. package/src/reverse.ts +11 -7
  84. package/src/root-error-boundary.tsx +30 -26
  85. package/src/route-definition/dsl-helpers.ts +9 -6
  86. package/src/route-definition/redirect.ts +15 -3
  87. package/src/route-map-builder.ts +38 -2
  88. package/src/route-name.ts +53 -0
  89. package/src/route-types.ts +7 -0
  90. package/src/router/content-negotiation.ts +1 -1
  91. package/src/router/debug-manifest.ts +16 -3
  92. package/src/router/handler-context.ts +94 -15
  93. package/src/router/intercept-resolution.ts +6 -4
  94. package/src/router/lazy-includes.ts +4 -0
  95. package/src/router/loader-resolution.ts +1 -0
  96. package/src/router/logging.ts +100 -3
  97. package/src/router/manifest.ts +32 -3
  98. package/src/router/match-api.ts +61 -7
  99. package/src/router/match-context.ts +3 -0
  100. package/src/router/match-handlers.ts +185 -11
  101. package/src/router/match-middleware/background-revalidation.ts +65 -85
  102. package/src/router/match-middleware/cache-lookup.ts +69 -4
  103. package/src/router/match-middleware/cache-store.ts +2 -0
  104. package/src/router/match-pipelines.ts +8 -43
  105. package/src/router/middleware-types.ts +7 -0
  106. package/src/router/middleware.ts +93 -8
  107. package/src/router/pattern-matching.ts +41 -5
  108. package/src/router/prerender-match.ts +34 -6
  109. package/src/router/preview-match.ts +7 -1
  110. package/src/router/revalidation.ts +61 -2
  111. package/src/router/router-context.ts +15 -0
  112. package/src/router/router-interfaces.ts +34 -0
  113. package/src/router/router-options.ts +200 -0
  114. package/src/router/segment-resolution/fresh.ts +123 -30
  115. package/src/router/segment-resolution/helpers.ts +19 -0
  116. package/src/router/segment-resolution/loader-cache.ts +37 -146
  117. package/src/router/segment-resolution/revalidation.ts +358 -94
  118. package/src/router/segment-wrappers.ts +3 -0
  119. package/src/router/telemetry-otel.ts +299 -0
  120. package/src/router/telemetry.ts +300 -0
  121. package/src/router/timeout.ts +148 -0
  122. package/src/router/types.ts +7 -1
  123. package/src/router.ts +155 -11
  124. package/src/rsc/handler-context.ts +11 -0
  125. package/src/rsc/handler.ts +380 -88
  126. package/src/rsc/helpers.ts +25 -16
  127. package/src/rsc/loader-fetch.ts +84 -42
  128. package/src/rsc/origin-guard.ts +141 -0
  129. package/src/rsc/progressive-enhancement.ts +232 -19
  130. package/src/rsc/response-route-handler.ts +37 -26
  131. package/src/rsc/rsc-rendering.ts +12 -5
  132. package/src/rsc/runtime-warnings.ts +42 -0
  133. package/src/rsc/server-action.ts +134 -58
  134. package/src/rsc/types.ts +8 -0
  135. package/src/search-params.ts +22 -10
  136. package/src/server/context.ts +53 -5
  137. package/src/server/fetchable-loader-store.ts +11 -6
  138. package/src/server/handle-store.ts +66 -9
  139. package/src/server/loader-registry.ts +11 -46
  140. package/src/server/request-context.ts +90 -9
  141. package/src/ssr/index.tsx +63 -27
  142. package/src/static-handler.ts +7 -0
  143. package/src/theme/ThemeProvider.tsx +6 -1
  144. package/src/theme/index.ts +1 -6
  145. package/src/theme/theme-context.ts +1 -28
  146. package/src/theme/theme-script.ts +2 -1
  147. package/src/types/cache-types.ts +5 -0
  148. package/src/types/error-types.ts +3 -0
  149. package/src/types/global-namespace.ts +9 -0
  150. package/src/types/handler-context.ts +35 -13
  151. package/src/types/loader-types.ts +7 -0
  152. package/src/types/route-entry.ts +28 -0
  153. package/src/urls/include-helper.ts +49 -8
  154. package/src/urls/index.ts +1 -0
  155. package/src/urls/path-helper-types.ts +30 -12
  156. package/src/urls/path-helper.ts +17 -2
  157. package/src/urls/pattern-types.ts +21 -1
  158. package/src/urls/response-types.ts +27 -2
  159. package/src/urls/type-extraction.ts +23 -15
  160. package/src/use-loader.tsx +12 -4
  161. package/src/vite/discovery/bundle-postprocess.ts +12 -7
  162. package/src/vite/discovery/discover-routers.ts +30 -18
  163. package/src/vite/discovery/prerender-collection.ts +24 -27
  164. package/src/vite/discovery/route-types-writer.ts +7 -7
  165. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  166. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  167. package/src/vite/plugins/use-cache-transform.ts +91 -3
  168. package/src/vite/rango.ts +3 -3
  169. package/src/vite/router-discovery.ts +99 -36
  170. package/src/vite/utils/prerender-utils.ts +21 -0
  171. package/src/vite/utils/shared-utils.ts +3 -1
  172. package/src/browser/request-controller.ts +0 -164
  173. package/src/href-context.ts +0 -33
  174. package/src/router.gen.ts +0 -6
  175. package/src/static-handler.gen.ts +0 -5
  176. package/src/urls.gen.ts +0 -8
  177. /package/src/browser/{prefetch-observer.ts → prefetch/observer.ts} +0 -0
@@ -22,7 +22,11 @@ import type {
22
22
  SegmentRevalidationResult,
23
23
  ActionContext,
24
24
  } from "../types.js";
25
- import { debugLog } from "../logging.js";
25
+ import {
26
+ debugLog,
27
+ pushRevalidationTraceEntry,
28
+ isTraceActive,
29
+ } from "../logging.js";
26
30
  import { resolveLoaderData } from "./loader-cache.js";
27
31
  import {
28
32
  handleHandlerResult,
@@ -31,6 +35,84 @@ import {
31
35
  resolveLayoutComponent,
32
36
  resolveWithErrorBoundary,
33
37
  } from "./helpers.js";
38
+ import { getRouterContext } from "../router-context.js";
39
+ import { resolveSink, safeEmit } from "../telemetry.js";
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Telemetry helpers
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /**
46
+ * Attach a fire-and-forget rejection observer to a streamed handler promise.
47
+ * Silently no-ops when called outside RouterContext (e.g. in unit tests).
48
+ */
49
+ function observeStreamedHandler(
50
+ promise: Promise<ReactNode>,
51
+ segmentId: string,
52
+ segmentType: string,
53
+ pathname?: string,
54
+ routeKey?: string,
55
+ params?: Record<string, string>,
56
+ ): void {
57
+ let routerCtx;
58
+ try {
59
+ routerCtx = getRouterContext();
60
+ } catch {
61
+ return;
62
+ }
63
+ if (!routerCtx?.telemetry) return;
64
+ const sink = resolveSink(routerCtx.telemetry);
65
+ const reqId = routerCtx.requestId;
66
+ promise.catch((err: unknown) => {
67
+ const errorObj = err instanceof Error ? err : new Error(String(err));
68
+ safeEmit(sink, {
69
+ type: "handler.error",
70
+ timestamp: performance.now(),
71
+ requestId: reqId,
72
+ segmentId,
73
+ segmentType,
74
+ error: errorObj,
75
+ handledByBoundary: true,
76
+ pathname,
77
+ routeKey,
78
+ params,
79
+ });
80
+ });
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Revalidation telemetry helper
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * Emit revalidation.decision telemetry for a segment if a sink is configured.
89
+ * Called after evaluateRevalidation returns to capture the decision.
90
+ * Silently no-ops when called outside RouterContext (e.g. in unit tests).
91
+ */
92
+ function emitRevalidationDecision(
93
+ segmentId: string,
94
+ pathname: string,
95
+ routeKey: string,
96
+ shouldRevalidate: boolean,
97
+ ): void {
98
+ let routerCtx;
99
+ try {
100
+ routerCtx = getRouterContext();
101
+ } catch {
102
+ return;
103
+ }
104
+ if (routerCtx?.telemetry) {
105
+ safeEmit(resolveSink(routerCtx.telemetry), {
106
+ type: "revalidation.decision",
107
+ timestamp: performance.now(),
108
+ requestId: routerCtx.requestId,
109
+ segmentId,
110
+ pathname,
111
+ routeKey,
112
+ shouldRevalidate,
113
+ });
114
+ }
115
+ }
34
116
 
35
117
  // ---------------------------------------------------------------------------
36
118
  // Revalidation path (partial match)
@@ -81,7 +163,20 @@ export async function resolveLoadersWithRevalidation<TEnv>(
81
163
  }) => {
82
164
  const shouldRun = await revalidate(
83
165
  async () => {
84
- if (!clientSegmentIds.has(segmentId)) return true;
166
+ if (!clientSegmentIds.has(segmentId)) {
167
+ if (isTraceActive()) {
168
+ pushRevalidationTraceEntry({
169
+ segmentId,
170
+ segmentType: "loader",
171
+ belongsToRoute,
172
+ source: "loader",
173
+ defaultShouldRevalidate: true,
174
+ finalShouldRevalidate: true,
175
+ reason: "new-segment",
176
+ });
177
+ }
178
+ return true;
179
+ }
85
180
 
86
181
  const dummySegment: ResolvedSegment = {
87
182
  id: segmentId,
@@ -109,11 +204,13 @@ export async function resolveLoadersWithRevalidation<TEnv>(
109
204
  context: ctx,
110
205
  actionContext,
111
206
  stale,
207
+ traceSource: "loader",
112
208
  });
113
209
  },
114
210
  async () => true,
115
211
  () => false,
116
212
  );
213
+ emitRevalidationDecision(segmentId, ctx.pathname, routeKey, shouldRun);
117
214
  return { shouldRun, loaderEntry, loader, segmentId, index };
118
215
  },
119
216
  ),
@@ -156,6 +253,7 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
156
253
  routeKey: string,
157
254
  deps: SegmentResolutionDeps<TEnv>,
158
255
  actionContext?: ActionContext,
256
+ stale?: boolean,
159
257
  ): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
160
258
  const allLoaderSegments: ResolvedSegment[] = [];
161
259
  const allMatchedIds: string[] = [];
@@ -174,6 +272,8 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
174
272
  routeKey,
175
273
  deps,
176
274
  actionContext,
275
+ undefined, // shortCodeOverride
276
+ stale,
177
277
  );
178
278
  allLoaderSegments.push(...segments);
179
279
  allMatchedIds.push(...matchedIds);
@@ -279,9 +379,35 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
279
379
  }
280
380
 
281
381
  const shouldResolve = await (async () => {
282
- if (isFullRefetch) return true;
283
- if (!clientSegmentIds.has(parallelId))
284
- return belongsToRoute || isNewParent;
382
+ if (isFullRefetch) {
383
+ if (isTraceActive()) {
384
+ pushRevalidationTraceEntry({
385
+ segmentId: parallelId,
386
+ segmentType: "parallel",
387
+ belongsToRoute,
388
+ source: "parallel",
389
+ defaultShouldRevalidate: true,
390
+ finalShouldRevalidate: true,
391
+ reason: "full-refetch",
392
+ });
393
+ }
394
+ return true;
395
+ }
396
+ if (!clientSegmentIds.has(parallelId)) {
397
+ const result = belongsToRoute || isNewParent;
398
+ if (isTraceActive()) {
399
+ pushRevalidationTraceEntry({
400
+ segmentId: parallelId,
401
+ segmentType: "parallel",
402
+ belongsToRoute,
403
+ source: "parallel",
404
+ defaultShouldRevalidate: result,
405
+ finalShouldRevalidate: result,
406
+ reason: result ? "new-segment" : "skip-parent-chain",
407
+ });
408
+ }
409
+ return result;
410
+ }
285
411
 
286
412
  const dummySegment: ResolvedSegment = {
287
413
  id: parallelId,
@@ -313,8 +439,15 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
313
439
  context,
314
440
  actionContext,
315
441
  stale,
442
+ traceSource: "parallel",
316
443
  });
317
444
  })();
445
+ emitRevalidationDecision(
446
+ parallelId,
447
+ context.pathname,
448
+ routeKey,
449
+ shouldResolve,
450
+ );
318
451
 
319
452
  let component: ReactNode | undefined;
320
453
  if (shouldResolve) {
@@ -327,9 +460,25 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
327
460
  if (!shouldResolve) {
328
461
  component = null;
329
462
  } else if (hasLoadingFallback) {
330
- component = (
331
- typeof handler === "function" ? handler(context) : handler
332
- ) as ReactNode;
463
+ const result =
464
+ typeof handler === "function" ? handler(context) : handler;
465
+ if (result instanceof Promise) {
466
+ const tracked = deps.trackHandler(result, {
467
+ segmentId: parallelId,
468
+ segmentType: "parallel",
469
+ });
470
+ observeStreamedHandler(
471
+ tracked,
472
+ parallelId,
473
+ "parallel",
474
+ context.pathname,
475
+ routeKey,
476
+ params,
477
+ );
478
+ component = tracked as ReactNode;
479
+ } else {
480
+ component = result as ReactNode;
481
+ }
333
482
  } else {
334
483
  component =
335
484
  typeof handler === "function" ? await handler(context) : handler;
@@ -407,7 +556,24 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
407
556
  clientHasSegment: hasSegment,
408
557
  belongsToRoute,
409
558
  });
410
- if (!hasSegment) return true;
559
+ if (!hasSegment) {
560
+ if (isTraceActive()) {
561
+ const segType =
562
+ entry.type === "cache"
563
+ ? "layout"
564
+ : (entry.type as "layout" | "route");
565
+ pushRevalidationTraceEntry({
566
+ segmentId: entry.shortCode,
567
+ segmentType: segType,
568
+ belongsToRoute,
569
+ source: "segment-resolution",
570
+ defaultShouldRevalidate: true,
571
+ finalShouldRevalidate: true,
572
+ reason: "new-segment",
573
+ });
574
+ }
575
+ return true;
576
+ }
411
577
 
412
578
  const dummySegment: ResolvedSegment = {
413
579
  id: entry.shortCode,
@@ -442,6 +608,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
442
608
  actionContext,
443
609
  stale,
444
610
  });
611
+ emitRevalidationDecision(
612
+ entry.shortCode,
613
+ context.pathname,
614
+ routeKey,
615
+ shouldRevalidate,
616
+ );
445
617
  debugLog("segment.revalidate", "entry revalidation decision", {
446
618
  segmentId: entry.shortCode,
447
619
  shouldRevalidate,
@@ -462,10 +634,22 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
462
634
  }
463
635
  if (!actionContext) {
464
636
  const result = handleHandlerResult(routeEntry.handler(context));
465
- return {
466
- content:
467
- result instanceof Promise ? deps.trackHandler(result) : result,
468
- };
637
+ if (result instanceof Promise) {
638
+ const tracked = deps.trackHandler(result, {
639
+ segmentId: entry.shortCode,
640
+ segmentType: entry.type,
641
+ });
642
+ observeStreamedHandler(
643
+ tracked,
644
+ entry.shortCode,
645
+ entry.type,
646
+ context.pathname,
647
+ routeKey,
648
+ params,
649
+ );
650
+ return { content: tracked };
651
+ }
652
+ return { content: result };
469
653
  }
470
654
  debugLog("segment.action", "resolving action route with awaited value", {
471
655
  entryId: entry.id,
@@ -590,31 +774,32 @@ export async function resolveSegmentWithRevalidation<TEnv>(
590
774
  }
591
775
  }
592
776
 
593
- const parallelResult = await resolveParallelSegmentsWithRevalidation(
594
- entry,
595
- params,
596
- context,
597
- belongsToRoute,
598
- clientSegmentIds,
599
- prevParams,
600
- request,
601
- prevUrl,
602
- nextUrl,
603
- routeKey,
604
- deps,
605
- actionContext,
606
- stale,
607
- );
608
- segments.push(...parallelResult.segments);
609
- matchedIds.push(...parallelResult.matchedIds);
610
-
611
- // Push handler BEFORE orphan layouts for layout/cache entries (matching SSR
612
- // order in resolveSegment). Route handler was already executed and is pushed
613
- // after children for tree composition.
614
777
  if (routeHandlerResult) {
778
+ // Route entry: handler already executed above; resolve parallels
779
+ // (handler data visible) then push handler segment last for tree order.
780
+ const parallelResult = await resolveParallelSegmentsWithRevalidation(
781
+ entry,
782
+ params,
783
+ context,
784
+ belongsToRoute,
785
+ clientSegmentIds,
786
+ prevParams,
787
+ request,
788
+ prevUrl,
789
+ nextUrl,
790
+ routeKey,
791
+ deps,
792
+ actionContext,
793
+ stale,
794
+ );
795
+ segments.push(...parallelResult.segments);
796
+ matchedIds.push(...parallelResult.matchedIds);
797
+
615
798
  segments.push(routeHandlerResult.segment);
616
799
  matchedIds.push(routeHandlerResult.matchedId);
617
800
  } else {
801
+ // Layout/cache entry: handler-first — resolve handler before parallels
802
+ // so ctx.set() values are visible to parallel children.
618
803
  const handlerResult = await resolveEntryHandlerWithRevalidation(
619
804
  entry,
620
805
  params,
@@ -632,9 +817,25 @@ export async function resolveSegmentWithRevalidation<TEnv>(
632
817
  );
633
818
  segments.push(handlerResult.segment);
634
819
  matchedIds.push(handlerResult.matchedId);
635
- }
636
820
 
637
- if (entry.type === "layout" || entry.type === "cache") {
821
+ const parallelResult = await resolveParallelSegmentsWithRevalidation(
822
+ entry,
823
+ params,
824
+ context,
825
+ belongsToRoute,
826
+ clientSegmentIds,
827
+ prevParams,
828
+ request,
829
+ prevUrl,
830
+ nextUrl,
831
+ routeKey,
832
+ deps,
833
+ actionContext,
834
+ stale,
835
+ );
836
+ segments.push(...parallelResult.segments);
837
+ matchedIds.push(...parallelResult.matchedIds);
838
+
638
839
  for (const orphan of entry.layout) {
639
840
  const orphanResult = await resolveOrphanLayoutWithRevalidation(
640
841
  orphan,
@@ -705,6 +906,82 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
705
906
  segments.push(...loaderResult.segments);
706
907
  matchedIds.push(...loaderResult.matchedIds);
707
908
 
909
+ // Handler-first: resolve orphan layout handler before its parallels
910
+ // so ctx.set() values are visible to parallel children.
911
+ matchedIds.push(orphan.shortCode);
912
+
913
+ const component = await revalidate(
914
+ async () => {
915
+ if (!clientSegmentIds.has(orphan.shortCode)) {
916
+ if (isTraceActive()) {
917
+ pushRevalidationTraceEntry({
918
+ segmentId: orphan.shortCode,
919
+ segmentType: "layout",
920
+ belongsToRoute,
921
+ source: "orphan-layout",
922
+ defaultShouldRevalidate: true,
923
+ finalShouldRevalidate: true,
924
+ reason: "new-segment",
925
+ });
926
+ }
927
+ return true;
928
+ }
929
+
930
+ const dummySegment: ResolvedSegment = {
931
+ id: orphan.shortCode,
932
+ namespace: orphan.id,
933
+ type: "layout",
934
+ index: 0,
935
+ component: null as any,
936
+ params,
937
+ belongsToRoute,
938
+ layoutName: orphan.id,
939
+ ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
940
+ };
941
+
942
+ const shouldRevalidate = await evaluateRevalidation({
943
+ segment: dummySegment,
944
+ prevParams,
945
+ getPrevSegment: null,
946
+ request,
947
+ prevUrl,
948
+ nextUrl,
949
+ revalidations: orphan.revalidate.map((fn, i) => ({
950
+ name: `revalidate${i}`,
951
+ fn,
952
+ })),
953
+ routeKey,
954
+ context,
955
+ actionContext,
956
+ stale,
957
+ traceSource: "orphan-layout",
958
+ });
959
+ emitRevalidationDecision(
960
+ orphan.shortCode,
961
+ context.pathname,
962
+ routeKey,
963
+ shouldRevalidate,
964
+ );
965
+ return shouldRevalidate;
966
+ },
967
+ async () => resolveLayoutComponent(orphan, context),
968
+ () => null,
969
+ );
970
+
971
+ segments.push({
972
+ id: orphan.shortCode,
973
+ namespace: orphan.id,
974
+ type: "layout",
975
+ index: 0,
976
+ component,
977
+ params,
978
+ belongsToRoute,
979
+ layoutName: orphan.id,
980
+ loading: orphan.loading === false ? null : orphan.loading,
981
+ transition: orphan.transition,
982
+ ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
983
+ });
984
+
708
985
  for (const parallelEntry of orphan.parallel) {
709
986
  invariant(
710
987
  parallelEntry.type === "parallel",
@@ -743,7 +1020,20 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
743
1020
  matchedIds.push(parallelId);
744
1021
 
745
1022
  const shouldResolve = await (async () => {
746
- if (!clientSegmentIds.has(parallelId)) return true;
1023
+ if (!clientSegmentIds.has(parallelId)) {
1024
+ if (isTraceActive()) {
1025
+ pushRevalidationTraceEntry({
1026
+ segmentId: parallelId,
1027
+ segmentType: "parallel",
1028
+ belongsToRoute,
1029
+ source: "parallel",
1030
+ defaultShouldRevalidate: true,
1031
+ finalShouldRevalidate: true,
1032
+ reason: "new-segment",
1033
+ });
1034
+ }
1035
+ return true;
1036
+ }
747
1037
 
748
1038
  const dummySegment: ResolvedSegment = {
749
1039
  id: parallelId,
@@ -775,8 +1065,15 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
775
1065
  context,
776
1066
  actionContext,
777
1067
  stale,
1068
+ traceSource: "parallel",
778
1069
  });
779
1070
  })();
1071
+ emitRevalidationDecision(
1072
+ parallelId,
1073
+ context.pathname,
1074
+ routeKey,
1075
+ shouldResolve,
1076
+ );
780
1077
 
781
1078
  let component: ReactNode | undefined;
782
1079
  if (shouldResolve) {
@@ -789,9 +1086,25 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
789
1086
  if (!shouldResolve) {
790
1087
  component = null;
791
1088
  } else if (hasLoadingFallback) {
792
- component = (
793
- typeof handler === "function" ? handler(context) : handler
794
- ) as ReactNode;
1089
+ const result =
1090
+ typeof handler === "function" ? handler(context) : handler;
1091
+ if (result instanceof Promise) {
1092
+ const tracked = deps.trackHandler(result, {
1093
+ segmentId: parallelId,
1094
+ segmentType: "parallel",
1095
+ });
1096
+ observeStreamedHandler(
1097
+ tracked,
1098
+ parallelId,
1099
+ "parallel",
1100
+ context.pathname,
1101
+ routeKey,
1102
+ params,
1103
+ );
1104
+ component = tracked as ReactNode;
1105
+ } else {
1106
+ component = result as ReactNode;
1107
+ }
795
1108
  } else {
796
1109
  component =
797
1110
  typeof handler === "function" ? await handler(context) : handler;
@@ -817,59 +1130,6 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
817
1130
  }
818
1131
  }
819
1132
 
820
- matchedIds.push(orphan.shortCode);
821
-
822
- const component = await revalidate(
823
- async () => {
824
- if (!clientSegmentIds.has(orphan.shortCode)) return true;
825
-
826
- const dummySegment: ResolvedSegment = {
827
- id: orphan.shortCode,
828
- namespace: orphan.id,
829
- type: "layout",
830
- index: 0,
831
- component: null as any,
832
- params,
833
- belongsToRoute,
834
- layoutName: orphan.id,
835
- ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
836
- };
837
-
838
- return await evaluateRevalidation({
839
- segment: dummySegment,
840
- prevParams,
841
- getPrevSegment: null,
842
- request,
843
- prevUrl,
844
- nextUrl,
845
- revalidations: orphan.revalidate.map((fn, i) => ({
846
- name: `revalidate${i}`,
847
- fn,
848
- })),
849
- routeKey,
850
- context,
851
- actionContext,
852
- stale,
853
- });
854
- },
855
- async () => resolveLayoutComponent(orphan, context),
856
- () => null,
857
- );
858
-
859
- segments.push({
860
- id: orphan.shortCode,
861
- namespace: orphan.id,
862
- type: "layout",
863
- index: 0,
864
- component,
865
- params,
866
- belongsToRoute,
867
- layoutName: orphan.id,
868
- loading: orphan.loading === false ? null : orphan.loading,
869
- transition: orphan.transition,
870
- ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
871
- });
872
-
873
1133
  return { segments, matchedIds };
874
1134
  }
875
1135
 
@@ -898,6 +1158,8 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
898
1158
  const seenSegIds = new Set<string>();
899
1159
  const seenMatchIds = new Set<string>();
900
1160
 
1161
+ const telemetry = getRouterContext()?.telemetry;
1162
+
901
1163
  for (const entry of entries) {
902
1164
  if (entry.type === "route" && interceptResult) {
903
1165
  debugLog(
@@ -937,7 +1199,9 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
937
1199
  ),
938
1200
  (seg) => ({ segments: [seg], matchedIds: [seg.id] }),
939
1201
  deps,
940
- undefined,
1202
+ telemetry
1203
+ ? { request, url: context.url, routeKey, isPartial: true, telemetry }
1204
+ : undefined,
941
1205
  pathname,
942
1206
  );
943
1207
 
@@ -50,6 +50,7 @@ export interface SegmentWrappers<TEnv = any> {
50
50
  actionResult?: any;
51
51
  formData?: FormData;
52
52
  },
53
+ stale?: boolean,
53
54
  ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
54
55
  buildEntryRevalidateMap: (
55
56
  entries: EntryData[],
@@ -158,6 +159,7 @@ export function createSegmentWrappers<TEnv = any>(
158
159
  actionResult?: any;
159
160
  formData?: FormData;
160
161
  },
162
+ stale?: boolean,
161
163
  ): ReturnType<typeof _resolveLoadersOnlyWithRevalidation> {
162
164
  return _resolveLoadersOnlyWithRevalidation(
163
165
  entries,
@@ -170,6 +172,7 @@ export function createSegmentWrappers<TEnv = any>(
170
172
  routeKey,
171
173
  segmentDeps,
172
174
  actionContext,
175
+ stale,
173
176
  );
174
177
  }
175
178