@timber-js/app 0.2.0-alpha.184 → 0.2.0-alpha.186
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/{actions-O_LsyCE4.js → actions-C-Rw9vPc.js} +3 -3
- package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-C-Rw9vPc.js.map} +1 -1
- package/dist/_chunks/als-slots-mFweg276.js +27 -0
- package/dist/_chunks/als-slots-mFweg276.js.map +1 -0
- package/dist/_chunks/base-path-DaQrzbez.js +80 -0
- package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
- package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
- package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-Cd0VZ_Pd.js} +3 -3
- package/dist/_chunks/{cache-api-B-lhk9p4.js.map → cache-api-Cd0VZ_Pd.js.map} +1 -1
- package/dist/_chunks/cli-schema-sync-CKgHC2MB.js +1857 -0
- package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +1 -0
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-Cs0uZXea.js} +89 -12
- package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-Cs0uZXea.js.map} +1 -1
- package/dist/_chunks/error-boundary-DpYRI_I1.js +330 -0
- package/dist/_chunks/error-boundary-DpYRI_I1.js.map +1 -0
- package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
- package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
- package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
- package/dist/_chunks/{logger-AWfuX-KJ.js → logger-N7e5auP0.js} +9 -13
- package/dist/_chunks/logger-N7e5auP0.js.map +1 -0
- package/dist/_chunks/navigation-root-B29qg0_T.js +274 -0
- package/dist/_chunks/navigation-root-B29qg0_T.js.map +1 -0
- package/dist/_chunks/param-value-C8TNYchQ.js +66 -0
- package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
- package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-DEGLSJs3.js} +2 -1
- package/dist/_chunks/plugin-context-DEGLSJs3.js.map +1 -0
- package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
- package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
- package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
- package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
- package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -0
- package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
- package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
- package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
- package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
- package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
- package/dist/_chunks/use-segment-params-ClyUNq4d.js +128 -0
- package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +1 -0
- package/dist/_chunks/{walkers-BtTlKviE.js → walkers-BhhwI9TD.js} +21 -87
- package/dist/_chunks/walkers-BhhwI9TD.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +37 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/fs-identity.d.ts +64 -0
- package/dist/adapters/fs-identity.d.ts.map +1 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +2 -3
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +57 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +13 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/fast-hash.d.ts +5 -0
- package/dist/cache/fast-hash.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts +20 -9
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts +4 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +3 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
- package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +1 -0
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -162
- package/dist/client/history.d.ts +13 -6
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +18 -11
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +4 -3
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +872 -305
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +2 -2
- package/dist/client/navigation-commit.d.ts +71 -0
- package/dist/client/navigation-commit.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +37 -20
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +63 -23
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/params-context.d.ts +81 -0
- package/dist/client/params-context.d.ts.map +1 -0
- package/dist/client/router-effects.d.ts +129 -0
- package/dist/client/router-effects.d.ts.map +1 -0
- package/dist/client/router-lifecycle.d.ts +55 -0
- package/dist/client/router-lifecycle.d.ts.map +1 -0
- package/dist/client/router-pipeline.d.ts +84 -0
- package/dist/client/router-pipeline.d.ts.map +1 -0
- package/dist/client/router-ref.d.ts +1 -1
- package/dist/client/router-ref.d.ts.map +1 -1
- package/dist/client/router-skew.d.ts +21 -0
- package/dist/client/router-skew.d.ts.map +1 -0
- package/dist/client/router-types.d.ts +207 -0
- package/dist/client/router-types.d.ts.map +1 -0
- package/dist/client/router.d.ts +1 -182
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +39 -16
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +79 -34
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/ssr-data.d.ts +6 -0
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/stale-client.d.ts +69 -0
- package/dist/client/stale-client.d.ts.map +1 -0
- package/dist/client/state.d.ts +10 -1
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +2 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/unload-guard.d.ts +5 -0
- package/dist/client/unload-guard.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +1 -1
- package/dist/client/use-segment-params.d.ts +18 -8
- package/dist/client/use-segment-params.d.ts.map +1 -1
- package/dist/cookies/index.js +3 -3
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +915 -320
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +33 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/request-dep/analysis.d.ts +106 -0
- package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
- package/dist/plugins/request-dep/index.d.ts +75 -0
- package/dist/plugins/request-dep/index.d.ts.map +1 -0
- package/dist/plugins/request-dep/origin.d.ts +61 -0
- package/dist/plugins/request-dep/origin.d.ts.map +1 -0
- package/dist/plugins/routing.d.ts +9 -0
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +149 -8
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +18 -0
- package/dist/routing/codegen-types.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/collision-probe.d.ts +104 -0
- package/dist/routing/collision-probe.d.ts.map +1 -0
- package/dist/routing/collision-spaces.d.ts +26 -0
- package/dist/routing/collision-spaces.d.ts.map +1 -0
- package/dist/routing/index.js +3 -3
- package/dist/routing/interception.d.ts +235 -6
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +33 -0
- package/dist/routing/segment-classify.d.ts.map +1 -1
- package/dist/routing/segment-keys.d.ts +95 -0
- package/dist/routing/segment-keys.d.ts.map +1 -0
- package/dist/routing/slot-placement.d.ts +48 -0
- package/dist/routing/slot-placement.d.ts.map +1 -0
- package/dist/routing/types.d.ts +1 -3
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +6 -0
- package/dist/routing/walkers.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +0 -6
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +176 -2
- package/dist/search-params/index.js.map +1 -1
- package/dist/segment-params/define.d.ts +0 -6
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +69 -1
- package/dist/segment-params/index.js.map +1 -0
- package/dist/server/actions.d.ts +6 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +2 -1
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +13 -1
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/chain-url-parts.d.ts +82 -0
- package/dist/server/chain-url-parts.d.ts.map +1 -0
- package/dist/server/children-interception.d.ts +49 -0
- package/dist/server/children-interception.d.ts.map +1 -0
- package/dist/server/default-status-page.d.ts +45 -0
- package/dist/server/default-status-page.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +4 -7
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +14 -3
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/head-response.d.ts +35 -0
- package/dist/server/head-response.d.ts.map +1 -0
- package/dist/server/index.js +2 -2
- package/dist/server/internal.js +528 -373
- package/dist/server/internal.js.map +1 -1
- package/dist/server/param-coercion.d.ts +10 -1
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +0 -18
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-interception.d.ts +59 -9
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.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/prebuilt-builder.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +0 -12
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/publish-params.d.ts +33 -0
- package/dist/server/publish-params.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +48 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +32 -2
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +13 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-cache-key-guard.d.ts +78 -0
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +14 -2
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -10
- 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/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -2
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +3 -2
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +2 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/sitemap-generator.d.ts.map +1 -1
- package/dist/server/skippable-prefix.d.ts +23 -0
- package/dist/server/skippable-prefix.d.ts.map +1 -0
- package/dist/server/slot-interception.d.ts +80 -0
- package/dist/server/slot-interception.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +70 -8
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +6 -0
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +24 -13
- package/dist/server/ssr-wrappers.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +6 -27
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/static-generator.d.ts +6 -0
- package/dist/server/static-generator.d.ts.map +1 -1
- package/dist/server/static-not-found.d.ts +52 -0
- package/dist/server/static-not-found.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +3 -10
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/shared/als-slots.d.ts +41 -0
- package/dist/shared/als-slots.d.ts.map +1 -0
- package/dist/shared/base-path.d.ts +56 -0
- package/dist/shared/base-path.d.ts.map +1 -0
- package/dist/shared/param-value.d.ts +80 -0
- package/dist/shared/param-value.d.ts.map +1 -0
- package/dist/shared/payload-root.d.ts +108 -0
- package/dist/shared/payload-root.d.ts.map +1 -0
- package/dist/shared/rsc-cache-key.d.ts +163 -0
- package/dist/shared/rsc-cache-key.d.ts.map +1 -0
- package/dist/shared/rsc-error-envelope.d.ts +25 -0
- package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
- package/dist/shared/rsc-manifest.d.ts +28 -0
- package/dist/shared/rsc-manifest.d.ts.map +1 -0
- package/dist/shared/rsc-payload-path.d.ts +42 -0
- package/dist/shared/rsc-payload-path.d.ts.map +1 -0
- package/dist/shared/segment-info.d.ts +59 -0
- package/dist/shared/segment-info.d.ts.map +1 -0
- package/dist/shared/slot-params.d.ts +51 -0
- package/dist/shared/slot-params.d.ts.map +1 -0
- package/dist/shared/static-platform-files.d.ts +71 -0
- package/dist/shared/static-platform-files.d.ts.map +1 -0
- package/dist/shims/navigation-client.d.ts +8 -19
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +0 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/docs/api/31-api-client.mdx +4 -2
- package/docs/learn/02-pages-and-layouts.mdx +1 -1
- package/docs/learn/11-error-handling.mdx +4 -3
- package/docs/learn/12-client-navigation.mdx +2 -2
- package/docs/learn/14-deploying.mdx +23 -0
- package/docs/more/01-advanced-routing.mdx +172 -2
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/package.json +2 -2
- package/src/adapters/build-output-helper.ts +116 -3
- package/src/adapters/cloudflare.ts +86 -11
- package/src/adapters/fs-identity.ts +156 -0
- package/src/adapters/nitro.ts +6 -6
- package/src/adapters/shared.ts +196 -21
- package/src/adapters/types.ts +13 -1
- package/src/cache/fast-hash.ts +5 -0
- package/src/client/browser-entry/action-dispatch.ts +9 -4
- package/src/client/browser-entry/hydrate.ts +40 -29
- package/src/client/browser-entry/index.ts +15 -1
- package/src/client/browser-entry/post-hydration.ts +10 -9
- package/src/client/browser-entry/router-init.ts +104 -48
- package/src/client/browser-entry/rsc-stream.ts +11 -2
- package/src/client/error-boundary.tsx +20 -15
- package/src/client/history.ts +13 -6
- package/src/client/internal.ts +4 -3
- package/src/client/link.tsx +55 -8
- package/src/client/navigation-api.ts +3 -3
- package/src/client/navigation-commit.ts +174 -0
- package/src/client/navigation-context.ts +40 -67
- package/src/client/navigation-root.tsx +326 -101
- package/src/client/params-context.ts +192 -0
- package/src/client/router-effects.ts +231 -0
- package/src/client/router-lifecycle.ts +288 -0
- package/src/client/router-pipeline.ts +389 -0
- package/src/client/router-ref.ts +1 -1
- package/src/client/router-skew.ts +30 -0
- package/src/client/router-types.ts +230 -0
- package/src/client/router.ts +242 -690
- package/src/client/rsc-fetch.ts +180 -122
- package/src/client/segment-cache.ts +121 -44
- package/src/client/ssr-data.ts +6 -0
- package/src/client/stale-client.ts +191 -0
- package/src/client/state.ts +14 -1
- package/src/client/top-loader.tsx +2 -1
- package/src/client/unload-guard.ts +34 -2
- package/src/client/use-cookie.ts +9 -2
- package/src/client/use-router.ts +1 -1
- package/src/client/use-segment-params.ts +41 -18
- package/src/index.ts +5 -1
- package/src/plugin-context.ts +34 -1
- package/src/plugins/adapter-build.ts +4 -0
- package/src/plugins/build-manifest.ts +34 -13
- package/src/plugins/entries.ts +4 -0
- package/src/plugins/fonts.ts +11 -8
- package/src/plugins/request-dep/analysis.ts +447 -0
- package/src/plugins/request-dep/index.ts +376 -0
- package/src/plugins/request-dep/origin.ts +310 -0
- package/src/plugins/routing.ts +60 -3
- package/src/plugins/static-build.ts +333 -63
- package/src/routing/codegen-types.ts +18 -0
- package/src/routing/codegen.ts +50 -10
- package/src/routing/collision-probe.ts +438 -0
- package/src/routing/collision-spaces.ts +281 -0
- package/src/routing/interception.ts +896 -50
- package/src/routing/manifest-codegen.ts +6 -9
- package/src/routing/scanner.ts +114 -113
- package/src/routing/segment-classify.ts +57 -0
- package/src/routing/segment-keys.ts +146 -0
- package/src/routing/slot-placement.ts +97 -0
- package/src/routing/types.ts +1 -3
- package/src/routing/walkers.ts +8 -0
- package/src/search-params/define.ts +6 -29
- package/src/segment-params/define.ts +4 -27
- package/src/server/action-handler.ts +3 -1
- package/src/server/actions.ts +6 -3
- package/src/server/als-registry.ts +2 -1
- package/src/server/build-manifest.ts +14 -1
- package/src/server/chain-url-parts.ts +163 -0
- package/src/server/children-interception.ts +115 -0
- package/src/server/default-status-page.ts +127 -0
- package/src/server/deny-boundary.ts +5 -31
- package/src/server/deny-renderer.ts +83 -22
- package/src/server/head-response.ts +54 -0
- package/src/server/param-coercion.ts +48 -18
- package/src/server/pipeline-helpers.ts +0 -45
- package/src/server/pipeline-interception.ts +81 -32
- package/src/server/pipeline-outcome.ts +3 -5
- package/src/server/pipeline-phases.ts +68 -16
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-builder.ts +1 -7
- package/src/server/prebuilt-runtime.ts +4 -4
- package/src/server/primitives.ts +0 -18
- package/src/server/publish-params.ts +42 -0
- package/src/server/request-context.ts +97 -18
- package/src/server/route-element-builder.ts +89 -98
- package/src/server/route-handler.ts +8 -8
- package/src/server/route-matcher.ts +21 -9
- package/src/server/rsc-cache-key-guard.ts +170 -0
- package/src/server/rsc-entry/api-handler.ts +6 -8
- package/src/server/rsc-entry/error-renderer.ts +64 -25
- package/src/server/rsc-entry/helpers.ts +31 -16
- package/src/server/rsc-entry/index.ts +36 -28
- package/src/server/rsc-entry/render-route.ts +12 -7
- package/src/server/rsc-entry/revalidate-renderer.ts +1 -1
- package/src/server/rsc-entry/rsc-payload.ts +14 -25
- package/src/server/rsc-entry/rsc-stream.ts +4 -3
- package/src/server/rsc-entry/ssr-renderer.ts +13 -9
- package/src/server/sitemap-generator.ts +1 -7
- package/src/server/skippable-prefix.ts +92 -0
- package/src/server/slot-interception.ts +168 -0
- package/src/server/slot-resolver.ts +253 -156
- package/src/server/ssr-bridge-types.ts +6 -0
- package/src/server/ssr-entry.ts +12 -4
- package/src/server/ssr-wrappers.tsx +33 -32
- package/src/server/state-tree-diff.ts +14 -55
- package/src/server/static-generator.ts +67 -45
- package/src/server/static-not-found.ts +134 -0
- package/src/server/status-code-resolver.ts +4 -47
- package/src/shared/als-slots.ts +72 -0
- package/src/shared/base-path.ts +80 -0
- package/src/shared/param-value.ts +244 -0
- package/src/shared/payload-root.ts +151 -0
- package/src/shared/rsc-cache-key.ts +269 -0
- package/src/shared/rsc-error-envelope.ts +31 -0
- package/src/shared/rsc-manifest.ts +29 -0
- package/src/shared/rsc-payload-path.ts +45 -0
- package/src/shared/segment-info.ts +60 -0
- package/src/shared/slot-params.ts +111 -0
- package/src/shared/static-platform-files.ts +75 -0
- package/src/shims/navigation-client.ts +8 -26
- package/src/shims/navigation.ts +0 -4
- package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
- package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
- package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-D3naS3eH.js +0 -688
- package/dist/_chunks/cli-schema-sync-D3naS3eH.js.map +0 -1
- package/dist/_chunks/define-COtkxMRT.js +0 -191
- package/dist/_chunks/define-COtkxMRT.js.map +0 -1
- package/dist/_chunks/define-c4au4I9R.js +0 -82
- package/dist/_chunks/define-c4au4I9R.js.map +0 -1
- package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
- package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
- package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
- package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
- package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
- package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
- package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
- package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
- package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
- package/dist/_chunks/walkers-BtTlKviE.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/plugins/request-dep.d.ts +0 -32
- package/dist/plugins/request-dep.d.ts.map +0 -1
- package/src/plugins/request-dep.ts +0 -499
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { createElement } from 'react';
|
|
11
11
|
import { createFromFetch } from '../../rsc-runtime/browser.js';
|
|
12
|
+
import { PayloadRoot } from '../params-context.js';
|
|
13
|
+
import { readPublishedParams, type ParamsSource } from '../../shared/payload-root.js';
|
|
12
14
|
import { createRouter, setGlobalRouter } from '#client-internal';
|
|
13
15
|
import type { RouterDeps, RouterInstance } from '#client-internal';
|
|
14
16
|
import type { NavigationState } from '#client-internal';
|
|
@@ -29,6 +31,8 @@ import { getScrollY, scrollToHashTarget } from './scroll.js';
|
|
|
29
31
|
|
|
30
32
|
export interface RouterInitOptions {
|
|
31
33
|
initialElement?: unknown;
|
|
34
|
+
/** The params the hydration payload published, beside `initialElement`. */
|
|
35
|
+
initialParams?: ParamsSource;
|
|
32
36
|
clientSegmentCache?: boolean;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -58,6 +62,63 @@ export function createTimberRouter(options?: RouterInitOptions): RouterInitResul
|
|
|
58
62
|
// very first client navigation (before any wrapPayload call).
|
|
59
63
|
let currentPayload: unknown = options?.initialElement ?? null;
|
|
60
64
|
|
|
65
|
+
// The params `currentPayload` is displayed with. Tracked beside it because
|
|
66
|
+
// the two render paths that do NOT receive a payload — a shallow search
|
|
67
|
+
// update and any re-wrap of the current tree — must republish the same
|
|
68
|
+
// record rather than dropping it. They are written together, in one place
|
|
69
|
+
// (`renderTree`), so they cannot come apart.
|
|
70
|
+
let currentParams: ParamsSource = options?.initialParams ?? readPublishedParams(undefined);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build the client's wrapper chain around a tree. **The only place it is
|
|
74
|
+
* built.**
|
|
75
|
+
*
|
|
76
|
+
* Every render path goes through here — hydration builds the same chain in
|
|
77
|
+
* `hydrate.ts` against this shape, and a navigation, a shallow search
|
|
78
|
+
* update, a popstate replay and a revalidation all call this. That matters
|
|
79
|
+
* beyond tidiness: an element type present on one render and absent on the
|
|
80
|
+
* next changes the type at that position, so React unmounts and remounts
|
|
81
|
+
* everything below it, destroying the layout state partial navigation
|
|
82
|
+
* exists to preserve (caught by tests/e2e/segment-merge-navigation.test.ts
|
|
83
|
+
* when the params wrapper was conditional).
|
|
84
|
+
*
|
|
85
|
+
* `PayloadRoot` is *above* `currentPayload`, which on a partial navigation
|
|
86
|
+
* is the retained tree. That position is the whole design: it is the only
|
|
87
|
+
* provider a reader in a skipped layout can resolve to (TIM-1297).
|
|
88
|
+
*
|
|
89
|
+
* Pure — building the element does not make it the displayed one. Every
|
|
90
|
+
* caller pairs this with `publishTree` at the point the tree is actually
|
|
91
|
+
* handed to React, because a navigation builds its element before it is
|
|
92
|
+
* known to have won and a superseded one must leave `currentPayload`
|
|
93
|
+
* describing the route still on screen (TIM-1301).
|
|
94
|
+
*/
|
|
95
|
+
function renderTree(
|
|
96
|
+
tree: unknown,
|
|
97
|
+
navState: NavigationState,
|
|
98
|
+
params: ParamsSource,
|
|
99
|
+
segmentUpdates?: Map<string, React.ReactNode>
|
|
100
|
+
): React.ReactElement {
|
|
101
|
+
const withParams = createElement(PayloadRoot, { source: params }, tree as React.ReactNode);
|
|
102
|
+
const withNav = createElement(NavigationProvider, { value: navState }, withParams);
|
|
103
|
+
const withUpdates = createElement(
|
|
104
|
+
SegmentUpdateContext.Provider,
|
|
105
|
+
{ value: segmentUpdates ?? EMPTY_SEGMENT_UPDATES },
|
|
106
|
+
withNav
|
|
107
|
+
);
|
|
108
|
+
return createElement(TimberNuqsAdapter, null, withUpdates);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Record the tree the client is now displaying, and the params published
|
|
113
|
+
* over it. Written together — the two paths that re-wrap without a fresh
|
|
114
|
+
* payload (a shallow search update, a partial navigation) read them back as
|
|
115
|
+
* a pair and would otherwise restore one route's tree with another's params.
|
|
116
|
+
*/
|
|
117
|
+
function publishTree(tree: unknown, params: ParamsSource): void {
|
|
118
|
+
currentPayload = tree;
|
|
119
|
+
currentParams = params;
|
|
120
|
+
}
|
|
121
|
+
|
|
61
122
|
// ─── History API Patch ──────────────────────────────────────────
|
|
62
123
|
// Patch pushState/replaceState to detect external URL changes (nuqs
|
|
63
124
|
// shallow updates, replaceUrl, third-party libraries). When the router
|
|
@@ -93,25 +154,20 @@ export function createTimberRouter(options?: RouterInitOptions): RouterInitResul
|
|
|
93
154
|
if (currentSearch === navState.search) return;
|
|
94
155
|
if (!currentPayload) return;
|
|
95
156
|
|
|
157
|
+
// A shallow search update does not re-render the tree from the server, so
|
|
158
|
+
// the params are unchanged — but they are the client's to publish now, and
|
|
159
|
+
// this path rebuilds the whole chain. Republishing `currentParams` is what
|
|
160
|
+
// keeps a `?q=` change from silently clearing the route's params
|
|
161
|
+
// (TIM-1285's failure mode, on a path that used to be exempt from it
|
|
162
|
+
// because the record lived inside the payload).
|
|
96
163
|
const newNavState: NavigationState = {
|
|
97
|
-
params: navState.params,
|
|
98
164
|
pathname: navState.pathname,
|
|
99
165
|
search: currentSearch,
|
|
100
166
|
};
|
|
101
167
|
setNavigationState(newNavState);
|
|
102
168
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{ value: newNavState },
|
|
106
|
-
currentPayload as React.ReactNode
|
|
107
|
-
);
|
|
108
|
-
const withUpdates = createElement(
|
|
109
|
-
SegmentUpdateContext.Provider,
|
|
110
|
-
{ value: EMPTY_SEGMENT_UPDATES },
|
|
111
|
-
withNav
|
|
112
|
-
);
|
|
113
|
-
const wrapped = createElement(TimberNuqsAdapter, null, withUpdates);
|
|
114
|
-
transitionRender(wrapped);
|
|
169
|
+
publishTree(currentPayload, currentParams);
|
|
170
|
+
transitionRender(renderTree(currentPayload, newNavState, currentParams));
|
|
115
171
|
}
|
|
116
172
|
|
|
117
173
|
window.history.pushState = function (
|
|
@@ -179,58 +235,58 @@ export function createTimberRouter(options?: RouterInitOptions): RouterInitResul
|
|
|
179
235
|
// Wraps with NavigationProvider + TimberNuqsAdapter.
|
|
180
236
|
//
|
|
181
237
|
// For navigation renders (navigate, refresh, popstate-with-fetch),
|
|
182
|
-
// navigateTransition is used instead — it
|
|
183
|
-
// in a
|
|
238
|
+
// navigateTransition is used instead — it hands the destination tree to
|
|
239
|
+
// React in a transition so the departing page stays visible.
|
|
184
240
|
//
|
|
185
241
|
// navState is passed explicitly by the router — no temporal coupling
|
|
186
242
|
// with getNavigationState().
|
|
187
|
-
renderRoot: (element: unknown, navState: NavigationState) => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
element as React.ReactNode
|
|
193
|
-
);
|
|
194
|
-
const withUpdates = createElement(
|
|
195
|
-
SegmentUpdateContext.Provider,
|
|
196
|
-
{ value: EMPTY_SEGMENT_UPDATES },
|
|
197
|
-
withNav
|
|
198
|
-
);
|
|
199
|
-
const wrapped = createElement(TimberNuqsAdapter, null, withUpdates);
|
|
200
|
-
transitionRender(wrapped);
|
|
243
|
+
renderRoot: (element: unknown, navState: NavigationState, params: ParamsSource) => {
|
|
244
|
+
// These paths render what is already decided (a replay, a
|
|
245
|
+
// revalidation), so publishing here is the commit.
|
|
246
|
+
publishTree(element, params);
|
|
247
|
+
transitionRender(renderTree(element, navState, params));
|
|
201
248
|
},
|
|
202
249
|
|
|
203
|
-
//
|
|
204
|
-
// The
|
|
205
|
-
//
|
|
206
|
-
//
|
|
250
|
+
// Hand a navigation's tree to React in a transition (navigation-root.tsx).
|
|
251
|
+
// The fetch runs outside any transition scope; only the state update that
|
|
252
|
+
// hands the tree over is wrapped, and in a synchronous `startTransition`.
|
|
253
|
+
// Do NOT make this callback's caller an async React action — see the
|
|
254
|
+
// entanglement note in `navigateTransition` (TIM-1306, TIM-1307).
|
|
207
255
|
//
|
|
208
256
|
// The perform callback receives a wrapPayload function that wraps the
|
|
209
257
|
// decoded RSC payload with NavigationProvider + NuqsAdapter. navState
|
|
210
258
|
// is passed explicitly by the router — no getNavigationState() needed.
|
|
211
|
-
navigateTransition: (
|
|
212
|
-
return navigateTransition(
|
|
213
|
-
|
|
259
|
+
navigateTransition: (url: string, perform) => {
|
|
260
|
+
return navigateTransition(url, async () => {
|
|
261
|
+
// What wrapPayload built, held until the transition wins. The client's
|
|
262
|
+
// record of the displayed tree is navigation state like any other, so
|
|
263
|
+
// it rides the same commit as the segment cache rather than being
|
|
264
|
+
// written as a side effect of building the element (TIM-1301).
|
|
265
|
+
let displayed: { tree: unknown; params: ParamsSource } | null = null;
|
|
266
|
+
const { element, decodePromise, commit } = await perform(
|
|
214
267
|
(
|
|
215
268
|
rawPayload: unknown,
|
|
216
269
|
navState: NavigationState,
|
|
270
|
+
params: ParamsSource,
|
|
217
271
|
segmentUpdates?: Map<string, unknown>
|
|
218
272
|
) => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const withUpdates = createElement(
|
|
226
|
-
SegmentUpdateContext.Provider,
|
|
227
|
-
{ value: (segmentUpdates ?? EMPTY_SEGMENT_UPDATES) as Map<string, React.ReactNode> },
|
|
228
|
-
withNav
|
|
273
|
+
displayed = { tree: rawPayload, params };
|
|
274
|
+
return renderTree(
|
|
275
|
+
rawPayload,
|
|
276
|
+
navState,
|
|
277
|
+
params,
|
|
278
|
+
segmentUpdates as Map<string, React.ReactNode> | undefined
|
|
229
279
|
);
|
|
230
|
-
return createElement(TimberNuqsAdapter, null, withUpdates);
|
|
231
280
|
}
|
|
232
281
|
);
|
|
233
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
element: element as React.ReactNode,
|
|
284
|
+
decodePromise,
|
|
285
|
+
commit: () => {
|
|
286
|
+
if (displayed) publishTree(displayed.tree, displayed.params);
|
|
287
|
+
commit();
|
|
288
|
+
},
|
|
289
|
+
};
|
|
234
290
|
});
|
|
235
291
|
},
|
|
236
292
|
|
|
@@ -13,12 +13,20 @@
|
|
|
13
13
|
|
|
14
14
|
import { createFromReadableStream } from '../../rsc-runtime/browser.js';
|
|
15
15
|
import { isPageUnloading } from '../unload-guard.js';
|
|
16
|
+
import { splitPayloadRoot, type PublishedParams } from '../../shared/payload-root.js';
|
|
16
17
|
|
|
17
18
|
type FlightSegment = [isBootstrap: 0] | [isData: 1, data: string];
|
|
18
19
|
|
|
19
20
|
export interface RscStreamResult {
|
|
20
21
|
/** The decoded RSC element (thenable/lazy — resolved by React during render) */
|
|
21
22
|
element: unknown;
|
|
23
|
+
/**
|
|
24
|
+
* The params the same payload published beside that tree. A promise: the
|
|
25
|
+
* inlined Flight stream is still arriving when this runs, and hydration must
|
|
26
|
+
* not wait for it. `PayloadRoot` suspends on it at the position React was
|
|
27
|
+
* going to suspend on `element` anyway (TIM-1297).
|
|
28
|
+
*/
|
|
29
|
+
params: Promise<PublishedParams>;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
/**
|
|
@@ -152,6 +160,7 @@ export function createRscPayloadStream(): RscStreamResult | null {
|
|
|
152
160
|
queueMicrotask(onDOMContentLoaded);
|
|
153
161
|
}
|
|
154
162
|
|
|
155
|
-
const
|
|
156
|
-
|
|
163
|
+
const { tree, params } = splitPayloadRoot(createFromReadableStream(rscPayload));
|
|
164
|
+
// The stream is a thenable, so the split is asynchronous on both halves.
|
|
165
|
+
return { element: tree, params: params as Promise<PublishedParams> };
|
|
157
166
|
}
|
|
@@ -21,20 +21,12 @@
|
|
|
21
21
|
import { Component, createElement, type ReactNode } from 'react';
|
|
22
22
|
import { getSsrData } from './ssr-data.js';
|
|
23
23
|
import { getRouterOrNull } from './router-ref.js';
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
if (typeof window !== 'undefined') {
|
|
31
|
-
window.addEventListener('beforeunload', () => {
|
|
32
|
-
_isUnloading = true;
|
|
33
|
-
});
|
|
34
|
-
window.addEventListener('pagehide', () => {
|
|
35
|
-
_isUnloading = true;
|
|
36
|
-
});
|
|
37
|
-
}
|
|
24
|
+
import { markStaleFromError } from './stale-client.js';
|
|
25
|
+
// Page unload detection: when the page is being torn down, error boundaries
|
|
26
|
+
// suppress activation — the error is from the aborted connection, not the
|
|
27
|
+
// application. unload-guard.ts is the single owner of that state; this module
|
|
28
|
+
// used to keep its own copy, which drifted (TIM-1260).
|
|
29
|
+
import { isPageUnloading } from './unload-guard.js';
|
|
38
30
|
|
|
39
31
|
// ─── Digest Types ────────────────────────────────────────────────────────────
|
|
40
32
|
|
|
@@ -124,13 +116,26 @@ export class TimberErrorBoundary extends Component<
|
|
|
124
116
|
// The aborted connection causes React's streaming hydration to error,
|
|
125
117
|
// but the page is about to be replaced — showing an error boundary
|
|
126
118
|
// would be a jarring flash for the user.
|
|
127
|
-
if (
|
|
119
|
+
if (isPageUnloading()) {
|
|
128
120
|
return { hasError: false, error: null };
|
|
129
121
|
}
|
|
130
122
|
|
|
131
123
|
return { hasError: true, error };
|
|
132
124
|
}
|
|
133
125
|
|
|
126
|
+
componentDidCatch(error: Error): void {
|
|
127
|
+
// A stale bundle most often surfaces here rather than as a rejected
|
|
128
|
+
// navigation: a client reference missing from this build's manifest, or
|
|
129
|
+
// a lazily-imported chunk the CDN no longer has, thrown during render.
|
|
130
|
+
//
|
|
131
|
+
// Recording it is all that happens — this boundary still renders its
|
|
132
|
+
// fallback, and the recovery is the user's next navigation, which the
|
|
133
|
+
// router turns into a full document load. Reloading from here would be a
|
|
134
|
+
// page load the user did not ask for, on a page that may be mid-rollout
|
|
135
|
+
// and would come back just as stale (TIM-1275).
|
|
136
|
+
markStaleFromError(error);
|
|
137
|
+
}
|
|
138
|
+
|
|
134
139
|
componentDidUpdate(prevProps: TimberErrorBoundaryProps): void {
|
|
135
140
|
// Reset error state when children change (e.g. client-side navigation).
|
|
136
141
|
// Without this, navigating from one error page to another keeps the
|
package/src/client/history.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// History Stack — stores RSC payloads by URL for instant back/forward navigation
|
|
2
2
|
// See design/19-client-navigation.md § History Stack
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { ParamsSource } from '../shared/payload-root.js';
|
|
5
|
+
import type { SegmentInfo } from '../shared/segment-info.js';
|
|
5
6
|
|
|
6
7
|
// ─── Types ───────────────────────────────────────────────────────
|
|
7
8
|
|
|
@@ -9,12 +10,18 @@ export interface HistoryEntry {
|
|
|
9
10
|
/** The complete segment tree payload at the time of navigation */
|
|
10
11
|
payload: unknown;
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* The params that payload published. Replaying the tree republishes them,
|
|
14
|
+
* because the client owns the provider now and reads it from here
|
|
15
|
+
* (TIM-1297).
|
|
16
|
+
*
|
|
17
|
+
* Stored beside the tree but never *produced* beside it: both come out of
|
|
18
|
+
* one `splitPayloadRoot` on one decoded root and travel together through
|
|
19
|
+
* `prepareNavigation`, the only producer of an entry. That is the property
|
|
20
|
+
* that matters — when they were derived from separate channels, a replay
|
|
21
|
+
* could restore one route's tree with another route's params (TIM-1037,
|
|
22
|
+
* TIM-1285).
|
|
16
23
|
*/
|
|
17
|
-
params
|
|
24
|
+
params: ParamsSource;
|
|
18
25
|
/**
|
|
19
26
|
* Segment metadata for this page's route. Restored into the segment cache
|
|
20
27
|
* on popstate cached replay so the next forward navigation computes a
|
package/src/client/internal.ts
CHANGED
|
@@ -19,7 +19,7 @@ export type {
|
|
|
19
19
|
RouterPhase,
|
|
20
20
|
RscDecoder,
|
|
21
21
|
RootRenderer,
|
|
22
|
-
} from './router.js';
|
|
22
|
+
} from './router-types.js';
|
|
23
23
|
export { getRouter, getRouterOrNull, setGlobalRouter } from './router-ref.js';
|
|
24
24
|
|
|
25
25
|
// ── SSR bridge ───────────────────────────────────────────────────────────
|
|
@@ -32,14 +32,15 @@ export type { SegmentContextValue } from './segment-context.js';
|
|
|
32
32
|
|
|
33
33
|
// ── Segment cache ────────────────────────────────────────────────────────
|
|
34
34
|
export { SegmentCache, PrefetchCache } from './segment-cache.js';
|
|
35
|
-
export type { SegmentNode,
|
|
35
|
+
export type { SegmentNode, PrefetchKey } from './segment-cache.js';
|
|
36
|
+
export type { SegmentInfo, StateTree } from '../shared/segment-info.js';
|
|
36
37
|
|
|
37
38
|
// ── History ──────────────────────────────────────────────────────────────
|
|
38
39
|
export { HistoryStack } from './history.js';
|
|
39
40
|
export type { HistoryEntry } from './history.js';
|
|
40
41
|
|
|
41
42
|
// ── Params (internal setter + raw hooks) ─────────────────────────────────
|
|
42
|
-
export { setCurrentParams } from './use-segment-params.js';
|
|
43
|
+
export { setCurrentParams, setCurrentSlotParams } from './use-segment-params.js';
|
|
43
44
|
export { useSearchParams } from './use-search-params.js';
|
|
44
45
|
|
|
45
46
|
// ── Navigation context ───────────────────────────────────────────────────
|
package/src/client/link.tsx
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
// - searchParams and inline query string are mutually exclusive
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
-
|
|
22
|
+
useRef,
|
|
23
|
+
useState,
|
|
23
24
|
type AnchorHTMLAttributes,
|
|
24
25
|
type ReactNode,
|
|
25
26
|
type MouseEvent as ReactMouseEvent,
|
|
@@ -472,11 +473,38 @@ export const Link: LinkFunction = function LinkImpl(props: any) {
|
|
|
472
473
|
const { href: baseHref } = buildLinkProps({ href, params: segmentParams, searchParams });
|
|
473
474
|
|
|
474
475
|
// ─── Per-link pending state ─────────────────────────────────────────
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
|
|
476
|
+
// Local `useState`, deliberately NOT `useTransition` (TIM-1307).
|
|
477
|
+
//
|
|
478
|
+
// Either shape keeps the re-render local — the state lives on this Link's
|
|
479
|
+
// own fiber, so no sibling link is touched. The difference is what wrapping
|
|
480
|
+
// `router.navigate()` in a transition does to the REST of the app: returning
|
|
481
|
+
// a thenable from `startTransition` hands it to `ReactSharedInternals.S`,
|
|
482
|
+
// which calls react-dom's `entangleAsyncAction`. That opens an action scope
|
|
483
|
+
// whose `currentEntangledLane` collects every transition update scheduled
|
|
484
|
+
// while it is open — including `NavigationRoot`'s `setRendered`, which is a
|
|
485
|
+
// fully synchronous `startTransition` in a different component — and
|
|
486
|
+
// rendering that lane suspends on `currentEntangledActionThenable` until the
|
|
487
|
+
// action settles. `navigateTransition` awaits `decodePromise`, so a Link
|
|
488
|
+
// navigation used to commit only after the whole Flight stream had decoded,
|
|
489
|
+
// while `useRouter().push()` committed as soon as React could render it.
|
|
490
|
+
//
|
|
491
|
+
// Two commit-timing regimes, and the slower one was the dominant path: no
|
|
492
|
+
// streaming reveal, and the TIM-1301 publish (address bar, segment cache,
|
|
493
|
+
// `timber:navigation-end`) waited for full decode. Not opening an action
|
|
494
|
+
// scope is what collapses them into one.
|
|
495
|
+
//
|
|
496
|
+
// `isPending` runs from the click until `router.navigate()` settles, which
|
|
497
|
+
// is after `decodePromise` — the same lifecycle the router's pending store
|
|
498
|
+
// and the TopLoader use.
|
|
499
|
+
//
|
|
500
|
+
// `clickSeq` exists because a superseded navigation RESOLVES rather than
|
|
501
|
+
// rejecting: `runNavigation` swallows AbortErrors. So clicking the same link
|
|
502
|
+
// twice in quick succession settles the first promise while the second is
|
|
503
|
+
// still in flight, and an unguarded clear would report idle mid-navigation.
|
|
504
|
+
// Only the newest click for this link may clear its flag. Written on click
|
|
505
|
+
// and nowhere else, so there is no reset for a settle handler to race.
|
|
506
|
+
const [isPending, setIsPending] = useState(false);
|
|
507
|
+
const clickSeq = useRef(0);
|
|
480
508
|
const linkStatus = isPending ? LINK_PENDING : LINK_IDLE;
|
|
481
509
|
|
|
482
510
|
// Preserve search params from the current URL when requested.
|
|
@@ -544,8 +572,27 @@ export const Link: LinkFunction = function LinkImpl(props: any) {
|
|
|
544
572
|
// from the RSC fetch URL inside the router (TIM-1035).
|
|
545
573
|
const absoluteHref = resolved.pathname + resolved.search + resolved.hash;
|
|
546
574
|
|
|
547
|
-
|
|
548
|
-
|
|
575
|
+
const seq = ++clickSeq.current;
|
|
576
|
+
const settle = () => {
|
|
577
|
+
if (clickSeq.current === seq) setIsPending(false);
|
|
578
|
+
};
|
|
579
|
+
setIsPending(true);
|
|
580
|
+
router.navigate(absoluteHref, { scroll: shouldScroll }).then(settle, (error: unknown) => {
|
|
581
|
+
settle();
|
|
582
|
+
// Rethrow, so a navigation error that the router did not already
|
|
583
|
+
// recover from surfaces as an unhandled rejection — the same
|
|
584
|
+
// regime as `useRouter().push()`'s `void router.navigate(...)`.
|
|
585
|
+
//
|
|
586
|
+
// It used to reach the nearest error boundary instead: React
|
|
587
|
+
// re-throws a rejected async action during render (measured on
|
|
588
|
+
// 19.2.7). That replaced the departing page with the app's error
|
|
589
|
+
// UI, which contradicts what every other failure path here
|
|
590
|
+
// promises — a navigation that fails leaves the user on the page
|
|
591
|
+
// they were already looking at (TIM-1306). Recoverable failures
|
|
592
|
+
// never get here anyway; `runNavigation` swallows AbortErrors and
|
|
593
|
+
// `recoverFromNavigationError` turns a failed fetch into a full
|
|
594
|
+
// document load.
|
|
595
|
+
throw error;
|
|
549
596
|
});
|
|
550
597
|
}
|
|
551
598
|
: userOnClick; // External links — just pass through user's onClick
|
|
@@ -97,8 +97,8 @@ export interface NavigationApiController {
|
|
|
97
97
|
/**
|
|
98
98
|
* Resolve the deferred promise created by setRouterNavigating(true),
|
|
99
99
|
* clearing the browser's native loading state. Call this when the
|
|
100
|
-
* navigation fully completes —
|
|
101
|
-
*
|
|
100
|
+
* navigation fully completes — the same finally block in router.navigate
|
|
101
|
+
* that clears the router's pending store.
|
|
102
102
|
*/
|
|
103
103
|
completeRouterNavigation: () => void;
|
|
104
104
|
|
|
@@ -147,7 +147,7 @@ export function setupNavigationApi(callbacks: NavigationApiCallbacks): Navigatio
|
|
|
147
147
|
// setRouterNavigating(true) is called, resolved by completeRouterNavigation().
|
|
148
148
|
// The navigate event handler intercepts with this promise so the browser's
|
|
149
149
|
// native loading state (tab spinner) stays active until the navigation
|
|
150
|
-
// completes —
|
|
150
|
+
// completes — the same lifecycle the TopLoader is driven by.
|
|
151
151
|
let routerNavDeferred: { promise: Promise<void>; resolve: () => void } | null = null;
|
|
152
152
|
|
|
153
153
|
function handleNavigate(event: NavigateEvent): void {
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What it means for a page to be "current" on the client.
|
|
3
|
+
*
|
|
4
|
+
* One module owns every write that makes a navigation's destination the
|
|
5
|
+
* current page, and the read that describes the current page back to the
|
|
6
|
+
* server. Keeping them together is the point: `X-Timber-State-Tree` is
|
|
7
|
+
* derived from the segment cache, so the thing that publishes and the thing
|
|
8
|
+
* that reports have to agree about when a page becomes current.
|
|
9
|
+
*
|
|
10
|
+
* See design/19-client-navigation.md §"State Update Invariants".
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { buildSegmentTree, type SegmentCache } from './segment-cache.js';
|
|
14
|
+
import type { HistoryStack } from './history.js';
|
|
15
|
+
import type { SegmentInfo, StateTree } from '../shared/segment-info.js';
|
|
16
|
+
import type { ParamsSource } from '../shared/payload-root.js';
|
|
17
|
+
import { setNavigationState, type NavigationState } from './navigation-context.js';
|
|
18
|
+
|
|
19
|
+
/** Everything a commit needs to describe the page it makes current. */
|
|
20
|
+
export interface NavigationCommitInput {
|
|
21
|
+
payload: unknown;
|
|
22
|
+
/** The params that payload published — replayed with it on popstate. */
|
|
23
|
+
params: ParamsSource;
|
|
24
|
+
/**
|
|
25
|
+
* The rendered chain's metadata, or null/undefined when this commit
|
|
26
|
+
* carries none at all — a revalidation payload, or a history entry
|
|
27
|
+
* stored before the channel existed.
|
|
28
|
+
*
|
|
29
|
+
* An empty *array* is a different statement from an absent one: the
|
|
30
|
+
* server rendered a route with no layout-bearing segments (a layoutless
|
|
31
|
+
* app — the missing-root-layout check is a warning, not an error), so
|
|
32
|
+
* the correct tree is the empty one. Collapsing the two left the
|
|
33
|
+
* departing route's tree in place, which `treePaths` then reported as
|
|
34
|
+
* mounted — enough to select an intercepting rewrite whose slot is not
|
|
35
|
+
* on screen (TIM-1282).
|
|
36
|
+
*/
|
|
37
|
+
segmentInfo?: SegmentInfo[] | null;
|
|
38
|
+
/** When true, clear the segment cache if segmentInfo is absent
|
|
39
|
+
* (popstate replay for entries without layout metadata). */
|
|
40
|
+
clearSegmentCacheOnEmpty?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A navigation's destination, split into what a render needs and what a
|
|
45
|
+
* commit publishes.
|
|
46
|
+
*/
|
|
47
|
+
export interface PreparedNavigation {
|
|
48
|
+
/** The state the incoming tree renders with. Pure — safe before winning. */
|
|
49
|
+
navState: NavigationState;
|
|
50
|
+
/** Publishes the destination. Call only once the render has won. */
|
|
51
|
+
commit: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface NavigationCommitter {
|
|
55
|
+
prepareNavigation(url: string, opts: NavigationCommitInput): PreparedNavigation;
|
|
56
|
+
currentStateTree(): StateTree | undefined;
|
|
57
|
+
updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Derive the navigation state (pathname + search) a URL renders with.
|
|
62
|
+
*
|
|
63
|
+
* Pure: publishing it — the module-level fallback for tests and SSR, and the
|
|
64
|
+
* globalThis bridge — is `prepareNavigation`'s commit, which runs only once
|
|
65
|
+
* the render is known to have won (TIM-1301). Callers pass the returned value
|
|
66
|
+
* explicitly to renderRoot/wrapPayload, so a render never depends on the
|
|
67
|
+
* publish having happened first.
|
|
68
|
+
*/
|
|
69
|
+
export function deriveNavigationState(url: string): NavigationState {
|
|
70
|
+
// Params are not part of it. They travel with the payload and are published
|
|
71
|
+
// by the one client-owned `ParamsProvider` as the new tree renders, which is
|
|
72
|
+
// also what clears the previous route's — a route with no params of its own
|
|
73
|
+
// still publishes an empty record (TIM-1294/TIM-1297, and the clearing
|
|
74
|
+
// requirement from TIM-1285).
|
|
75
|
+
const parsed = new URL(url, 'http://localhost');
|
|
76
|
+
return { pathname: parsed.pathname || '/', search: parsed.search };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createNavigationCommitter(deps: {
|
|
80
|
+
segmentCache: SegmentCache;
|
|
81
|
+
historyStack: HistoryStack;
|
|
82
|
+
/** Read at call time — the router's `clientSegmentCache` opt-in. */
|
|
83
|
+
clientSegmentCache: () => boolean | undefined;
|
|
84
|
+
}): NavigationCommitter {
|
|
85
|
+
const { segmentCache, historyStack } = deps;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Update the segment cache from server-provided segment metadata.
|
|
89
|
+
*
|
|
90
|
+
* Runs whether or not `clientSegmentCache` is enabled: the tree is also
|
|
91
|
+
* where the mounted tree paths come from, and those cross on every
|
|
92
|
+
* navigation. What the flag gates is which parts get serialized — see
|
|
93
|
+
* `currentStateTree`.
|
|
94
|
+
*/
|
|
95
|
+
function updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void {
|
|
96
|
+
if (!segmentInfo || segmentInfo.length === 0) return;
|
|
97
|
+
const tree = buildSegmentTree(segmentInfo);
|
|
98
|
+
if (tree) {
|
|
99
|
+
segmentCache.set('/', tree);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
updateSegmentCache,
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The X-Timber-State-Tree to send with the next RSC request.
|
|
108
|
+
*
|
|
109
|
+
* Two different things travel on one header. `segments`/`slots` are the
|
|
110
|
+
* caching opt-in and are omitted unless `clientSegmentCache` is on.
|
|
111
|
+
* `treePaths` is how the server learns which route is actually mounted,
|
|
112
|
+
* which stops being derivable from the address bar as soon as a modal is
|
|
113
|
+
* open — so it crosses regardless (TIM-1282). Undefined when there is
|
|
114
|
+
* nothing to say, so a no-op header never enters the `_rsc` cache key.
|
|
115
|
+
*
|
|
116
|
+
* Reads what the last *committed* navigation published, so a superseded
|
|
117
|
+
* navigation's destination can never end up describing the mounted tree
|
|
118
|
+
* (TIM-1301).
|
|
119
|
+
*/
|
|
120
|
+
currentStateTree(): StateTree | undefined {
|
|
121
|
+
const tree = segmentCache.serializeStateTree();
|
|
122
|
+
if (deps.clientSegmentCache()) return tree;
|
|
123
|
+
return tree.treePaths ? { segments: [], treePaths: tree.treePaths } : undefined;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Prepare all navigation-owned state for a new page — without publishing
|
|
128
|
+
* any of it. Every code path that changes the "current page" must go
|
|
129
|
+
* through this function, which is what makes "forgot a field" impossible
|
|
130
|
+
* by construction.
|
|
131
|
+
*
|
|
132
|
+
* Returns the destination's `NavigationState`, which is a pure function of
|
|
133
|
+
* the URL and is what the incoming tree is *rendered* with, plus a
|
|
134
|
+
* `commit` thunk that performs the writes that make a page current:
|
|
135
|
+
* 1. Segment cache — update from server-provided segment metadata
|
|
136
|
+
* 2. Navigation state — pathname/search for usePathname/useSearchParams
|
|
137
|
+
* 3. History stack — store the payload for instant back/forward replay
|
|
138
|
+
*
|
|
139
|
+
* Nothing is written until `commit()` runs, and on a transitioned
|
|
140
|
+
* navigation it runs only once the transition is known to have won — see
|
|
141
|
+
* `renderViaTransition` and `NavigationRoot`. A navigation superseded
|
|
142
|
+
* while its payload was in flight never calls it, so the cache, the
|
|
143
|
+
* pathname the client reports and the history stack all keep describing
|
|
144
|
+
* the route still on screen (TIM-1301).
|
|
145
|
+
*
|
|
146
|
+
* Callers with state of their own to publish — the address bar, the
|
|
147
|
+
* client's record of the mounted tree — wrap this thunk rather than
|
|
148
|
+
* commit beside it, so there stays exactly one moment at which a page
|
|
149
|
+
* becomes current.
|
|
150
|
+
*/
|
|
151
|
+
prepareNavigation(url: string, opts: NavigationCommitInput): PreparedNavigation {
|
|
152
|
+
const navState = deriveNavigationState(url);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
navState,
|
|
156
|
+
commit() {
|
|
157
|
+
if (opts.segmentInfo && opts.segmentInfo.length > 0) {
|
|
158
|
+
updateSegmentCache(opts.segmentInfo);
|
|
159
|
+
} else if (opts.segmentInfo?.length === 0 || opts.clearSegmentCacheOnEmpty) {
|
|
160
|
+
segmentCache.clear();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
setNavigationState(navState);
|
|
164
|
+
|
|
165
|
+
historyStack.push(url, {
|
|
166
|
+
payload: opts.payload,
|
|
167
|
+
params: opts.params,
|
|
168
|
+
segmentInfo: opts.segmentInfo,
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|