@timber-js/app 0.2.0-alpha.124 → 0.2.0-alpha.125
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/{build-output-helper-DYOWsPjZ.js → build-output-helper-K7oUCRqU.js} +1 -1
- package/dist/_chunks/{build-output-helper-DYOWsPjZ.js.map → build-output-helper-K7oUCRqU.js.map} +1 -1
- package/dist/_chunks/{navigation-root-DaERBEGV.js → navigation-root-CeNTCxzS.js} +5 -5
- package/dist/_chunks/navigation-root-CeNTCxzS.js.map +1 -0
- package/dist/_chunks/{stale-reload-BN2kcE06.js → stale-reload-CCqb62Z3.js} +22 -2
- package/dist/_chunks/{stale-reload-BN2kcE06.js.map → stale-reload-CCqb62Z3.js.map} +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/nitro.js +1 -1
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +4 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/index.js +3 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +127 -269
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link-pending-store.d.ts +19 -17
- package/dist/client/link-pending-store.d.ts.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +4 -1
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/router.d.ts +10 -7
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +2 -0
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +23 -37
- package/dist/client/segment-outlet.d.ts.map +1 -1
- package/dist/client/segment-update-context.d.ts +21 -0
- package/dist/client/segment-update-context.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/server/internal.js +57 -1
- package/dist/server/internal.js.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +1 -0
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +12 -5
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/browser-entry/hydrate.ts +7 -1
- package/src/client/browser-entry/index.ts +3 -1
- package/src/client/browser-entry/post-hydration.ts +0 -13
- package/src/client/browser-entry/router-init.ts +50 -24
- package/src/client/link-pending-store.ts +22 -17
- package/src/client/link.tsx +12 -6
- package/src/client/navigation-root.tsx +40 -18
- package/src/client/router.ts +109 -97
- package/src/client/rsc-fetch.ts +85 -6
- package/src/client/segment-cache.ts +5 -2
- package/src/client/segment-outlet.tsx +40 -49
- package/src/client/segment-update-context.ts +42 -0
- package/src/server/route-element-builder.ts +69 -2
- package/src/server/rsc-entry/helpers.ts +18 -14
- package/src/server/state-tree-diff.ts +26 -22
- package/dist/_chunks/navigation-root-DaERBEGV.js.map +0 -1
- package/dist/client/segment-merger.d.ts +0 -119
- package/dist/client/segment-merger.d.ts.map +0 -1
- package/src/client/segment-merger.ts +0 -339
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SegmentOutlet — client component boundary at each layout segment.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Each layout in the segment tree is wrapped with a SegmentOutlet that:
|
|
5
|
+
* 1. Knows its segment path (prop from the server)
|
|
6
|
+
* 2. Reads from SegmentUpdateContext for partial navigation updates
|
|
7
|
+
* 3. Caches its rendered content in a ref across navigations
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* stores and renders the new children
|
|
9
|
+
* On full navigation: receives new children via props, caches and renders them.
|
|
10
|
+
* On partial navigation (this segment skipped): the context map has no entry
|
|
11
|
+
* for this path, so the outlet returns cached content — preserving layout state.
|
|
12
|
+
* On partial navigation (this segment updated): the context map has content
|
|
13
|
+
* for this path, so the outlet renders the update.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Uses React context instead of useSyncExternalStore to stay compatible
|
|
16
|
+
* with concurrent rendering (transitions). All outlets re-render when the
|
|
17
|
+
* context value changes, but each bails out quickly if its segment has
|
|
18
|
+
* no update — same approach as Next.js LayoutRouter.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* own subtree. See design/19-client-navigation.md.
|
|
21
|
-
*
|
|
22
|
-
* Security: This is a performance optimization only. The server always
|
|
23
|
-
* runs all access.ts files regardless of segment skipping. A fabricated
|
|
24
|
-
* keepCurrent prop can only cause stale layouts — never auth bypass.
|
|
20
|
+
* Security: performance optimization only. The server always runs all
|
|
21
|
+
* access.ts files regardless of segment skipping.
|
|
25
22
|
* See design/13-security.md §"State tree manipulation".
|
|
26
23
|
*/
|
|
27
24
|
import { type ReactNode } from 'react';
|
|
@@ -33,31 +30,20 @@ export interface SegmentOutletProps {
|
|
|
33
30
|
* the same urlPath. Must match the segmentId used in state-tree-diff.ts.
|
|
34
31
|
*/
|
|
35
32
|
segmentPath: string;
|
|
36
|
-
/**
|
|
37
|
-
* When true, the outlet returns its previously cached children instead
|
|
38
|
-
* of rendering the new children prop. Set by the server when this
|
|
39
|
-
* segment was skipped (the client already has the layout mounted).
|
|
40
|
-
*
|
|
41
|
-
* On the first render (SSR/hydration), this is always false — there's
|
|
42
|
-
* no cached content yet. On subsequent partial navigations, the server
|
|
43
|
-
* sets this to true for segments it skipped rendering.
|
|
44
|
-
*/
|
|
45
|
-
keepCurrent?: boolean;
|
|
46
33
|
/** The segment's React subtree (layout + inner content). */
|
|
47
34
|
children: ReactNode;
|
|
48
35
|
}
|
|
49
36
|
/**
|
|
50
37
|
* Client component boundary at each layout segment in the element tree.
|
|
51
38
|
*
|
|
52
|
-
* On full navigation:
|
|
53
|
-
* On partial navigation
|
|
54
|
-
*
|
|
55
|
-
*
|
|
39
|
+
* On full navigation (context map is empty): renders children, caches in ref.
|
|
40
|
+
* On partial navigation: checks the context map for an update at segmentPath.
|
|
41
|
+
* - Update found → renders update, caches in ref.
|
|
42
|
+
* - No update → returns cached content (layout state preserved).
|
|
56
43
|
*
|
|
57
|
-
* React preserves
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* - React reconciles same-type, same-position → instance preserved
|
|
44
|
+
* React preserves component instances across reactRoot.render() calls
|
|
45
|
+
* when the same type appears at the same tree position. The ref persists
|
|
46
|
+
* across navigations because SegmentOutlet is reconciled, not remounted.
|
|
61
47
|
*/
|
|
62
|
-
export declare function SegmentOutlet({ segmentPath
|
|
48
|
+
export declare function SegmentOutlet({ segmentPath, children }: SegmentOutletProps): ReactNode;
|
|
63
49
|
//# sourceMappingURL=segment-outlet.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segment-outlet.d.ts","sourceRoot":"","sources":["../../src/client/segment-outlet.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"segment-outlet.d.ts","sourceRoot":"","sources":["../../src/client/segment-outlet.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3D,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB,4DAA4D;IAC5D,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,kBAAkB,aAsB1E"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SegmentUpdateContext — React context for partial navigation updates.
|
|
3
|
+
*
|
|
4
|
+
* During partial navigation (server skips unchanged sync layouts), the
|
|
5
|
+
* router builds a Map of segment path → ReactNode updates and passes it
|
|
6
|
+
* as the context value. Mounted SegmentOutlet components read from this
|
|
7
|
+
* context to decide whether to render the update or their cached content.
|
|
8
|
+
*
|
|
9
|
+
* SINGLETON GUARANTEE: Uses globalThis + Symbol.for — same pattern as
|
|
10
|
+
* NavigationContext. The RSC client bundler can duplicate this module
|
|
11
|
+
* across chunks (browser-entry graph + client-reference graph). With
|
|
12
|
+
* ESM output, each chunk gets its own module scope — a bare createContext
|
|
13
|
+
* at module level would create separate instances per chunk. globalThis
|
|
14
|
+
* guarantees a single instance regardless of duplication.
|
|
15
|
+
*
|
|
16
|
+
* See design/19-client-navigation.md §"Singleton Guarantee via globalThis"
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
export declare const EMPTY_SEGMENT_UPDATES: Map<string, React.ReactNode>;
|
|
20
|
+
export declare const SegmentUpdateContext: React.Context<Map<string, React.ReactNode>>;
|
|
21
|
+
//# sourceMappingURL=segment-update-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment-update-context.d.ts","sourceRoot":"","sources":["../../src/client/segment-update-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAyB,MAAM,OAAO,CAAC;AAE9C,eAAO,MAAM,qBAAqB,8BAA+B,CAAC;AAmBlE,eAAO,MAAM,oBAAoB,6CAAuB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/server/internal.js
CHANGED
|
@@ -9,7 +9,7 @@ import { n as toBracketKey } from "../_chunks/resolve-schema-DU2nbVOi.js";
|
|
|
9
9
|
import "../_chunks/segment-context-Bg0pFbLT.js";
|
|
10
10
|
import { readFile } from "node:fs/promises";
|
|
11
11
|
import "node:crypto";
|
|
12
|
-
import { createElement } from "react";
|
|
12
|
+
import React, { createElement } from "react";
|
|
13
13
|
//#region src/server/server-timing.ts
|
|
14
14
|
/**
|
|
15
15
|
* Server-Timing header — dev-mode timing breakdowns for Chrome DevTools.
|
|
@@ -1401,6 +1401,62 @@ async function coerceSegmentParams(match) {
|
|
|
1401
1401
|
}
|
|
1402
1402
|
}
|
|
1403
1403
|
//#endregion
|
|
1404
|
+
//#region src/client/segment-update-context.ts
|
|
1405
|
+
/**
|
|
1406
|
+
* SegmentUpdateContext — React context for partial navigation updates.
|
|
1407
|
+
*
|
|
1408
|
+
* During partial navigation (server skips unchanged sync layouts), the
|
|
1409
|
+
* router builds a Map of segment path → ReactNode updates and passes it
|
|
1410
|
+
* as the context value. Mounted SegmentOutlet components read from this
|
|
1411
|
+
* context to decide whether to render the update or their cached content.
|
|
1412
|
+
*
|
|
1413
|
+
* SINGLETON GUARANTEE: Uses globalThis + Symbol.for — same pattern as
|
|
1414
|
+
* NavigationContext. The RSC client bundler can duplicate this module
|
|
1415
|
+
* across chunks (browser-entry graph + client-reference graph). With
|
|
1416
|
+
* ESM output, each chunk gets its own module scope — a bare createContext
|
|
1417
|
+
* at module level would create separate instances per chunk. globalThis
|
|
1418
|
+
* guarantees a single instance regardless of duplication.
|
|
1419
|
+
*
|
|
1420
|
+
* See design/19-client-navigation.md §"Singleton Guarantee via globalThis"
|
|
1421
|
+
*/
|
|
1422
|
+
var EMPTY_SEGMENT_UPDATES = /* @__PURE__ */ new Map();
|
|
1423
|
+
var CTX_KEY = Symbol.for("__timber_segment_update_ctx");
|
|
1424
|
+
function getOrCreateContext() {
|
|
1425
|
+
const existing = globalThis[CTX_KEY];
|
|
1426
|
+
if (existing !== void 0) return existing;
|
|
1427
|
+
if (typeof React.createContext === "function") {
|
|
1428
|
+
const ctx = React.createContext(EMPTY_SEGMENT_UPDATES);
|
|
1429
|
+
globalThis[CTX_KEY] = ctx;
|
|
1430
|
+
return ctx;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
getOrCreateContext();
|
|
1434
|
+
//#endregion
|
|
1435
|
+
//#region src/client/segment-outlet.tsx
|
|
1436
|
+
/**
|
|
1437
|
+
* SegmentOutlet — client component boundary at each layout segment.
|
|
1438
|
+
*
|
|
1439
|
+
* Each layout in the segment tree is wrapped with a SegmentOutlet that:
|
|
1440
|
+
* 1. Knows its segment path (prop from the server)
|
|
1441
|
+
* 2. Reads from SegmentUpdateContext for partial navigation updates
|
|
1442
|
+
* 3. Caches its rendered content in a ref across navigations
|
|
1443
|
+
*
|
|
1444
|
+
* On full navigation: receives new children via props, caches and renders them.
|
|
1445
|
+
* On partial navigation (this segment skipped): the context map has no entry
|
|
1446
|
+
* for this path, so the outlet returns cached content — preserving layout state.
|
|
1447
|
+
* On partial navigation (this segment updated): the context map has content
|
|
1448
|
+
* for this path, so the outlet renders the update.
|
|
1449
|
+
*
|
|
1450
|
+
* Uses React context instead of useSyncExternalStore to stay compatible
|
|
1451
|
+
* with concurrent rendering (transitions). All outlets re-render when the
|
|
1452
|
+
* context value changes, but each bails out quickly if its segment has
|
|
1453
|
+
* no update — same approach as Next.js LayoutRouter.
|
|
1454
|
+
*
|
|
1455
|
+
* Security: performance optimization only. The server always runs all
|
|
1456
|
+
* access.ts files regardless of segment skipping.
|
|
1457
|
+
* See design/13-security.md §"State tree manipulation".
|
|
1458
|
+
*/
|
|
1459
|
+
//#endregion
|
|
1404
1460
|
//#region src/server/route-element-builder.ts
|
|
1405
1461
|
/**
|
|
1406
1462
|
* Thrown when a defineSegmentParams codec's parse() fails.
|