@timber-js/app 0.1.54 → 0.1.56
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/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +8 -4
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/client/top-loader.d.ts +46 -0
- package/dist/client/top-loader.d.ts.map +1 -0
- package/dist/client/transition-root.d.ts +3 -1
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +1 -1
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/nitro.ts +8 -4
- package/src/client/browser-entry.ts +1 -1
- package/src/client/top-loader.tsx +213 -0
- package/src/client/transition-root.tsx +11 -3
- package/src/index.ts +24 -0
- package/src/plugins/entries.ts +1 -0
- package/src/server/state-tree-diff.ts +18 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/plugins/entries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/plugins/entries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA0HhD;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAwBxF;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAwExD"}
|
|
@@ -39,5 +39,5 @@ export declare function parseClientStateTree(req: Request): Set<string> | null;
|
|
|
39
39
|
* @param isLeaf - Whether this is the leaf segment (page segment)
|
|
40
40
|
* @param clientSegments - Set of paths from X-Timber-State-Tree, or null
|
|
41
41
|
*/
|
|
42
|
-
export declare function shouldSkipSegment(
|
|
42
|
+
export declare function shouldSkipSegment(_urlPath: string, _layoutComponent: ((...args: unknown[]) => unknown) | undefined, _isLeaf: boolean, _clientSegments: Set<string> | null): boolean;
|
|
43
43
|
//# sourceMappingURL=state-tree-diff.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-tree-diff.d.ts","sourceRoot":"","sources":["../../src/server/state-tree-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAarE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,
|
|
1
|
+
{"version":3,"file":"state-tree-diff.d.ts","sourceRoot":"","sources":["../../src/server/state-tree-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAarE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,SAAS,EAC/D,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GAClC,OAAO,CAeT"}
|
package/package.json
CHANGED
package/src/adapters/nitro.ts
CHANGED
|
@@ -341,20 +341,24 @@ export function generateNitroEntry(
|
|
|
341
341
|
return `// Generated by @timber-js/app/adapters/nitro
|
|
342
342
|
// Do not edit — this file is regenerated on each build.
|
|
343
343
|
|
|
344
|
-
${manifestImport}import {
|
|
345
|
-
import handler, { runWithEarlyHintsSender${waitUntilImport} } from '${serverEntryRelative}'
|
|
344
|
+
${manifestImport}import handler, { runWithEarlyHintsSender${waitUntilImport} } from '${serverEntryRelative}'
|
|
346
345
|
import { compressResponse } from './_compress.mjs'
|
|
347
346
|
|
|
348
347
|
// Set TIMBER_RUNTIME for instrumentation.ts conditional SDK initialization.
|
|
349
348
|
// See design/25-production-deployments.md §"TIMBER_RUNTIME".
|
|
350
349
|
process.env.TIMBER_RUNTIME = '${runtimeName}'
|
|
351
350
|
|
|
352
|
-
|
|
351
|
+
// Nitro's index.mjs wraps this import with defineLazyEventHandler, which
|
|
352
|
+
// already handles the event handler protocol. We export a plain async
|
|
353
|
+
// function — no defineEventHandler wrapper needed.
|
|
354
|
+
// Nitro bundles h3 into its internal _libs/ directory but doesn't make
|
|
355
|
+
// it available as a bare specifier in the output's node_modules.
|
|
356
|
+
export default async function timberHandler(event) {
|
|
353
357
|
// h3 v2: event.req is the Web Request
|
|
354
358
|
const webRequest = event.req
|
|
355
359
|
${handlerCall}
|
|
356
360
|
return compressResponse(webRequest, webResponse)
|
|
357
|
-
}
|
|
361
|
+
}
|
|
358
362
|
`;
|
|
359
363
|
}
|
|
360
364
|
|
|
@@ -381,7 +381,7 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
381
381
|
element as React.ReactNode
|
|
382
382
|
);
|
|
383
383
|
const wrapped = createElement(TimberNuqsAdapter, null, withNav);
|
|
384
|
-
const rootElement = createElement(TransitionRoot, { initial: wrapped });
|
|
384
|
+
const rootElement = createElement(TransitionRoot, { initial: wrapped, topLoaderConfig: _config.topLoader });
|
|
385
385
|
_reactRoot = hydrateRoot(document, rootElement, {
|
|
386
386
|
// Suppress recoverable hydration errors from deny/error signals
|
|
387
387
|
// inside Suspense boundaries. The server already handled these
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopLoader — Built-in progress bar for client navigations.
|
|
3
|
+
*
|
|
4
|
+
* Shows an animated progress bar at the top of the viewport while an RSC
|
|
5
|
+
* navigation is in flight. Injected automatically by the framework into
|
|
6
|
+
* TransitionRoot — users never render this component directly.
|
|
7
|
+
*
|
|
8
|
+
* Configuration is via timber.config.ts `topLoader` key. Enabled by default.
|
|
9
|
+
* Users who want a fully custom progress indicator disable the built-in one
|
|
10
|
+
* (`topLoader: { enabled: false }`) and use `useNavigationPending()` directly.
|
|
11
|
+
*
|
|
12
|
+
* Animation approach: pure CSS @keyframes. The bar crawls from 0% to ~90%
|
|
13
|
+
* width over ~30s using ease-out timing. When navigation completes, the bar
|
|
14
|
+
* snaps to 100% and fades out over 200ms. No JS animation loops (RAF, setInterval).
|
|
15
|
+
*
|
|
16
|
+
* The bar respects a configurable delay (default 150ms) — if navigation
|
|
17
|
+
* resolves faster than the delay, the bar never appears.
|
|
18
|
+
*
|
|
19
|
+
* See design/19-client-navigation.md §"useNavigationPending()"
|
|
20
|
+
* See LOCAL-336 for design decisions.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use client';
|
|
24
|
+
|
|
25
|
+
import { useState, useEffect, useRef, createElement } from 'react';
|
|
26
|
+
import { usePendingNavigationUrl } from './navigation-context.js';
|
|
27
|
+
|
|
28
|
+
// ─── Types ───────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export interface TopLoaderConfig {
|
|
31
|
+
/** Whether the top-loader is enabled. Default: true. */
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
/** Bar color. Default: '#2299DD'. */
|
|
34
|
+
color?: string;
|
|
35
|
+
/** Bar height in pixels. Default: 3. */
|
|
36
|
+
height?: number;
|
|
37
|
+
/** Show subtle glow/shadow effect. Default: true. */
|
|
38
|
+
shadow?: boolean;
|
|
39
|
+
/** Delay in ms before showing the bar. Default: 150. */
|
|
40
|
+
delay?: number;
|
|
41
|
+
/** CSS z-index. Default: 1600. */
|
|
42
|
+
zIndex?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Defaults ────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const DEFAULT_COLOR = '#2299DD';
|
|
48
|
+
const DEFAULT_HEIGHT = 3;
|
|
49
|
+
const DEFAULT_SHADOW = true;
|
|
50
|
+
const DEFAULT_DELAY = 150;
|
|
51
|
+
const DEFAULT_Z_INDEX = 1600;
|
|
52
|
+
|
|
53
|
+
// ─── Keyframes ID ────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
// Unique keyframes name to avoid collisions with user styles.
|
|
56
|
+
const KEYFRAMES_NAME = '__timber_top_loader_crawl';
|
|
57
|
+
|
|
58
|
+
// Track whether the @keyframes rule has been injected into the document.
|
|
59
|
+
let keyframesInjected = false;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Inject the @keyframes rule into the document head once.
|
|
63
|
+
* Uses a <style> tag so the animation is available for inline-styled elements.
|
|
64
|
+
*/
|
|
65
|
+
function ensureKeyframes(): void {
|
|
66
|
+
if (keyframesInjected) return;
|
|
67
|
+
if (typeof document === 'undefined') return;
|
|
68
|
+
|
|
69
|
+
const style = document.createElement('style');
|
|
70
|
+
style.textContent = `
|
|
71
|
+
@keyframes ${KEYFRAMES_NAME} {
|
|
72
|
+
0% { width: 0%; }
|
|
73
|
+
100% { width: 90%; }
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
document.head.appendChild(style);
|
|
77
|
+
keyframesInjected = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Component ───────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Internal top-loader component. Injected by TransitionRoot.
|
|
84
|
+
*
|
|
85
|
+
* Reads pending navigation state from PendingNavigationContext.
|
|
86
|
+
* Shows nothing when no navigation is pending or when the navigation
|
|
87
|
+
* resolves before the delay threshold.
|
|
88
|
+
*/
|
|
89
|
+
export function TopLoader({ config }: { config?: TopLoaderConfig }): React.ReactElement | null {
|
|
90
|
+
const pendingUrl = usePendingNavigationUrl();
|
|
91
|
+
const isPending = pendingUrl !== null;
|
|
92
|
+
|
|
93
|
+
const color = config?.color ?? DEFAULT_COLOR;
|
|
94
|
+
const height = config?.height ?? DEFAULT_HEIGHT;
|
|
95
|
+
const shadow = config?.shadow ?? DEFAULT_SHADOW;
|
|
96
|
+
const delay = config?.delay ?? DEFAULT_DELAY;
|
|
97
|
+
const zIndex = config?.zIndex ?? DEFAULT_Z_INDEX;
|
|
98
|
+
|
|
99
|
+
// Track visibility states:
|
|
100
|
+
// - 'hidden': no bar shown
|
|
101
|
+
// - 'crawling': bar is animating from 0% to 90%
|
|
102
|
+
// - 'finishing': bar is snapping to 100% and fading out
|
|
103
|
+
const [phase, setPhase] = useState<'hidden' | 'crawling' | 'finishing'>('hidden');
|
|
104
|
+
const delayTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
105
|
+
const finishTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
106
|
+
|
|
107
|
+
// Inject keyframes on mount
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
ensureKeyframes();
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
// Handle pending state changes
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (isPending) {
|
|
115
|
+
// Navigation started — wait for delay before showing the bar
|
|
116
|
+
// Clear any finish timer from a previous navigation
|
|
117
|
+
if (finishTimerRef.current !== null) {
|
|
118
|
+
clearTimeout(finishTimerRef.current);
|
|
119
|
+
finishTimerRef.current = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (delay > 0) {
|
|
123
|
+
delayTimerRef.current = setTimeout(() => {
|
|
124
|
+
delayTimerRef.current = null;
|
|
125
|
+
setPhase('crawling');
|
|
126
|
+
}, delay);
|
|
127
|
+
} else {
|
|
128
|
+
setPhase('crawling');
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// Navigation ended (or was never pending)
|
|
132
|
+
// Clear the delay timer — if it hasn't fired, the bar never shows
|
|
133
|
+
if (delayTimerRef.current !== null) {
|
|
134
|
+
clearTimeout(delayTimerRef.current);
|
|
135
|
+
delayTimerRef.current = null;
|
|
136
|
+
// Bar was never shown — stay hidden
|
|
137
|
+
if (phase === 'hidden') return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (phase === 'crawling') {
|
|
141
|
+
// Bar was visible — snap to 100% and fade out
|
|
142
|
+
setPhase('finishing');
|
|
143
|
+
finishTimerRef.current = setTimeout(() => {
|
|
144
|
+
finishTimerRef.current = null;
|
|
145
|
+
setPhase('hidden');
|
|
146
|
+
}, 200);
|
|
147
|
+
}
|
|
148
|
+
// If already 'finishing' or 'hidden', do nothing
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return () => {
|
|
152
|
+
if (delayTimerRef.current !== null) {
|
|
153
|
+
clearTimeout(delayTimerRef.current);
|
|
154
|
+
delayTimerRef.current = null;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- phase is read but intentionally not a dependency
|
|
158
|
+
}, [isPending, delay]);
|
|
159
|
+
|
|
160
|
+
// Cleanup finish timer on unmount
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
return () => {
|
|
163
|
+
if (finishTimerRef.current !== null) {
|
|
164
|
+
clearTimeout(finishTimerRef.current);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
if (phase === 'hidden') return null;
|
|
170
|
+
|
|
171
|
+
// ─── Styles ──────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
const containerStyle: React.CSSProperties = {
|
|
174
|
+
position: 'fixed',
|
|
175
|
+
top: 0,
|
|
176
|
+
left: 0,
|
|
177
|
+
width: '100%',
|
|
178
|
+
height: `${height}px`,
|
|
179
|
+
zIndex,
|
|
180
|
+
pointerEvents: 'none',
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const barStyle: React.CSSProperties = {
|
|
184
|
+
height: '100%',
|
|
185
|
+
backgroundColor: color,
|
|
186
|
+
...(phase === 'crawling'
|
|
187
|
+
? {
|
|
188
|
+
// Crawling: animate from 0% to 90% over 30s
|
|
189
|
+
animation: `${KEYFRAMES_NAME} 30s ease-out forwards`,
|
|
190
|
+
}
|
|
191
|
+
: {
|
|
192
|
+
// Finishing: snap to 100% and fade out
|
|
193
|
+
width: '100%',
|
|
194
|
+
opacity: 0,
|
|
195
|
+
transition: 'width 200ms ease, opacity 200ms ease',
|
|
196
|
+
}),
|
|
197
|
+
...(shadow
|
|
198
|
+
? {
|
|
199
|
+
boxShadow: `0 0 10px ${color}, 0 0 5px ${color}`,
|
|
200
|
+
}
|
|
201
|
+
: {}),
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return createElement(
|
|
205
|
+
'div',
|
|
206
|
+
{
|
|
207
|
+
style: containerStyle,
|
|
208
|
+
'aria-hidden': 'true',
|
|
209
|
+
'data-timber-top-loader': '',
|
|
210
|
+
},
|
|
211
|
+
createElement('div', { style: barStyle })
|
|
212
|
+
);
|
|
213
|
+
}
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
* See design/19-client-navigation.md §"NavigationContext"
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { useState, useTransition, createElement, type ReactNode } from 'react';
|
|
25
|
+
import { useState, useTransition, createElement, Fragment, type ReactNode } from 'react';
|
|
26
26
|
import { PendingNavigationProvider } from './navigation-context.js';
|
|
27
|
+
import { TopLoader, type TopLoaderConfig } from './top-loader.js';
|
|
27
28
|
|
|
28
29
|
// ─── Module-level functions ──────────────────────────────────────
|
|
29
30
|
|
|
@@ -61,7 +62,7 @@ let _navigateTransition:
|
|
|
61
62
|
* Non-navigation renders:
|
|
62
63
|
* transitionRender(newWrappedElement);
|
|
63
64
|
*/
|
|
64
|
-
export function TransitionRoot({ initial }: { initial: ReactNode }): ReactNode {
|
|
65
|
+
export function TransitionRoot({ initial, topLoaderConfig }: { initial: ReactNode; topLoaderConfig?: TopLoaderConfig }): ReactNode {
|
|
65
66
|
const [element, setElement] = useState<ReactNode>(initial);
|
|
66
67
|
const [pendingUrl, setPendingUrl] = useState<string | null>(null);
|
|
67
68
|
const [, startTransition] = useTransition();
|
|
@@ -101,7 +102,14 @@ export function TransitionRoot({ initial }: { initial: ReactNode }): ReactNode {
|
|
|
101
102
|
});
|
|
102
103
|
};
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
// Inject TopLoader alongside the element tree inside PendingNavigationProvider.
|
|
106
|
+
// The TopLoader reads pendingUrl from context to show/hide the progress bar.
|
|
107
|
+
// It is rendered only when not explicitly disabled via config.
|
|
108
|
+
const showTopLoader = topLoaderConfig?.enabled !== false;
|
|
109
|
+
const children = showTopLoader
|
|
110
|
+
? createElement(Fragment, null, createElement(TopLoader, { config: topLoaderConfig }), element)
|
|
111
|
+
: element;
|
|
112
|
+
return createElement(PendingNavigationProvider, { value: pendingUrl }, children);
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
// ─── Public API ──────────────────────────────────────────────────
|
package/src/index.ts
CHANGED
|
@@ -110,6 +110,30 @@ export interface TimberUserConfig {
|
|
|
110
110
|
recmaPlugins?: unknown[];
|
|
111
111
|
remarkRehypeOptions?: Record<string, unknown>;
|
|
112
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* Built-in top-loader progress bar for client navigations.
|
|
115
|
+
* Shows an animated bar at the top of the viewport during RSC navigations.
|
|
116
|
+
* Enabled by default — set `enabled: false` to opt out.
|
|
117
|
+
*
|
|
118
|
+
* Users who want a fully custom progress indicator should disable this
|
|
119
|
+
* and use `useNavigationPending()` directly.
|
|
120
|
+
*
|
|
121
|
+
* See LOCAL-336 for design decisions.
|
|
122
|
+
*/
|
|
123
|
+
topLoader?: {
|
|
124
|
+
/** Whether the top-loader is enabled. Default: true. */
|
|
125
|
+
enabled?: boolean;
|
|
126
|
+
/** Bar color. Default: '#2299DD'. */
|
|
127
|
+
color?: string;
|
|
128
|
+
/** Bar height in pixels. Default: 3. */
|
|
129
|
+
height?: number;
|
|
130
|
+
/** Show subtle glow/shadow effect. Default: true. */
|
|
131
|
+
shadow?: boolean;
|
|
132
|
+
/** Delay in ms before showing the bar (avoids flash on fast navs). Default: 150. */
|
|
133
|
+
delay?: number;
|
|
134
|
+
/** CSS z-index. Default: 1600. */
|
|
135
|
+
zIndex?: number;
|
|
136
|
+
};
|
|
113
137
|
}
|
|
114
138
|
|
|
115
139
|
/**
|
package/src/plugins/entries.ts
CHANGED
|
@@ -55,23 +55,23 @@ export function parseClientStateTree(req: Request): Set<string> | null {
|
|
|
55
55
|
* @param clientSegments - Set of paths from X-Timber-State-Tree, or null
|
|
56
56
|
*/
|
|
57
57
|
export function shouldSkipSegment(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
_urlPath: string,
|
|
59
|
+
_layoutComponent: ((...args: unknown[]) => unknown) | undefined,
|
|
60
|
+
_isLeaf: boolean,
|
|
61
|
+
_clientSegments: Set<string> | null
|
|
62
62
|
): boolean {
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
return
|
|
63
|
+
// DISABLED: Client-side segment merging via React element tree walking
|
|
64
|
+
// is too fragile for production. The merger's replaceInnerSegment relies
|
|
65
|
+
// on walking RSC-decoded element trees to find SegmentProvider boundaries,
|
|
66
|
+
// but production trees contain structures the walker can't handle:
|
|
67
|
+
// Suspense wrappers, error boundaries, AccessGate components, React lazy
|
|
68
|
+
// refs, and client component module references.
|
|
69
|
+
//
|
|
70
|
+
// The merger infrastructure (segment-merger.ts, element cache, state tree
|
|
71
|
+
// filtering) is in place and tested. Re-enable when the merger can handle
|
|
72
|
+
// real RSC element trees — likely requires a SegmentOutlet client component
|
|
73
|
+
// approach instead of post-hoc element tree walking.
|
|
74
|
+
//
|
|
75
|
+
// See design/19-client-navigation.md §"X-Timber-State-Tree Header"
|
|
76
|
+
return false;
|
|
77
77
|
}
|