@timber-js/app 0.2.0-alpha.185 → 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-Byy425ng.js.map → segment-classify-C539Pa2O.js.map} +1 -1
- 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-_6zKFlch.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 +865 -306
- 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 +38 -25
- 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 +906 -321
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +31 -0
- 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 +136 -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/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-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 +393 -212
- 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/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/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/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/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 +2 -0
- 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 -707
- package/src/client/rsc-fetch.ts +169 -102
- 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 +32 -0
- 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 +59 -2
- package/src/plugins/static-build.ts +315 -73
- 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 +5 -3
- package/src/routing/scanner.ts +114 -89
- 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/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-renderer.ts +81 -20
- 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 +2 -4
- package/src/server/pipeline-phases.ts +68 -16
- package/src/server/pipeline.ts +57 -10
- package/src/server/prebuilt-runtime.ts +4 -4
- 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 +9 -4
- 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/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 +65 -27
- package/src/server/static-not-found.ts +134 -0
- 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/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-BTWEKJXo.js +0 -668
- package/dist/_chunks/cli-schema-sync-BTWEKJXo.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/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-_6zKFlch.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,14 +9,18 @@
|
|
|
9
9
|
* NavigationRoot holds the current element in React state. Navigation
|
|
10
10
|
* updates call startTransition(() => setState(newElement)), which IS
|
|
11
11
|
* a transition update. React keeps the old committed tree visible while
|
|
12
|
-
*
|
|
12
|
+
* the new tree resolves, instead of hiding it behind a Suspense fallback.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* The navigation's async work runs OUTSIDE the transition scope and every
|
|
15
|
+
* state update it schedules gets its own synchronous `startTransition` — a
|
|
16
|
+
* transition scope does not survive an `await`, and an async callback that
|
|
17
|
+
* returns a thenable defers the commit past the promise callers await. See
|
|
18
|
+
* the long comment on `_navigateTransition` (TIM-1306).
|
|
19
|
+
*
|
|
20
|
+
* This component holds no pending state. The `TopLoader` it renders and the
|
|
21
|
+
* public `usePendingNavigation()` both subscribe to the router's external
|
|
22
|
+
* pending store; NavigationRoot's own `pendingUrl` was a second, unread
|
|
23
|
+
* representation of the same fact and is gone (TIM-1307).
|
|
20
24
|
*
|
|
21
25
|
* Hard navigation guard: When a hard navigation is triggered (500 error,
|
|
22
26
|
* version skew), the component throws an unresolved thenable AFTER all
|
|
@@ -31,10 +35,56 @@
|
|
|
31
35
|
* See design/19-client-navigation.md §"NavigationContext"
|
|
32
36
|
*/
|
|
33
37
|
|
|
34
|
-
import {
|
|
35
|
-
|
|
38
|
+
import {
|
|
39
|
+
createElement,
|
|
40
|
+
Fragment,
|
|
41
|
+
startTransition,
|
|
42
|
+
useLayoutEffect,
|
|
43
|
+
useRef,
|
|
44
|
+
useState,
|
|
45
|
+
type ReactNode,
|
|
46
|
+
} from 'react';
|
|
36
47
|
import { TopLoader, type TopLoaderConfig } from './top-loader.js';
|
|
37
48
|
|
|
49
|
+
// ─── Transition Result ──────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* What a navigation's `perform()` hands back to the transition.
|
|
53
|
+
*
|
|
54
|
+
* Declared once and shared by every layer that passes it along — the router's
|
|
55
|
+
* `RouterDeps.navigateTransition` imports it too — so a field cannot be added
|
|
56
|
+
* to the producer and dropped by the adapter in between. `E` is the element
|
|
57
|
+
* type: `ReactNode` here, `unknown` in the router, which never touches it.
|
|
58
|
+
*/
|
|
59
|
+
export interface TransitionResult<E = ReactNode> {
|
|
60
|
+
/** The wrapped tree to render. */
|
|
61
|
+
element: E;
|
|
62
|
+
/** Resolves when the Flight stream finishes decoding, or null. */
|
|
63
|
+
decodePromise: Promise<void> | null;
|
|
64
|
+
/**
|
|
65
|
+
* Publishes the navigation's state — segment cache, pathname, address bar,
|
|
66
|
+
* history stack, and the client's record of the mounted tree — and
|
|
67
|
+
* announces it to listeners outside React.
|
|
68
|
+
*
|
|
69
|
+
* Run when React commits this tree, not when the tree is handed over: a
|
|
70
|
+
* tree React is still waiting on, or one a later navigation replaces first,
|
|
71
|
+
* has been given to React without being on screen. A superseded navigation
|
|
72
|
+
* never runs it and leaves every one of those consumers describing the
|
|
73
|
+
* route still on screen (TIM-1301).
|
|
74
|
+
*/
|
|
75
|
+
commit: () => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The tree NavigationRoot holds in state, and the state publish that belongs
|
|
80
|
+
* to it. They travel as one object so React's commit of the tree is the event
|
|
81
|
+
* that publishes — see the effect in NavigationRoot.
|
|
82
|
+
*/
|
|
83
|
+
interface RenderedTree {
|
|
84
|
+
element: ReactNode;
|
|
85
|
+
publish: (() => void) | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
38
88
|
// ─── Navigation Transition Counter ──────────────────────────────
|
|
39
89
|
// Monotonically increasing counter that increments each time
|
|
40
90
|
// navigateTransition() is called. Used to detect stale transitions:
|
|
@@ -50,12 +100,37 @@ import { TopLoader, type TopLoaderConfig } from './top-loader.js';
|
|
|
50
100
|
|
|
51
101
|
const NAV_TRANSITION_KEY = Symbol.for('__timber_nav_transition_counter');
|
|
52
102
|
|
|
53
|
-
|
|
103
|
+
/**
|
|
104
|
+
* `waiters` are woken on every bump so an in-flight navigation can stop
|
|
105
|
+
* waiting on its own payload the moment it is superseded — see
|
|
106
|
+
* `settleOnDecodeOrSupersession`.
|
|
107
|
+
*/
|
|
108
|
+
interface TransitionCounter {
|
|
109
|
+
id: number;
|
|
110
|
+
waiters: Set<() => void>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getTransitionCounter(): TransitionCounter {
|
|
54
114
|
const g = globalThis as Record<symbol, unknown>;
|
|
55
|
-
|
|
56
|
-
|
|
115
|
+
const existing = g[NAV_TRANSITION_KEY] as Partial<TransitionCounter> | undefined;
|
|
116
|
+
if (!existing) {
|
|
117
|
+
const created: TransitionCounter = { id: 0, waiters: new Set() };
|
|
118
|
+
g[NAV_TRANSITION_KEY] = created;
|
|
119
|
+
return created;
|
|
57
120
|
}
|
|
58
|
-
|
|
121
|
+
// A duplicated copy of this module may have created the singleton before
|
|
122
|
+
// `waiters` existed. The object is shared across chunks, so fill it in
|
|
123
|
+
// rather than replacing it — replacing would strand the other copy's id.
|
|
124
|
+
existing.waiters ??= new Set();
|
|
125
|
+
return existing as TransitionCounter;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Bump the counter and wake everything waiting on an older transition. */
|
|
129
|
+
function bumpTransitionCounter(): number {
|
|
130
|
+
const counter = getTransitionCounter();
|
|
131
|
+
counter.id += 1;
|
|
132
|
+
for (const wake of [...counter.waiters]) wake();
|
|
133
|
+
return counter.id;
|
|
59
134
|
}
|
|
60
135
|
|
|
61
136
|
/**
|
|
@@ -71,7 +146,54 @@ function getTransitionCounter(): { id: number } {
|
|
|
71
146
|
* replayed back page (TIM-1022).
|
|
72
147
|
*/
|
|
73
148
|
export function supersedeNavigationTransitions(): void {
|
|
74
|
-
|
|
149
|
+
bumpTransitionCounter();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Wait for the payload to finish decoding, OR for this transition to be
|
|
154
|
+
* superseded — whichever happens first.
|
|
155
|
+
*
|
|
156
|
+
* A superseded navigation must stop waiting on its own stream. The stream is
|
|
157
|
+
* deliberately NOT aborted once its tree has been handed to React (the tree
|
|
158
|
+
* may be on screen with boundaries still feeding from it — see
|
|
159
|
+
* `handedOffNavAbort` in `client/router.ts`), so there is nothing left to make
|
|
160
|
+
* `decodePromise` settle promptly. Awaiting it bare would keep the loser's
|
|
161
|
+
* `router.navigate()` promise pending for the rest of the stream — and
|
|
162
|
+
* forever if it stalls — which is what `<Link>`'s `isPending` is timed
|
|
163
|
+
* against, so the losing link would sit spinning while the winner loaded
|
|
164
|
+
* (codex on #1004).
|
|
165
|
+
*
|
|
166
|
+
* A decode *failure* still propagates: it is a real error for this
|
|
167
|
+
* navigation, and the caller's recovery is timed against it.
|
|
168
|
+
*/
|
|
169
|
+
function settleOnDecodeOrSupersession(
|
|
170
|
+
decodePromise: Promise<void>,
|
|
171
|
+
counter: TransitionCounter,
|
|
172
|
+
transId: number
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
if (counter.id !== transId) return Promise.resolve();
|
|
175
|
+
return new Promise<void>((resolve, reject) => {
|
|
176
|
+
const stopWaiting = (): void => {
|
|
177
|
+
counter.waiters.delete(wake);
|
|
178
|
+
};
|
|
179
|
+
const wake = (): void => {
|
|
180
|
+
if (counter.id !== transId) {
|
|
181
|
+
stopWaiting();
|
|
182
|
+
resolve();
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
counter.waiters.add(wake);
|
|
186
|
+
decodePromise.then(
|
|
187
|
+
() => {
|
|
188
|
+
stopWaiting();
|
|
189
|
+
resolve();
|
|
190
|
+
},
|
|
191
|
+
(error: unknown) => {
|
|
192
|
+
stopWaiting();
|
|
193
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
});
|
|
75
197
|
}
|
|
76
198
|
|
|
77
199
|
// ─── Hard Navigation Guard ──────────────────────────────────────
|
|
@@ -143,14 +265,13 @@ let _transitionRender: ((element: ReactNode) => void) | null = null;
|
|
|
143
265
|
|
|
144
266
|
/**
|
|
145
267
|
* Module-level reference to the navigation transition function.
|
|
146
|
-
*
|
|
147
|
-
*
|
|
268
|
+
*
|
|
269
|
+
* Runs the fetch OUTSIDE any transition scope and wraps only the state update
|
|
270
|
+
* that hands the resulting tree to React — describing this as "a full
|
|
271
|
+
* navigation in a single startTransition" is the shape TIM-1306 removed.
|
|
148
272
|
*/
|
|
149
273
|
let _navigateTransition:
|
|
150
|
-
| ((
|
|
151
|
-
pendingUrl: string,
|
|
152
|
-
perform: () => Promise<{ element: ReactNode; decodePromise: Promise<void> | null }>
|
|
153
|
-
) => Promise<void>)
|
|
274
|
+
| ((url: string, perform: () => Promise<TransitionResult>) => Promise<void>)
|
|
154
275
|
| null = null;
|
|
155
276
|
|
|
156
277
|
// ─── Component ───────────────────────────────────────────────────
|
|
@@ -158,9 +279,8 @@ let _navigateTransition:
|
|
|
158
279
|
/**
|
|
159
280
|
* Root wrapper component that enables transition-based rendering.
|
|
160
281
|
*
|
|
161
|
-
* Renders
|
|
162
|
-
*
|
|
163
|
-
* (the provider renders no extra DOM elements).
|
|
282
|
+
* Renders the TopLoader alongside the tree it holds in state. Neither adds a
|
|
283
|
+
* DOM element on the hydration path, so the tree matches the server HTML.
|
|
164
284
|
*
|
|
165
285
|
* Usage in browser-entry.ts:
|
|
166
286
|
* const rootEl = createElement(NavigationRoot, { initial: wrapped });
|
|
@@ -179,8 +299,39 @@ export function NavigationRoot({
|
|
|
179
299
|
initial: ReactNode;
|
|
180
300
|
topLoaderConfig?: TopLoaderConfig;
|
|
181
301
|
}): ReactNode {
|
|
182
|
-
const [
|
|
183
|
-
|
|
302
|
+
const [rendered, setRendered] = useState<RenderedTree>({ element: initial, publish: null });
|
|
303
|
+
|
|
304
|
+
// Publish the navigation's state when React commits its tree — not when the
|
|
305
|
+
// tree is handed over. A tree React is still waiting on, or one a later
|
|
306
|
+
// navigation replaces before React renders it, has been *given* to React
|
|
307
|
+
// without being on screen; publishing then describes a route the user never
|
|
308
|
+
// saw, which is the defect this ordering exists to prevent (TIM-1301).
|
|
309
|
+
//
|
|
310
|
+
// A **layout** effect, so the publish lands before the browser paints and,
|
|
311
|
+
// more importantly, before every descendant's passive effect. A destination
|
|
312
|
+
// component that navigates from a mount effect — a redirect guard, a
|
|
313
|
+
// `refresh()` on mount — would otherwise start its navigation while the
|
|
314
|
+
// segment cache, address bar and pathname still described the departing
|
|
315
|
+
// route, and send an X-Timber-State-Tree for a tree that is no longer
|
|
316
|
+
// mounted (codex on #998).
|
|
317
|
+
//
|
|
318
|
+
// Descendant *layout* effects still run first: React runs layout effects
|
|
319
|
+
// child-before-parent, and the only way to precede them would be a fiber
|
|
320
|
+
// rendered as an earlier sibling of the payload. That is not free here —
|
|
321
|
+
// `server/ssr-wrappers.tsx` mirrors this component's fiber shape so `useId`
|
|
322
|
+
// agrees across hydration, and an extra sibling shifts every id in the
|
|
323
|
+
// payload subtree. A layout effect that navigates on mount is the price.
|
|
324
|
+
//
|
|
325
|
+
// Keyed on the tree object rather than on the effect run. A publish moves
|
|
326
|
+
// the address bar, which is not idempotent, so any second invocation for the
|
|
327
|
+
// same tree — an effect re-run React is entitled to perform — must be a
|
|
328
|
+
// no-op rather than a second history entry.
|
|
329
|
+
const publishedRef = useRef<RenderedTree | null>(null);
|
|
330
|
+
useLayoutEffect(() => {
|
|
331
|
+
if (publishedRef.current === rendered) return;
|
|
332
|
+
publishedRef.current = rendered;
|
|
333
|
+
rendered.publish?.();
|
|
334
|
+
}, [rendered]);
|
|
184
335
|
|
|
185
336
|
// NOTE: We use standalone `startTransition` (imported from 'react'),
|
|
186
337
|
// NOT `useTransition`. The `useTransition` hook's `startTransition`
|
|
@@ -192,67 +343,118 @@ export function NavigationRoot({
|
|
|
192
343
|
//
|
|
193
344
|
// Standalone `startTransition` creates independent transition lanes
|
|
194
345
|
// for each call, so concurrent navigations don't interfere. We don't
|
|
195
|
-
// need useTransition's `isPending` —
|
|
196
|
-
//
|
|
346
|
+
// need useTransition's `isPending` — pending state lives in the router's
|
|
347
|
+
// external store, which TopLoader and usePendingNavigation() subscribe to.
|
|
197
348
|
//
|
|
198
349
|
// This matches the Next.js pattern (TIM-625): "No useTransition in
|
|
199
350
|
// the router at all — only standalone startTransition."
|
|
200
351
|
|
|
201
352
|
// Non-navigation render (revalidation, popstate cached replay).
|
|
353
|
+
// Non-navigation render (revalidation, popstate cached replay). Both publish
|
|
354
|
+
// synchronously in the router before calling this — neither has an in-flight
|
|
355
|
+
// window in which it could be superseded — so there is nothing to publish
|
|
356
|
+
// on commit.
|
|
202
357
|
_transitionRender = (newElement: ReactNode) => {
|
|
203
358
|
startTransition(() => {
|
|
204
|
-
|
|
359
|
+
setRendered({ element: newElement, publish: null });
|
|
205
360
|
});
|
|
206
361
|
};
|
|
207
362
|
|
|
208
363
|
// Full navigation transition.
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
364
|
+
//
|
|
365
|
+
// The async work runs OUTSIDE any transition scope, and each state update is
|
|
366
|
+
// its own synchronous `startTransition`. Two React behaviours force that
|
|
367
|
+
// shape; both were measured on React 19.2.7 (tests/navigation-transition-
|
|
368
|
+
// suspense.test.ts pins the observable half).
|
|
369
|
+
//
|
|
370
|
+
// 1. A transition scope does not survive an `await`. `startTransition`
|
|
371
|
+
// restores the previous scope in its `finally`, which for an async
|
|
372
|
+
// callback runs when that callback RETURNS — i.e. at its first `await`.
|
|
373
|
+
// So an update scheduled past an await inside `startTransition(async …)`
|
|
374
|
+
// is an ordinary urgent update:
|
|
375
|
+
//
|
|
376
|
+
// startTransition(() => setEl(x)) -> 'HOME' held
|
|
377
|
+
// startTransition(async () => { await p; setEl(x) }) -> fallback shown
|
|
378
|
+
//
|
|
379
|
+
// Left uncorrected, every navigation whose new tree suspends replaces the
|
|
380
|
+
// visible page with a Suspense fallback — the exact thing this component
|
|
381
|
+
// exists to prevent (TIM-1306). React documents the caveat under
|
|
382
|
+
// `startTransition`: updates after an await need their own transition.
|
|
383
|
+
//
|
|
384
|
+
// 2. Re-wrapping *inside* the async callback is not enough. Returning a
|
|
385
|
+
// thenable from `startTransition` hands it to `ReactSharedInternals.S`,
|
|
386
|
+
// which calls react-dom's `entangleAsyncAction`. That opens an action
|
|
387
|
+
// scope: `currentEntangledLane` collects EVERY transition update
|
|
388
|
+
// scheduled while the scope is open — including one from a nested,
|
|
389
|
+
// fully synchronous `startTransition` — and rendering that lane throws
|
|
390
|
+
// `currentEntangledActionThenable`, suspending until the action settles.
|
|
391
|
+
// Which is after the promise `navigateTransition` hands back, so a caller
|
|
392
|
+
// that awaits a navigation and then reads the DOM sees the departing
|
|
393
|
+
// page, and the TIM-1301 publish (a layout effect on the commit) is just
|
|
394
|
+
// as late. (Not `ReactSharedInternals.asyncTransitions` — that counter is
|
|
395
|
+
// write-only in 19.2.7.)
|
|
396
|
+
//
|
|
397
|
+
// No async action is created here, so every navigation commits as soon as
|
|
398
|
+
// React can render its destination — where TIM-1301 put it. That
|
|
399
|
+
// now holds for ALL of them. It used not to hold for `<Link>`, which wrapped
|
|
400
|
+
// `router.navigate()` in its own `useTransition`: that action scope
|
|
401
|
+
// entangled this component's updates just the same, so a Link navigation
|
|
402
|
+
// committed only once the payload had finished decoding, and with it
|
|
403
|
+
// `pushState`, the segment cache and `timber:navigation-end`. Link now calls
|
|
404
|
+
// `router.navigate()` outside any transition and tracks its own `isPending`
|
|
405
|
+
// with a plain `useState` (TIM-1307).
|
|
406
|
+
//
|
|
407
|
+
// Nothing here may reopen an action scope: no `startTransition` callback in
|
|
408
|
+
// this path may return a thenable, and no caller may invoke this from inside
|
|
409
|
+
// a React action scope. That is a rule about `startTransition`, not about
|
|
410
|
+
// `perform` — `perform` is async by contract and is awaited OUTSIDE any
|
|
411
|
+
// transition scope, which is exactly why it is safe.
|
|
412
|
+
//
|
|
413
|
+
// Standalone `startTransition` rather than `useTransition`'s is still
|
|
414
|
+
// deliberate — see the TIM-625 note above; each navigation needs an
|
|
415
|
+
// independent lane.
|
|
416
|
+
//
|
|
417
|
+
// `url` is unused: it named the pending state this component used to hold,
|
|
418
|
+
// and the router already publishes the same URL to its own pending store
|
|
419
|
+
// before calling here. It stays in the signature because the router's
|
|
420
|
+
// `RouterDeps.navigateTransition` adapter passes it and the argument reads
|
|
421
|
+
// at the call site.
|
|
422
|
+
_navigateTransition = (_url: string, perform: () => Promise<TransitionResult>) => {
|
|
423
|
+
// Increment the transition counter SYNCHRONOUSLY (before any await). Each
|
|
424
|
+
// call gets a unique transId; the counter is the same globalThis
|
|
425
|
+
// singleton, so a newer call always has a higher id.
|
|
222
426
|
const counter = getTransitionCounter();
|
|
223
|
-
const transId =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
setElement(element);
|
|
241
|
-
if (decodePromise) await decodePromise;
|
|
242
|
-
if (counter.id === transId) {
|
|
243
|
-
setPendingUrl(null);
|
|
244
|
-
resolve();
|
|
245
|
-
} else {
|
|
246
|
-
reject(new DOMException('Navigation superseded', 'AbortError'));
|
|
247
|
-
}
|
|
248
|
-
} catch (err) {
|
|
249
|
-
if (counter.id === transId) {
|
|
250
|
-
setPendingUrl(null);
|
|
251
|
-
}
|
|
252
|
-
reject(err);
|
|
253
|
-
}
|
|
427
|
+
const transId = bumpTransitionCounter();
|
|
428
|
+
|
|
429
|
+
const superseded = () => new DOMException('Navigation superseded', 'AbortError');
|
|
430
|
+
|
|
431
|
+
return (async () => {
|
|
432
|
+
const { element, decodePromise, commit } = await perform();
|
|
433
|
+
if (counter.id !== transId) {
|
|
434
|
+
decodePromise?.catch(() => {});
|
|
435
|
+
throw superseded();
|
|
436
|
+
}
|
|
437
|
+
// Hand the tree over with its commit attached. The effect above runs
|
|
438
|
+
// it if and when React commits this tree, so a navigation that is
|
|
439
|
+
// superseded while its payload streams publishes nothing (TIM-1301).
|
|
440
|
+
//
|
|
441
|
+
// This is THE update that must not hide the departing page (TIM-1306).
|
|
442
|
+
startTransition(() => {
|
|
443
|
+
setRendered({ element, publish: commit });
|
|
254
444
|
});
|
|
255
|
-
|
|
445
|
+
// React may commit the tree before this settles — that is the point:
|
|
446
|
+
// the destination reveals as React is able to render it
|
|
447
|
+
// rather than waiting for the whole Flight stream. The await is here so
|
|
448
|
+
// the promise this function hands back still means "the payload is
|
|
449
|
+
// decoded", which is what the router's scroll restoration, the
|
|
450
|
+
// Navigation API deferred and `<Link>`'s `isPending` are timed against.
|
|
451
|
+
//
|
|
452
|
+
// ...unless this navigation loses first, in which case it stops waiting
|
|
453
|
+
// on a stream that is no longer its business. See
|
|
454
|
+
// `settleOnDecodeOrSupersession`.
|
|
455
|
+
if (decodePromise) await settleOnDecodeOrSupersession(decodePromise, counter, transId);
|
|
456
|
+
if (counter.id !== transId) throw superseded();
|
|
457
|
+
})();
|
|
256
458
|
};
|
|
257
459
|
|
|
258
460
|
// ─── Hard navigation guard ─────────────────────────────────
|
|
@@ -261,10 +463,10 @@ export function NavigationRoot({
|
|
|
261
463
|
// page teardown. This avoids "Rendered more hooks" crashes in
|
|
262
464
|
// CHILD components whose hook counts may shift during teardown.
|
|
263
465
|
//
|
|
264
|
-
// CRITICAL: This throw MUST come AFTER all hooks (the
|
|
265
|
-
//
|
|
266
|
-
// every render. If we threw before hooks, React would see
|
|
267
|
-
// on the re-render vs
|
|
466
|
+
// CRITICAL: This throw MUST come AFTER all hooks (the useState,
|
|
467
|
+
// useRef and useLayoutEffect above). React requires the same hooks
|
|
468
|
+
// to run on every render. If we threw before hooks, React would see
|
|
469
|
+
// 0 hooks on the re-render vs 3 on the initial render — triggering
|
|
268
470
|
// the exact "Rendered more hooks" error we're trying to prevent.
|
|
269
471
|
//
|
|
270
472
|
// By placing it after hooks but before the return, all hooks
|
|
@@ -275,14 +477,23 @@ export function NavigationRoot({
|
|
|
275
477
|
throw unresolvedThenable;
|
|
276
478
|
}
|
|
277
479
|
|
|
278
|
-
// Inject TopLoader alongside the element tree
|
|
279
|
-
//
|
|
280
|
-
//
|
|
480
|
+
// Inject TopLoader alongside the element tree. It subscribes to the router's
|
|
481
|
+
// pending store itself, so it takes no props from here beyond its config,
|
|
482
|
+
// and only it re-renders when a navigation starts or ends. Rendered only
|
|
483
|
+
// when not explicitly disabled via config.
|
|
484
|
+
//
|
|
485
|
+
// This Fragment is a FORK — two children — so it advances React's tree
|
|
486
|
+
// context and shifts every `useId` below it. `server/ssr-wrappers.tsx`
|
|
487
|
+
// mirrors it for exactly that reason; a single-child wrapper would not need
|
|
488
|
+
// a mirror. See tests/ssr-wrapper-parity.test.ts.
|
|
281
489
|
const showTopLoader = topLoaderConfig?.enabled !== false;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
490
|
+
if (!showTopLoader) return rendered.element;
|
|
491
|
+
return createElement(
|
|
492
|
+
Fragment,
|
|
493
|
+
null,
|
|
494
|
+
createElement(TopLoader, { config: topLoaderConfig }),
|
|
495
|
+
rendered.element
|
|
496
|
+
);
|
|
286
497
|
}
|
|
287
498
|
|
|
288
499
|
// ─── Public API ──────────────────────────────────────────────────
|
|
@@ -301,27 +512,43 @@ export function transitionRender(element: ReactNode): void {
|
|
|
301
512
|
}
|
|
302
513
|
|
|
303
514
|
/**
|
|
304
|
-
* Run a full navigation
|
|
515
|
+
* Run a full navigation, handing its tree to React in a transition.
|
|
516
|
+
*
|
|
517
|
+
* The `perform` callback fetches the RSC payload, updates router state, and
|
|
518
|
+
* returns the wrapped React element. `perform` is async by contract and runs
|
|
519
|
+
* OUTSIDE any transition scope — only the `setRendered` that hands its result
|
|
520
|
+
* over is wrapped, in a synchronous `startTransition`.
|
|
521
|
+
*
|
|
522
|
+
* Do not call this from inside a React action scope, and never let a
|
|
523
|
+
* `startTransition` callback in this path return a thenable: either opens an
|
|
524
|
+
* action scope that entangles the update and defers the commit until the
|
|
525
|
+
* action settles (TIM-1306, TIM-1307).
|
|
305
526
|
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
527
|
+
* Returns a Promise that resolves when the async work completes — the payload
|
|
528
|
+
* is fetched, decoded, and handed to React. It does **not** wait for React to
|
|
529
|
+
* commit the tree, so the state that publishes on that commit (address bar,
|
|
530
|
+
* segment cache, history entry, pathname) may still be a beat behind when it
|
|
531
|
+
* resolves. Anything that needs the destination to be current should listen
|
|
532
|
+
* for `timber:navigation-end`, which is dispatched by the publish itself.
|
|
310
533
|
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
534
|
+
* Awaiting the publish here was tried and rejected: it makes the promise
|
|
535
|
+
* depend on a React commit, which never arrives if the root unmounts
|
|
536
|
+
* mid-navigation, and deadlocks any caller that awaits a navigation inside
|
|
537
|
+
* `act()`.
|
|
313
538
|
*
|
|
314
539
|
* Used for: navigate(), refresh(), popstate with fetch.
|
|
315
540
|
*/
|
|
316
541
|
export function navigateTransition(
|
|
317
|
-
|
|
318
|
-
perform: () => Promise<
|
|
542
|
+
url: string,
|
|
543
|
+
perform: () => Promise<TransitionResult>
|
|
319
544
|
): Promise<void> {
|
|
320
545
|
if (_navigateTransition) {
|
|
321
|
-
return _navigateTransition(
|
|
546
|
+
return _navigateTransition(url, perform);
|
|
322
547
|
}
|
|
323
|
-
// Fallback: no NavigationRoot mounted (shouldn't happen in production)
|
|
324
|
-
|
|
548
|
+
// Fallback: no NavigationRoot mounted (shouldn't happen in production).
|
|
549
|
+
// Nothing can supersede a transition that does not exist, so the commit
|
|
550
|
+
// runs unconditionally.
|
|
551
|
+
return perform().then((result) => result.commit());
|
|
325
552
|
}
|
|
326
553
|
|
|
327
554
|
/**
|
|
@@ -345,11 +572,9 @@ export function installDeferredNavigation(createAndMount: (initial: ReactNode) =
|
|
|
345
572
|
_transitionRender = (element: ReactNode) => {
|
|
346
573
|
mountOnce(element);
|
|
347
574
|
};
|
|
348
|
-
_navigateTransition = async (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
) => {
|
|
352
|
-
const { element } = await perform();
|
|
575
|
+
_navigateTransition = async (_url: string, perform: () => Promise<TransitionResult>) => {
|
|
576
|
+
const { element, commit } = await perform();
|
|
577
|
+
commit();
|
|
353
578
|
mountOnce(element);
|
|
354
579
|
};
|
|
355
580
|
}
|