@timber-js/app 0.2.0-alpha.52 → 0.2.0-alpha.53
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/_chunks/{segment-context-DBn-nrMN.js → segment-context-Bmugn-ao.js} +8 -5
- package/dist/_chunks/segment-context-Bmugn-ao.js.map +1 -0
- package/dist/client/index.js +23 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +13 -0
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/server/default-logger.d.ts.map +1 -1
- package/dist/server/flight-scripts.d.ts +5 -2
- package/dist/server/flight-scripts.d.ts.map +1 -1
- package/dist/server/index.js +1 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +1 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +11 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +12 -8
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-error-bridge.d.ts +12 -0
- package/dist/server/rsc-entry/ssr-error-bridge.d.ts.map +1 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts +12 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-error-entry.d.ts +65 -0
- package/dist/server/ssr-error-entry.d.ts.map +1 -0
- package/dist/shared/merge-search-params.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +7 -6
- package/src/client/router.ts +13 -1
- package/src/client/rsc-fetch.ts +25 -0
- package/src/plugins/routing.ts +7 -1
- package/src/server/default-logger.ts +4 -3
- package/src/server/flight-scripts.ts +6 -3
- package/src/server/logger.ts +6 -1
- package/src/server/pipeline.ts +1 -1
- package/src/server/rsc-entry/error-renderer.ts +201 -45
- package/src/server/rsc-entry/helpers.ts +12 -2
- package/src/server/rsc-entry/index.ts +4 -1
- package/src/server/rsc-entry/rsc-payload.ts +37 -4
- package/src/server/rsc-entry/rsc-stream.ts +21 -2
- package/src/server/rsc-entry/ssr-error-bridge.ts +20 -0
- package/src/server/rsc-entry/ssr-renderer.ts +79 -14
- package/src/server/slot-resolver.ts +7 -1
- package/src/server/ssr-error-entry.ts +237 -0
- package/src/shared/merge-search-params.ts +11 -4
- package/src/shims/navigation.ts +1 -0
- package/LICENSE +0 -8
- package/dist/_chunks/segment-context-DBn-nrMN.js.map +0 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR Error Entry — Renders error pages directly through Fizz (no RSC).
|
|
3
|
+
*
|
|
4
|
+
* Error pages bypass the RSC Flight pipeline because Error objects are not
|
|
5
|
+
* RSC-serializable (React Flight throws "Only plain objects can be passed
|
|
6
|
+
* to Client Components"). Instead, this entry receives component module
|
|
7
|
+
* paths, imports them in the SSR environment, builds the element tree,
|
|
8
|
+
* and renders through Fizz.
|
|
9
|
+
*
|
|
10
|
+
* Layout components may be server components that import 'use client'
|
|
11
|
+
* children — in the SSR environment, client references resolve to actual
|
|
12
|
+
* component modules (unlike the RSC environment where they're opaque
|
|
13
|
+
* reference objects). This is why we import in SSR, not pass from RSC.
|
|
14
|
+
*
|
|
15
|
+
* Design docs: 10-error-handling.md §"Three-Tier Error Page Rendering"
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// @ts-expect-error — virtual module provided by timber-entries plugin
|
|
19
|
+
import config from 'virtual:timber-config';
|
|
20
|
+
import { createElement, type ReactNode } from 'react';
|
|
21
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
renderSsrStream,
|
|
25
|
+
renderSsrNodeStream,
|
|
26
|
+
nodeReadableToWeb,
|
|
27
|
+
useNodeStreams,
|
|
28
|
+
buildSsrResponse,
|
|
29
|
+
} from './ssr-render.js';
|
|
30
|
+
import { logRenderError } from './logger.js';
|
|
31
|
+
import { createBufferedTransformStream, injectHead } from './html-injectors.js';
|
|
32
|
+
import { wrapSsrElement } from './ssr-wrappers.js';
|
|
33
|
+
import { registerSsrDataProvider, type SsrData } from '#/client/ssr-data.js';
|
|
34
|
+
import { setCurrentParams } from '#/client/use-params.js';
|
|
35
|
+
import { withSpan } from './tracing.js';
|
|
36
|
+
|
|
37
|
+
// ─── SSR Data ALS (shared pattern with ssr-entry.ts) ──────────────────────
|
|
38
|
+
|
|
39
|
+
const ssrDataAls = new AsyncLocalStorage<SsrData>();
|
|
40
|
+
registerSsrDataProvider(() => ssrDataAls.getStore());
|
|
41
|
+
|
|
42
|
+
// Pre-import Node.js stream modules at module load time (same as ssr-entry.ts).
|
|
43
|
+
let _nodeStreamImports: {
|
|
44
|
+
createNodeBufferedTransform: typeof import('./node-stream-transforms.js').createNodeBufferedTransform;
|
|
45
|
+
createNodeHeadInjector: typeof import('./node-stream-transforms.js').createNodeHeadInjector;
|
|
46
|
+
createNodeMoveSuffixTransform: typeof import('./node-stream-transforms.js').createNodeMoveSuffixTransform;
|
|
47
|
+
createNodeErrorHandler: typeof import('./node-stream-transforms.js').createNodeErrorHandler;
|
|
48
|
+
pipeline: typeof import('node:stream/promises').pipeline;
|
|
49
|
+
PassThrough: typeof import('node:stream').PassThrough;
|
|
50
|
+
} | null = null;
|
|
51
|
+
|
|
52
|
+
if (useNodeStreams) {
|
|
53
|
+
try {
|
|
54
|
+
const [transforms, streamPromises, stream] = await Promise.all([
|
|
55
|
+
import('./node-stream-transforms.js'),
|
|
56
|
+
import('node:stream/promises'),
|
|
57
|
+
import('node:stream'),
|
|
58
|
+
]);
|
|
59
|
+
_nodeStreamImports = {
|
|
60
|
+
createNodeBufferedTransform: transforms.createNodeBufferedTransform,
|
|
61
|
+
createNodeHeadInjector: transforms.createNodeHeadInjector,
|
|
62
|
+
createNodeMoveSuffixTransform: transforms.createNodeMoveSuffixTransform,
|
|
63
|
+
createNodeErrorHandler: transforms.createNodeErrorHandler,
|
|
64
|
+
pipeline: streamPromises.pipeline,
|
|
65
|
+
PassThrough: stream.PassThrough,
|
|
66
|
+
};
|
|
67
|
+
} catch {
|
|
68
|
+
// Fall back to Web Streams path
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Context for SSR-only error page rendering.
|
|
74
|
+
* Subset of NavContext — no RSC stream, no Flight injection.
|
|
75
|
+
*/
|
|
76
|
+
export interface ErrorPageContext {
|
|
77
|
+
/** The requested pathname */
|
|
78
|
+
pathname: string;
|
|
79
|
+
/** Extracted route params */
|
|
80
|
+
params: Record<string, string | string[]>;
|
|
81
|
+
/** Search params from the URL */
|
|
82
|
+
searchParams: Record<string, string>;
|
|
83
|
+
/** The committed HTTP status code */
|
|
84
|
+
statusCode: number;
|
|
85
|
+
/** Response headers from middleware/proxy */
|
|
86
|
+
responseHeaders: Headers;
|
|
87
|
+
/** Pre-rendered metadata HTML to inject before </head> */
|
|
88
|
+
headHtml: string;
|
|
89
|
+
/** Inline JS for React's bootstrapScriptContent */
|
|
90
|
+
bootstrapScriptContent: string;
|
|
91
|
+
/** Request abort signal */
|
|
92
|
+
signal?: AbortSignal;
|
|
93
|
+
/** Request cookies for useCookie() during SSR */
|
|
94
|
+
cookies?: Map<string, string>;
|
|
95
|
+
/** Error component props: { error, digest, reset } */
|
|
96
|
+
errorProps: {
|
|
97
|
+
error: Error;
|
|
98
|
+
digest: { code: string; data: unknown } | null;
|
|
99
|
+
reset: undefined;
|
|
100
|
+
};
|
|
101
|
+
/** File path of the error component module (imported in SSR env) */
|
|
102
|
+
errorComponentPath: string;
|
|
103
|
+
/** File paths of layout component modules, ordered root → leaf */
|
|
104
|
+
layoutPaths: string[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Render an error page directly through Fizz (no RSC Flight stream).
|
|
109
|
+
*
|
|
110
|
+
* Imports the error component and layout components in the SSR environment,
|
|
111
|
+
* builds the element tree, wraps with SSR-specific components (for useId
|
|
112
|
+
* matching), and renders to HTML.
|
|
113
|
+
*
|
|
114
|
+
* Key difference from handleSsr: NO RSC stream decode, NO Flight injection.
|
|
115
|
+
*/
|
|
116
|
+
export async function handleSsrErrorPage(ctx: ErrorPageContext): Promise<Response> {
|
|
117
|
+
return withSpan('timber.ssr.error-page', { 'timber.environment': 'ssr' }, async () => {
|
|
118
|
+
const _runtimeConfig = config;
|
|
119
|
+
|
|
120
|
+
const ssrData: SsrData = {
|
|
121
|
+
pathname: ctx.pathname,
|
|
122
|
+
searchParams: ctx.searchParams,
|
|
123
|
+
cookies: ctx.cookies ?? new Map(),
|
|
124
|
+
params: ctx.params,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return ssrDataAls.run(ssrData, async () => {
|
|
128
|
+
setCurrentParams(ctx.params);
|
|
129
|
+
|
|
130
|
+
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
131
|
+
|
|
132
|
+
// Import error component in SSR environment.
|
|
133
|
+
// In SSR, 'use client' components resolve to actual module functions
|
|
134
|
+
// (not opaque client references like in RSC).
|
|
135
|
+
const errorMod = (await import(/* @vite-ignore */ ctx.errorComponentPath)) as Record<
|
|
136
|
+
string,
|
|
137
|
+
unknown
|
|
138
|
+
>;
|
|
139
|
+
const ErrorComponent = errorMod.default as (...args: unknown[]) => ReactNode;
|
|
140
|
+
|
|
141
|
+
// Build innermost element: error component with props
|
|
142
|
+
let element: React.ReactElement = h(ErrorComponent, ctx.errorProps);
|
|
143
|
+
|
|
144
|
+
// Import and wrap with layouts (root → leaf order, wrap inside-out)
|
|
145
|
+
for (let i = ctx.layoutPaths.length - 1; i >= 0; i--) {
|
|
146
|
+
const layoutMod = (await import(/* @vite-ignore */ ctx.layoutPaths[i])) as Record<
|
|
147
|
+
string,
|
|
148
|
+
unknown
|
|
149
|
+
>;
|
|
150
|
+
const LayoutComponent = layoutMod.default as (...args: unknown[]) => ReactNode;
|
|
151
|
+
element = h(LayoutComponent, null, element);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Wrap with SSR-specific components for useId matching.
|
|
155
|
+
const hasTopLoader = _runtimeConfig.topLoader?.enabled !== false;
|
|
156
|
+
const wrappedElement = wrapSsrElement(element, ctx.searchParams, hasTopLoader);
|
|
157
|
+
|
|
158
|
+
// Render to HTML — same dual-path as ssr-entry.ts but NO Flight injection.
|
|
159
|
+
if (_nodeStreamImports) {
|
|
160
|
+
return renderViaNodeStreams(wrappedElement, ctx, _runtimeConfig);
|
|
161
|
+
}
|
|
162
|
+
return renderViaWebStreams(wrappedElement, ctx, _runtimeConfig);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Render Paths ─────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
async function renderViaNodeStreams(
|
|
170
|
+
element: ReactNode,
|
|
171
|
+
ctx: ErrorPageContext,
|
|
172
|
+
runtimeConfig: Record<string, unknown>
|
|
173
|
+
): Promise<Response> {
|
|
174
|
+
const {
|
|
175
|
+
createNodeBufferedTransform,
|
|
176
|
+
createNodeHeadInjector,
|
|
177
|
+
createNodeMoveSuffixTransform,
|
|
178
|
+
createNodeErrorHandler,
|
|
179
|
+
pipeline,
|
|
180
|
+
PassThrough,
|
|
181
|
+
} = _nodeStreamImports!;
|
|
182
|
+
|
|
183
|
+
const renderTimeoutMs = (runtimeConfig.renderTimeoutMs as number | undefined) ?? undefined;
|
|
184
|
+
|
|
185
|
+
let nodeHtmlStream: import('node:stream').Readable;
|
|
186
|
+
try {
|
|
187
|
+
nodeHtmlStream = await renderSsrNodeStream(element, {
|
|
188
|
+
bootstrapScriptContent: ctx.bootstrapScriptContent || undefined,
|
|
189
|
+
signal: ctx.signal,
|
|
190
|
+
renderTimeoutMs,
|
|
191
|
+
});
|
|
192
|
+
} catch (renderError) {
|
|
193
|
+
logRenderError({ method: '', path: '', error: renderError });
|
|
194
|
+
return new Response(null, { status: ctx.statusCode, headers: ctx.responseHeaders });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Pipeline: buffer → errorHandler → headInjector → moveSuffix → output
|
|
198
|
+
// NO flightInjector — there's no RSC stream to inline.
|
|
199
|
+
const bufferedTransform = createNodeBufferedTransform();
|
|
200
|
+
const errorHandler = createNodeErrorHandler(ctx.signal);
|
|
201
|
+
const headInjector = createNodeHeadInjector(ctx.headHtml);
|
|
202
|
+
const moveSuffix = createNodeMoveSuffixTransform();
|
|
203
|
+
|
|
204
|
+
const output = new PassThrough();
|
|
205
|
+
pipeline(nodeHtmlStream, bufferedTransform, errorHandler, headInjector, moveSuffix, output).catch(
|
|
206
|
+
() => {}
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const webStream = nodeReadableToWeb(output);
|
|
210
|
+
return buildSsrResponse(webStream, ctx.statusCode, ctx.responseHeaders);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function renderViaWebStreams(
|
|
214
|
+
element: ReactNode,
|
|
215
|
+
ctx: ErrorPageContext,
|
|
216
|
+
runtimeConfig: Record<string, unknown>
|
|
217
|
+
): Promise<Response> {
|
|
218
|
+
const renderTimeoutMs = (runtimeConfig.renderTimeoutMs as number | undefined) ?? undefined;
|
|
219
|
+
|
|
220
|
+
let htmlStream: ReadableStream<Uint8Array>;
|
|
221
|
+
try {
|
|
222
|
+
htmlStream = await renderSsrStream(element, {
|
|
223
|
+
bootstrapScriptContent: ctx.bootstrapScriptContent || undefined,
|
|
224
|
+
signal: ctx.signal,
|
|
225
|
+
renderTimeoutMs,
|
|
226
|
+
});
|
|
227
|
+
} catch (renderError) {
|
|
228
|
+
logRenderError({ method: '', path: '', error: renderError });
|
|
229
|
+
return new Response(null, { status: ctx.statusCode, headers: ctx.responseHeaders });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let outputStream = htmlStream.pipeThrough(createBufferedTransformStream());
|
|
233
|
+
outputStream = injectHead(outputStream, ctx.headHtml);
|
|
234
|
+
return buildSsrResponse(outputStream, ctx.statusCode, ctx.responseHeaders);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default handleSsrErrorPage;
|
|
@@ -27,10 +27,16 @@ export function mergePreservedSearchParams(
|
|
|
27
27
|
const currentParams = new URLSearchParams(currentSearch);
|
|
28
28
|
if (currentParams.size === 0) return targetHref;
|
|
29
29
|
|
|
30
|
+
// Split hash fragment from target before processing query params.
|
|
31
|
+
// Hash must come after query string: /path?query=value#hash
|
|
32
|
+
const hashIdx = targetHref.indexOf('#');
|
|
33
|
+
const hrefWithoutHash = hashIdx >= 0 ? targetHref.slice(0, hashIdx) : targetHref;
|
|
34
|
+
const hash = hashIdx >= 0 ? targetHref.slice(hashIdx) : '';
|
|
35
|
+
|
|
30
36
|
// Split target into path and existing query
|
|
31
|
-
const qIdx =
|
|
32
|
-
const targetPath = qIdx >= 0 ?
|
|
33
|
-
const targetParams = new URLSearchParams(qIdx >= 0 ?
|
|
37
|
+
const qIdx = hrefWithoutHash.indexOf('?');
|
|
38
|
+
const targetPath = qIdx >= 0 ? hrefWithoutHash.slice(0, qIdx) : hrefWithoutHash;
|
|
39
|
+
const targetParams = new URLSearchParams(qIdx >= 0 ? hrefWithoutHash.slice(qIdx + 1) : '');
|
|
34
40
|
|
|
35
41
|
// Collect params to preserve (that aren't already in the target)
|
|
36
42
|
const merged = new URLSearchParams(targetParams);
|
|
@@ -44,5 +50,6 @@ export function mergePreservedSearchParams(
|
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
const qs = merged.toString();
|
|
47
|
-
|
|
53
|
+
const pathWithQuery = qs ? `${targetPath}?${qs}` : targetPath;
|
|
54
|
+
return pathWithQuery + hash;
|
|
48
55
|
}
|
package/src/shims/navigation.ts
CHANGED
package/LICENSE
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
DONTFUCKINGUSE LICENSE
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Daniel Saewitz
|
|
4
|
-
|
|
5
|
-
This software may not be used, copied, modified, merged, published,
|
|
6
|
-
distributed, sublicensed, or sold by anyone other than the copyright holder.
|
|
7
|
-
|
|
8
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"segment-context-DBn-nrMN.js","names":[],"sources":["../../src/shared/merge-search-params.ts","../../src/client/segment-context.ts"],"sourcesContent":["/**\n * Shared utility for merging preserved search params into a target URL.\n *\n * Used by both <Link> (client) and redirect() (server). Extracted to a shared\n * module to avoid importing client code ('use client') from server modules.\n */\n\n/**\n * Merge preserved search params from the current URL into a target href.\n *\n * When `preserve` is `true`, all current search params are merged.\n * When `preserve` is a `string[]`, only the named params are merged.\n *\n * The target href's own search params take precedence — preserved params\n * are only added if the target doesn't already define them.\n *\n * @param targetHref - The resolved target href (may already contain query string)\n * @param currentSearch - The current URL's search string (e.g. \"?private=access&page=2\")\n * @param preserve - `true` to preserve all, or `string[]` to preserve specific params\n * @returns The target href with preserved search params merged in\n */\nexport function mergePreservedSearchParams(\n targetHref: string,\n currentSearch: string,\n preserve: true | string[]\n): string {\n const currentParams = new URLSearchParams(currentSearch);\n if (currentParams.size === 0) return targetHref;\n\n // Split target into path and existing query\n const qIdx = targetHref.indexOf('?');\n const targetPath = qIdx >= 0 ? targetHref.slice(0, qIdx) : targetHref;\n const targetParams = new URLSearchParams(qIdx >= 0 ? targetHref.slice(qIdx + 1) : '');\n\n // Collect params to preserve (that aren't already in the target)\n const merged = new URLSearchParams(targetParams);\n for (const [key, value] of currentParams) {\n // Only preserve if: (a) not already in target, and (b) included in preserve list\n if (!targetParams.has(key)) {\n if (preserve === true || preserve.includes(key)) {\n merged.append(key, value);\n }\n }\n }\n\n const qs = merged.toString();\n return qs ? `${targetPath}?${qs}` : targetPath;\n}\n","/**\n * Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.\n *\n * Each layout in the segment tree is wrapped with a SegmentProvider that stores\n * the URL segments from root to the current layout level. The hooks read this\n * context to determine which child segments are active below the calling layout.\n *\n * The context value is intentionally minimal: just the segment path array and\n * parallel route keys. No internal cache details are exposed.\n *\n * Design docs: design/19-client-navigation.md, design/14-ecosystem.md\n */\n\n'use client';\n\nimport { createContext, useContext, createElement, useMemo } from 'react';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface SegmentContextValue {\n /** URL segments from root to this layout (e.g. ['', 'dashboard', 'settings']) */\n segments: string[];\n /** Parallel route slot keys available at this layout level (e.g. ['sidebar', 'modal']) */\n parallelRouteKeys: string[];\n}\n\n// ─── Context ─────────────────────────────────────────────────────\n\nconst SegmentContext = createContext<SegmentContextValue | null>(null);\n\n/** Read the segment context. Returns null if no provider is above this component. */\nexport function useSegmentContext(): SegmentContextValue | null {\n return useContext(SegmentContext);\n}\n\n// ─── Provider ────────────────────────────────────────────────────\n\ninterface SegmentProviderProps {\n segments: string[];\n /**\n * Unique identifier for this segment, used by the client-side segment\n * merger for element caching. For route groups this includes the group\n * name (e.g., \"/(marketing)\") since groups share their parent's urlPath.\n * Falls back to the reconstructed path from `segments` if not provided.\n */\n segmentId?: string;\n parallelRouteKeys: string[];\n children: React.ReactNode;\n}\n\n/**\n * Wraps each layout to provide segment position context.\n * Injected by rsc-entry.ts during element tree construction.\n */\nexport function SegmentProvider({\n segments,\n segmentId: _segmentId,\n parallelRouteKeys,\n children,\n}: SegmentProviderProps) {\n const value = useMemo(\n () => ({ segments, parallelRouteKeys }),\n // segments and parallelRouteKeys are static per layout — they don't change\n // across navigations. The layout's position in the tree is fixed.\n // Intentionally using derived keys — segments/parallelRouteKeys are static per layout\n [segments.join('/'), parallelRouteKeys.join(',')]\n );\n return createElement(SegmentContext.Provider, { value }, children);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,2BACd,YACA,eACA,UACQ;CACR,MAAM,gBAAgB,IAAI,gBAAgB,cAAc;AACxD,KAAI,cAAc,SAAS,EAAG,QAAO;CAGrC,MAAM,OAAO,WAAW,QAAQ,IAAI;CACpC,MAAM,aAAa,QAAQ,IAAI,WAAW,MAAM,GAAG,KAAK,GAAG;CAC3D,MAAM,eAAe,IAAI,gBAAgB,QAAQ,IAAI,WAAW,MAAM,OAAO,EAAE,GAAG,GAAG;CAGrF,MAAM,SAAS,IAAI,gBAAgB,aAAa;AAChD,MAAK,MAAM,CAAC,KAAK,UAAU,cAEzB,KAAI,CAAC,aAAa,IAAI,IAAI;MACpB,aAAa,QAAQ,SAAS,SAAS,IAAI,CAC7C,QAAO,OAAO,KAAK,MAAM;;CAK/B,MAAM,KAAK,OAAO,UAAU;AAC5B,QAAO,KAAK,GAAG,WAAW,GAAG,OAAO;;;;;;;;;;;;;;;;AClBtC,IAAM,iBAAiB,cAA0C,KAAK;;AAGtE,SAAgB,oBAAgD;AAC9D,QAAO,WAAW,eAAe;;;;;;AAsBnC,SAAgB,gBAAgB,EAC9B,UACA,WAAW,YACX,mBACA,YACuB;CACvB,MAAM,QAAQ,eACL;EAAE;EAAU;EAAmB,GAItC,CAAC,SAAS,KAAK,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC,CAClD;AACD,QAAO,cAAc,eAAe,UAAU,EAAE,OAAO,EAAE,SAAS"}
|