@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71
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/LICENSE +8 -0
- package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-BJARkOcu.js} +1 -1
- package/dist/_chunks/als-registry-BJARkOcu.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-CGuYoRHU.js +199 -0
- package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
- package/dist/_chunks/define-Dz1bqwaS.js +106 -0
- package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
- package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
- package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
- package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
- package/dist/_chunks/format-Rn922VH2.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
- package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
- package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
- package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
- package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
- package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
- package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
- package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
- package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
- package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
- package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
- package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
- package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
- package/dist/adapters/cloudflare-dev.d.ts +109 -0
- package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
- package/dist/adapters/cloudflare-dev.js +73 -0
- package/dist/adapters/cloudflare-dev.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +148 -12
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +135 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +17 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -13
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/cache-api.d.ts +24 -0
- package/dist/cache/cache-api.d.ts.map +1 -0
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/handler-store.d.ts +31 -0
- package/dist/cache/handler-store.d.ts.map +1 -0
- package/dist/cache/index.d.ts +7 -5
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +111 -73
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/singleflight.d.ts +18 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +12 -5
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/error-reconstituter.d.ts +54 -0
- package/dist/client/error-reconstituter.d.ts.map +1 -0
- package/dist/client/form.d.ts +2 -2
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/history.d.ts +19 -4
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +6 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +537 -166
- package/dist/client/index.js.map +1 -1
- package/dist/client/link-pending-store.d.ts +78 -0
- package/dist/client/link-pending-store.d.ts.map +1 -0
- package/dist/client/link.d.ts +90 -32
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/nav-link-store.d.ts +36 -0
- package/dist/client/nav-link-store.d.ts.map +1 -0
- package/dist/client/navigation-api-types.d.ts +90 -0
- package/dist/client/navigation-api-types.d.ts.map +1 -0
- package/dist/client/navigation-api.d.ts +115 -0
- package/dist/client/navigation-api.d.ts.map +1 -0
- package/dist/client/navigation-context.d.ts +13 -2
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
- package/dist/client/navigation-root.d.ts.map +1 -0
- package/dist/client/nuqs-adapter.d.ts.map +1 -1
- package/dist/client/router.d.ts +70 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +38 -3
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-merger.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +63 -0
- package/dist/client/segment-outlet.d.ts.map +1 -0
- package/dist/client/ssr-data.d.ts +13 -4
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +3 -3
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +6 -4
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/codec.d.ts +23 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +2 -0
- package/dist/cookies/define-cookie.d.ts +35 -14
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -0
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +3 -84
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/index.d.ts +154 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12092 -11916
- package/dist/index.js.map +1 -1
- package/dist/plugins/adapter-build.d.ts +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +2 -2
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +3 -3
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts +32 -0
- package/dist/plugins/client-chunks.d.ts.map +1 -0
- package/dist/plugins/content.d.ts +1 -1
- package/dist/plugins/content.d.ts.map +1 -1
- package/dist/plugins/dev-browser-logs.d.ts +84 -0
- package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
- package/dist/plugins/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/dev-logs.d.ts +1 -1
- package/dist/plugins/dev-logs.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +19 -5
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +1 -1
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts +6 -5
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts +2 -2
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/index.d.ts +2 -0
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +3 -2
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +46 -0
- package/dist/routing/segment-classify.d.ts.map +1 -0
- package/dist/routing/status-file-lint.d.ts +2 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/routing/types.d.ts +16 -4
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +12 -0
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +76 -0
- package/dist/schema-bridge.d.ts.map +1 -0
- package/dist/search-params/define.d.ts +139 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -6
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +4 -474
- package/dist/search-params/registry.d.ts +1 -1
- package/dist/search-params/wrappers.d.ts +53 -0
- package/dist/search-params/wrappers.d.ts.map +1 -0
- package/dist/segment-params/define.d.ts +78 -0
- package/dist/segment-params/define.d.ts.map +1 -0
- package/dist/segment-params/index.d.ts +7 -0
- package/dist/segment-params/index.d.ts.map +1 -0
- package/dist/segment-params/index.js +4 -0
- package/dist/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-client.d.ts +12 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/action-encryption.d.ts +76 -0
- package/dist/server/action-encryption.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +3 -6
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +32 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/debug.d.ts +1 -1
- package/dist/server/default-logger.d.ts +22 -0
- package/dist/server/default-logger.d.ts.map +1 -0
- package/dist/server/deny-page-resolver.d.ts +52 -0
- package/dist/server/deny-page-resolver.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/dev-warnings.d.ts +0 -14
- package/dist/server/dev-warnings.d.ts.map +1 -1
- package/dist/server/early-hints.d.ts +13 -5
- package/dist/server/early-hints.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +7 -1
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +4 -3
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +66 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +42 -0
- package/dist/server/flight-scripts.d.ts.map +1 -0
- package/dist/server/flush.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +29 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +51 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2176 -1663
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +25 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +19 -4
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +113 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/page-deny-boundary.d.ts +31 -0
- package/dist/server/page-deny-boundary.d.ts.map +1 -0
- package/dist/server/pipeline-interception.d.ts +1 -1
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline-metadata.d.ts +6 -0
- package/dist/server/pipeline-metadata.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +32 -10
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +30 -3
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts +51 -0
- package/dist/server/render-timeout.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +76 -37
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +27 -1
- 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 +9 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts +2 -2
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +48 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +8 -3
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts +46 -0
- package/dist/server/safe-load.d.ts.map +1 -0
- package/dist/server/sitemap-generator.d.ts +129 -0
- package/dist/server/sitemap-generator.d.ts.map +1 -0
- package/dist/server/sitemap-handler.d.ts +22 -0
- package/dist/server/sitemap-handler.d.ts.map +1 -0
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +22 -0
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +39 -21
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/ssr-wrappers.d.ts +50 -0
- package/dist/server/ssr-wrappers.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +1 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +36 -0
- package/dist/server/stream-utils.d.ts.map +1 -0
- package/dist/server/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +22 -19
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -4
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/version-skew.d.ts +61 -0
- package/dist/server/version-skew.d.ts.map +1 -0
- package/dist/server/waituntil-bridge.d.ts.map +1 -1
- package/dist/shared/merge-search-params.d.ts +22 -0
- package/dist/shared/merge-search-params.d.ts.map +1 -0
- package/dist/shims/font-google.d.ts +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/font-google.js +42 -0
- package/dist/shims/font-google.js.map +1 -0
- package/dist/shims/font-local.d.ts +26 -0
- package/dist/shims/font-local.d.ts.map +1 -0
- package/dist/shims/font-local.js +20 -0
- package/dist/shims/font-local.js.map +1 -0
- package/dist/shims/navigation-client.d.ts +1 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/utils/directive-parser.d.ts +5 -2
- package/dist/utils/directive-parser.d.ts.map +1 -1
- package/dist/utils/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/package.json +37 -17
- package/src/adapters/cloudflare-dev.ts +177 -0
- package/src/adapters/cloudflare.ts +342 -28
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/adapters/wrangler.d.ts +7 -0
- package/src/cache/cache-api.ts +38 -0
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/handler-store.ts +68 -0
- package/src/cache/index.ts +9 -5
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +40 -29
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +314 -142
- package/src/client/error-boundary.tsx +48 -16
- package/src/client/error-reconstituter.tsx +65 -0
- package/src/client/form.tsx +2 -2
- package/src/client/history.ts +26 -4
- package/src/client/index.ts +13 -4
- package/src/client/link-pending-store.ts +136 -0
- package/src/client/link.tsx +346 -105
- package/src/client/nav-link-store.ts +47 -0
- package/src/client/navigation-api-types.ts +112 -0
- package/src/client/navigation-api.ts +332 -0
- package/src/client/navigation-context.ts +27 -6
- package/src/client/navigation-root.tsx +346 -0
- package/src/client/nuqs-adapter.tsx +16 -3
- package/src/client/router.ts +302 -77
- package/src/client/rsc-fetch.ts +93 -5
- package/src/client/segment-cache.ts +1 -1
- package/src/client/segment-context.ts +6 -1
- package/src/client/segment-merger.ts +2 -8
- package/src/client/segment-outlet.tsx +86 -0
- package/src/client/ssr-data.ts +13 -5
- package/src/client/stale-reload.ts +73 -6
- package/src/client/top-loader.tsx +22 -13
- package/src/client/use-navigation-pending.ts +1 -1
- package/src/client/use-params.ts +7 -5
- package/src/client/use-query-states.ts +2 -2
- package/src/codec.ts +34 -0
- package/src/cookies/define-cookie.ts +72 -21
- package/src/cookies/index.ts +7 -0
- package/src/fonts/css.ts +2 -1
- package/src/index.ts +328 -92
- package/src/plugins/adapter-build.ts +8 -2
- package/src/plugins/build-manifest.ts +13 -2
- package/src/plugins/build-report.ts +3 -3
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-browser-logs.ts +288 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-logs.ts +1 -1
- package/src/plugins/dev-server.ts +55 -9
- package/src/plugins/entries.ts +70 -9
- package/src/plugins/fonts.ts +167 -61
- package/src/plugins/mdx.ts +1 -1
- package/src/plugins/routing.ts +57 -17
- package/src/plugins/server-action-exports.ts +1 -1
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +76 -33
- package/src/plugins/static-build.ts +10 -6
- package/src/routing/codegen.ts +165 -105
- package/src/routing/index.ts +2 -0
- package/src/routing/scanner.ts +93 -23
- package/src/routing/segment-classify.ts +89 -0
- package/src/routing/status-file-lint.ts +3 -2
- package/src/routing/types.ts +17 -4
- package/src/rsc-runtime/rsc.ts +2 -0
- package/src/rsc-runtime/ssr.ts +50 -0
- package/src/rsc-runtime/vendor-types.d.ts +7 -0
- package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
- package/src/search-params/define.ts +482 -0
- package/src/search-params/index.ts +13 -19
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/segment-params/define.ts +279 -0
- package/src/segment-params/index.ts +28 -0
- package/src/server/access-gate.tsx +70 -29
- package/src/server/action-client.ts +28 -3
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +20 -3
- package/src/server/actions.ts +10 -9
- package/src/server/als-registry.ts +32 -4
- package/src/server/build-manifest.ts +10 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +1 -1
- package/src/server/default-logger.ts +99 -0
- package/src/server/deny-page-resolver.ts +154 -0
- package/src/server/deny-renderer.ts +24 -38
- package/src/server/dev-warnings.ts +2 -28
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +74 -22
- package/src/server/fallback-error.ts +31 -15
- package/src/server/flight-injection-state.ts +113 -0
- package/src/server/flight-scripts.ts +62 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +277 -117
- package/src/server/index.ts +9 -5
- package/src/server/logger.ts +44 -36
- package/src/server/middleware-runner.ts +31 -4
- package/src/server/node-stream-transforms.ts +509 -0
- package/src/server/page-deny-boundary.tsx +56 -0
- package/src/server/pipeline-interception.ts +17 -16
- package/src/server/pipeline-metadata.ts +13 -0
- package/src/server/pipeline.ts +195 -51
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +240 -117
- package/src/server/route-element-builder.ts +284 -197
- package/src/server/route-handler.ts +24 -4
- package/src/server/route-matcher.ts +24 -20
- package/src/server/rsc-entry/api-handler.ts +15 -16
- package/src/server/rsc-entry/error-renderer.ts +300 -89
- package/src/server/rsc-entry/helpers.ts +134 -5
- package/src/server/rsc-entry/index.ts +202 -113
- package/src/server/rsc-entry/rsc-payload.ts +100 -21
- package/src/server/rsc-entry/rsc-stream.ts +74 -18
- package/src/server/rsc-entry/ssr-bridge.ts +14 -5
- package/src/server/rsc-entry/ssr-renderer.ts +173 -40
- package/src/server/safe-load.ts +60 -0
- package/src/server/sitemap-generator.ts +338 -0
- package/src/server/sitemap-handler.ts +126 -0
- package/src/server/slot-resolver.ts +243 -228
- package/src/server/ssr-entry.ts +211 -32
- package/src/server/ssr-render.ts +289 -67
- package/src/server/ssr-wrappers.tsx +139 -0
- package/src/server/status-code-resolver.ts +1 -1
- package/src/server/stream-utils.ts +213 -0
- package/src/server/tracing.ts +37 -3
- package/src/server/tree-builder.ts +92 -58
- package/src/server/types.ts +3 -6
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +55 -0
- package/src/shims/font-google.ts +1 -1
- package/src/shims/font-local.ts +34 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +2 -1
- package/src/utils/directive-parser.ts +5 -2
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
- package/dist/_chunks/format-DviM89f0.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
- package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
- package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
- package/dist/_chunks/tracing-Cwn7697K.js.map +0 -1
- package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
- package/dist/cache/register-cached-function.d.ts +0 -17
- package/dist/cache/register-cached-function.d.ts.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/client/link-status-provider.d.ts +0 -11
- package/dist/client/link-status-provider.d.ts.map +0 -1
- package/dist/client/transition-root.d.ts.map +0 -1
- package/dist/cookies/index.js.map +0 -1
- package/dist/plugins/cache-transform.d.ts +0 -36
- package/dist/plugins/cache-transform.d.ts.map +0 -1
- package/dist/plugins/dynamic-transform.d.ts +0 -72
- package/dist/plugins/dynamic-transform.d.ts.map +0 -1
- package/dist/search-params/analyze.d.ts +0 -54
- package/dist/search-params/analyze.d.ts.map +0 -1
- package/dist/search-params/builtin-codecs.d.ts +0 -105
- package/dist/search-params/builtin-codecs.d.ts.map +0 -1
- package/dist/search-params/codecs.d.ts +0 -53
- package/dist/search-params/codecs.d.ts.map +0 -1
- package/dist/search-params/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/search-params/index.js.map +0 -1
- package/dist/server/prerender.d.ts +0 -77
- package/dist/server/prerender.d.ts.map +0 -1
- package/dist/server/response-cache.d.ts +0 -53
- package/dist/server/response-cache.d.ts.map +0 -1
- package/src/cache/register-cached-function.ts +0 -99
- package/src/client/link-status-provider.tsx +0 -30
- package/src/client/transition-root.tsx +0 -160
- package/src/plugins/cache-transform.ts +0 -199
- package/src/plugins/dynamic-transform.ts +0 -161
- package/src/search-params/analyze.ts +0 -192
- package/src/search-params/builtin-codecs.ts +0 -228
- package/src/search-params/create.ts +0 -321
- package/src/server/prerender.ts +0 -139
- package/src/server/response-cache.ts +0 -277
|
@@ -28,14 +28,20 @@ import {
|
|
|
28
28
|
createFromFetch,
|
|
29
29
|
setServerCallback,
|
|
30
30
|
encodeReply,
|
|
31
|
-
} from '
|
|
31
|
+
} from '../rsc-runtime/browser.js';
|
|
32
32
|
// Shared-state modules MUST be imported from @timber-js/app/client (the public
|
|
33
33
|
// barrel) so they resolve to the same module instances as user code. In Vite dev,
|
|
34
34
|
// user code imports @timber-js/app/client from dist/ via package.json exports.
|
|
35
35
|
// If we used relative imports (./router-ref.js), Vite would load separate src/
|
|
36
36
|
// copies with separate module-level state — e.g., globalRouter set here but
|
|
37
37
|
// read as null from the dist/ copy used by useRouter().
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
createRouter,
|
|
40
|
+
setGlobalRouter,
|
|
41
|
+
getRouter,
|
|
42
|
+
getRouterOrNull,
|
|
43
|
+
setCurrentParams,
|
|
44
|
+
} from '@timber-js/app/client';
|
|
39
45
|
import type { RouterDeps, RouterInstance } from '@timber-js/app/client';
|
|
40
46
|
|
|
41
47
|
// Internal-only modules (no shared mutable state with user code) use relative
|
|
@@ -47,12 +53,35 @@ import {
|
|
|
47
53
|
NavigationProvider,
|
|
48
54
|
getNavigationState,
|
|
49
55
|
setNavigationState,
|
|
56
|
+
type NavigationState,
|
|
50
57
|
} from './navigation-context.js';
|
|
51
58
|
import { setupServerLogReplay, setupClientErrorForwarding } from './browser-dev.js';
|
|
52
59
|
// browser-links.ts removed — Link components own their click/hover handlers directly.
|
|
53
60
|
// See LOCAL-340.
|
|
54
|
-
import {
|
|
55
|
-
|
|
61
|
+
import {
|
|
62
|
+
NavigationRoot,
|
|
63
|
+
transitionRender,
|
|
64
|
+
navigateTransition,
|
|
65
|
+
installDeferredNavigation,
|
|
66
|
+
setHardNavigating,
|
|
67
|
+
} from './navigation-root.js';
|
|
68
|
+
import {
|
|
69
|
+
isStaleClientReference,
|
|
70
|
+
isChunkLoadError,
|
|
71
|
+
triggerStaleReload,
|
|
72
|
+
clearStaleReloadFlag,
|
|
73
|
+
} from './stale-reload.js';
|
|
74
|
+
import {
|
|
75
|
+
setClientDeploymentId,
|
|
76
|
+
getClientDeploymentId,
|
|
77
|
+
DEPLOYMENT_ID_HEADER,
|
|
78
|
+
RELOAD_HEADER,
|
|
79
|
+
} from './rsc-fetch.js';
|
|
80
|
+
import {
|
|
81
|
+
hasNavigationApi,
|
|
82
|
+
setupNavigationApi,
|
|
83
|
+
type NavigationApiController,
|
|
84
|
+
} from './navigation-api.js';
|
|
56
85
|
|
|
57
86
|
// ─── Server Action Dispatch ──────────────────────────────────────
|
|
58
87
|
|
|
@@ -81,14 +110,28 @@ setServerCallback(async (id: string, args: unknown[]) => {
|
|
|
81
110
|
let hasRedirect = false;
|
|
82
111
|
let headElementsJson: string | null = null;
|
|
83
112
|
|
|
113
|
+
// Build action request headers. Include deployment ID for version
|
|
114
|
+
// skew detection (TIM-446) — the server rejects stale actions gracefully.
|
|
115
|
+
const actionHeaders: Record<string, string> = {
|
|
116
|
+
'Accept': 'text/x-component',
|
|
117
|
+
'x-rsc-action': id,
|
|
118
|
+
};
|
|
119
|
+
const actionDeploymentId = getClientDeploymentId();
|
|
120
|
+
if (actionDeploymentId) {
|
|
121
|
+
actionHeaders[DEPLOYMENT_ID_HEADER] = actionDeploymentId;
|
|
122
|
+
}
|
|
123
|
+
|
|
84
124
|
const response = fetch(window.location.href, {
|
|
85
125
|
method: 'POST',
|
|
86
|
-
headers:
|
|
87
|
-
'Accept': 'text/x-component',
|
|
88
|
-
'x-rsc-action': id,
|
|
89
|
-
},
|
|
126
|
+
headers: actionHeaders,
|
|
90
127
|
body,
|
|
91
128
|
}).then((res) => {
|
|
129
|
+
// Version skew detection (TIM-446): if the server signals a reload,
|
|
130
|
+
// trigger a full page load to pick up the new deployment.
|
|
131
|
+
if (res.headers.get(RELOAD_HEADER) === '1') {
|
|
132
|
+
window.location.reload();
|
|
133
|
+
throw new Error('Version skew detected — reloading page');
|
|
134
|
+
}
|
|
92
135
|
hasRevalidation = res.headers.get('X-Timber-Revalidation') === '1';
|
|
93
136
|
hasRedirect = res.headers.get('X-Timber-Redirect') != null;
|
|
94
137
|
headElementsJson = res.headers.get('X-Timber-Head');
|
|
@@ -115,7 +158,10 @@ setServerCallback(async (id: string, args: unknown[]) => {
|
|
|
115
158
|
const router = getRouter();
|
|
116
159
|
void router.navigate(wrapper._redirect);
|
|
117
160
|
} catch {
|
|
118
|
-
// Router not yet initialized — fall back to full navigation
|
|
161
|
+
// Router not yet initialized — fall back to full navigation.
|
|
162
|
+
// Set hard-navigating flag to prevent Navigation API interception
|
|
163
|
+
// and React from rendering during page teardown. See TIM-626.
|
|
164
|
+
setHardNavigating(true);
|
|
119
165
|
window.location.href = wrapper._redirect;
|
|
120
166
|
}
|
|
121
167
|
return undefined;
|
|
@@ -155,7 +201,17 @@ setServerCallback(async (id: string, args: unknown[]) => {
|
|
|
155
201
|
* Hydrates the server-rendered HTML with React, then initializes
|
|
156
202
|
* client-side navigation for SPA transitions.
|
|
157
203
|
*/
|
|
158
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* Read the current scroll position.
|
|
206
|
+
*
|
|
207
|
+
* Checks window scroll first, then explicit `data-timber-scroll-restoration`
|
|
208
|
+
* containers. With segment tree merging, shared layouts are reconciled in
|
|
209
|
+
* place via `cloneElement` — React preserves their DOM and scroll state
|
|
210
|
+
* naturally. We don't need to auto-detect overflow containers; only
|
|
211
|
+
* explicitly marked containers are tracked.
|
|
212
|
+
*
|
|
213
|
+
* See design/19-client-navigation.md §"Overflow Scroll Containers".
|
|
214
|
+
*/
|
|
159
215
|
function getScrollY(): number {
|
|
160
216
|
if (window.scrollY || document.documentElement.scrollTop || document.body.scrollTop) {
|
|
161
217
|
return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
|
|
@@ -163,74 +219,24 @@ function getScrollY(): number {
|
|
|
163
219
|
for (const el of document.querySelectorAll('[data-timber-scroll-restoration]')) {
|
|
164
220
|
if ((el as HTMLElement).scrollTop > 0) return (el as HTMLElement).scrollTop;
|
|
165
221
|
}
|
|
166
|
-
// Auto-detect: if window isn't scrolled, check for overflow containers.
|
|
167
|
-
// Common pattern: layouts use a scrollable div (overflow-y: auto/scroll)
|
|
168
|
-
// inside a fixed-height parent (h-screen). In this case window.scrollY is
|
|
169
|
-
// always 0 and the real scroll position lives on the overflow container.
|
|
170
|
-
const container = findOverflowContainer();
|
|
171
|
-
if (container && container.scrollTop > 0) return container.scrollTop;
|
|
172
222
|
return 0;
|
|
173
223
|
}
|
|
174
224
|
|
|
175
|
-
/**
|
|
176
|
-
* Find the primary overflow scroll container in the document.
|
|
177
|
-
*
|
|
178
|
-
* Walks direct children of body and their immediate children looking for
|
|
179
|
-
* an element with overflow-y: auto|scroll that is actually scrollable
|
|
180
|
-
* (scrollHeight > clientHeight). Returns the first match, or null.
|
|
181
|
-
*
|
|
182
|
-
* This heuristic covers the common layout patterns:
|
|
183
|
-
* <body> → <root-layout> → <div class="overflow-y-auto">
|
|
184
|
-
* <body> → <root-layout> → <main> → <nested-layout overflow-y-auto>
|
|
185
|
-
*
|
|
186
|
-
* We limit depth to 3 to avoid expensive full-tree traversals while still
|
|
187
|
-
* reaching nested layout scroll containers (e.g., parallel route layouts
|
|
188
|
-
* inside a root layout's <main> element).
|
|
189
|
-
*
|
|
190
|
-
* DIVERGENCE FROM NEXT.JS: Next.js's ScrollAndFocusHandler scrolls only
|
|
191
|
-
* document.documentElement.scrollTop — it does NOT handle overflow containers.
|
|
192
|
-
* Layouts using h-screen + overflow-y-auto have the same scroll bug in Next.js.
|
|
193
|
-
* This heuristic is a deliberate improvement. The tradeoff is fragility: depth-3
|
|
194
|
-
* traversal may miss deeply nested containers or match the wrong element.
|
|
195
|
-
* See design/19-client-navigation.md §"Overflow Scroll Containers".
|
|
196
|
-
*/
|
|
197
|
-
function findOverflowContainer(): HTMLElement | null {
|
|
198
|
-
const candidates: HTMLElement[] = [];
|
|
199
|
-
// Check body's descendants up to depth 3. Depth 3 covers the common case:
|
|
200
|
-
// <body> → <root-layout-div> → <main> → <overflow-container>
|
|
201
|
-
// React context providers (SegmentProvider, NavigationProvider) don't add
|
|
202
|
-
// DOM elements, so depth 3 from body reaches nested layout scroll containers.
|
|
203
|
-
for (const child of document.body.children) {
|
|
204
|
-
candidates.push(child as HTMLElement);
|
|
205
|
-
for (const grandchild of child.children) {
|
|
206
|
-
candidates.push(grandchild as HTMLElement);
|
|
207
|
-
for (const greatGrandchild of grandchild.children) {
|
|
208
|
-
candidates.push(greatGrandchild as HTMLElement);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
for (const el of candidates) {
|
|
213
|
-
if (!(el instanceof HTMLElement)) continue;
|
|
214
|
-
const style = getComputedStyle(el);
|
|
215
|
-
const overflowY = style.overflowY;
|
|
216
|
-
if (
|
|
217
|
-
(overflowY === 'auto' || overflowY === 'scroll') &&
|
|
218
|
-
el.scrollHeight > el.clientHeight
|
|
219
|
-
) {
|
|
220
|
-
return el;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
225
|
function bootstrap(runtimeConfig: typeof config): void {
|
|
227
226
|
const _config = runtimeConfig;
|
|
228
227
|
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
// Initialize deployment ID for version skew detection (TIM-446).
|
|
229
|
+
// In dev mode this is null — skew checks are skipped.
|
|
230
|
+
const deploymentId = (_config as Record<string, unknown>).deploymentId as string | null;
|
|
231
|
+
if (deploymentId) {
|
|
232
|
+
setClientDeploymentId(deploymentId);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Take manual control of scroll restoration. Even though segment tree
|
|
236
|
+
// merging preserves shared layout DOM via cloneElement (so React doesn't
|
|
237
|
+
// reset scroll on those elements), the root-level reactRoot.render() with
|
|
238
|
+
// a new element tree can still cause scroll resets on the document during
|
|
239
|
+
// reconciliation. Manual control ensures consistent behavior.
|
|
234
240
|
window.history.scrollRestoration = 'manual';
|
|
235
241
|
|
|
236
242
|
// Hydrate the React tree from the RSC payload.
|
|
@@ -246,7 +252,13 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
246
252
|
// For subsequent navigations, it's fetched from the server.
|
|
247
253
|
type FlightSegment = [isBootstrap: 0] | [isData: 1, data: string];
|
|
248
254
|
|
|
249
|
-
|
|
255
|
+
// __timber_f is initialized in <head> via flightInitScript() (see
|
|
256
|
+
// flight-scripts.ts). If it doesn't exist, skip Flight decoding
|
|
257
|
+
// entirely and fall through to the createRoot branch.
|
|
258
|
+
// Do NOT defensively create it here: that would cause
|
|
259
|
+
// createFromReadableStream to be called on an empty stream, producing
|
|
260
|
+
// a "Connection closed" error on hydration. See TIM-552.
|
|
261
|
+
const timberChunks = (self as unknown as Record<string, FlightSegment[] | undefined>).__timber_f;
|
|
250
262
|
|
|
251
263
|
let _reactRoot: Root | null = null;
|
|
252
264
|
let initialElement: unknown = null;
|
|
@@ -254,6 +266,12 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
254
266
|
// Assigned inside initRouter() which is called in both branches.
|
|
255
267
|
let router!: RouterInstance;
|
|
256
268
|
|
|
269
|
+
// Navigation API controller — initialized when the API is available.
|
|
270
|
+
// Declared here (before the hydration if/else) because initRouter()
|
|
271
|
+
// is called from runPreHydration() inside both branches, and it
|
|
272
|
+
// assigns to this variable. Must be in scope before first use.
|
|
273
|
+
let navApiController: NavigationApiController | null = null;
|
|
274
|
+
|
|
257
275
|
if (timberChunks) {
|
|
258
276
|
const encoder = new TextEncoder();
|
|
259
277
|
|
|
@@ -318,6 +336,28 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
318
336
|
// Leaving the stream open is harmless: the page is being torn down.
|
|
319
337
|
function onDOMContentLoaded(): void {
|
|
320
338
|
if (isPageUnloading()) return;
|
|
339
|
+
|
|
340
|
+
// In dev mode, do NOT close the stream. React's RSC renderer
|
|
341
|
+
// includes debug owner/stack references ($1, $14, etc.) in the
|
|
342
|
+
// Flight payload that point to rows delivered through the debug
|
|
343
|
+
// channel, not the main Flight stream. The browser Flight client
|
|
344
|
+
// tracks these as pending chunks. Closing the stream with
|
|
345
|
+
// unresolved chunks triggers reportGlobalError("Connection closed")
|
|
346
|
+
// which kills the entire React tree.
|
|
347
|
+
//
|
|
348
|
+
// Leaving the stream open is harmless: React has already received
|
|
349
|
+
// all data rows and can hydrate fully. The pending debug chunks
|
|
350
|
+
// just remain unresolved (they're only used for React DevTools
|
|
351
|
+
// component stacks, not rendering).
|
|
352
|
+
//
|
|
353
|
+
// In production, debug rows are not emitted, so closing is safe.
|
|
354
|
+
if (process.env.NODE_ENV === 'development') {
|
|
355
|
+
// Mark as flushed so no more data is buffered, but don't close.
|
|
356
|
+
streamFlushed = true;
|
|
357
|
+
dataBuffer = undefined;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
321
361
|
if (streamWriter && !streamFlushed) {
|
|
322
362
|
streamWriter.close();
|
|
323
363
|
streamFlushed = true;
|
|
@@ -329,49 +369,58 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
329
369
|
if (document.readyState === 'loading') {
|
|
330
370
|
document.addEventListener('DOMContentLoaded', onDOMContentLoaded, false);
|
|
331
371
|
} else {
|
|
332
|
-
// DOM already parsed
|
|
333
|
-
//
|
|
334
|
-
|
|
372
|
+
// DOM already parsed. All inline RSC <script> tags have already
|
|
373
|
+
// executed and pushed their data into the buffer. The buffer was
|
|
374
|
+
// flushed into the stream during start() above.
|
|
375
|
+
//
|
|
376
|
+
// Close via queueMicrotask rather than setTimeout. setTimeout
|
|
377
|
+
// defers to the next macrotask, which can race with React's
|
|
378
|
+
// Flight client read loop — if React finishes reading all queued
|
|
379
|
+
// chunks and issues a reader.read() that pends, the stream is
|
|
380
|
+
// NOT closed yet (setTimeout hasn't fired), so React sees an
|
|
381
|
+
// open stream and waits. Then setTimeout fires and closes it.
|
|
382
|
+
// This works in theory but some React Flight builds interpret
|
|
383
|
+
// a mid-read close as "Connection closed" rather than clean EOF.
|
|
384
|
+
// queueMicrotask fires at the end of the current microtask
|
|
385
|
+
// checkpoint — after start() and createFromReadableStream
|
|
386
|
+
// initialization but before any macrotask, giving React a
|
|
387
|
+
// consistent close signal. See TIM-524.
|
|
388
|
+
queueMicrotask(onDOMContentLoaded);
|
|
335
389
|
}
|
|
336
390
|
|
|
337
391
|
const element = createFromReadableStream(rscPayload);
|
|
338
392
|
initialElement = element;
|
|
339
393
|
|
|
340
|
-
// ──
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
setNavigationState({
|
|
363
|
-
params: {},
|
|
364
|
-
pathname: window.location.pathname,
|
|
365
|
-
});
|
|
366
|
-
}
|
|
394
|
+
// ── Pre-hydration bootstrap sequence ──────────────────────────────
|
|
395
|
+
//
|
|
396
|
+
// These steps MUST execute in this exact order before hydrateRoot():
|
|
397
|
+
//
|
|
398
|
+
// 1. initRouter() — creates the global router so useRouter()
|
|
399
|
+
// works during render (methods lazily resolve
|
|
400
|
+
// the router at invocation, not render time,
|
|
401
|
+
// but initRouter must still run first)
|
|
402
|
+
//
|
|
403
|
+
// 2. setCurrentParams() — populates module-level params snapshot so
|
|
404
|
+
// + setNavigationState() useSegmentParams() and usePathname()
|
|
405
|
+
// return correct values during hydration
|
|
406
|
+
//
|
|
407
|
+
// 3. hydrateRoot() — synchronously executes component render
|
|
408
|
+
// functions that depend on steps 1-2
|
|
409
|
+
//
|
|
410
|
+
// Implicit prerequisite: the __timber_f RSC stream (ReadableStream
|
|
411
|
+
// above) must be wired up before hydrateRoot, because React starts
|
|
412
|
+
// consuming it synchronously during hydration.
|
|
413
|
+
//
|
|
414
|
+
// See design/19-client-navigation.md §"NavigationContext"
|
|
415
|
+
runPreHydration(element);
|
|
367
416
|
|
|
368
417
|
// Hydrate on document — the root layout renders the full <html> tree,
|
|
369
418
|
// so React owns the entire document from the root.
|
|
370
419
|
// Wrap with NavigationProvider (for atomic useParams/usePathname),
|
|
371
|
-
// TimberNuqsAdapter (for nuqs context), and
|
|
420
|
+
// TimberNuqsAdapter (for nuqs context), and NavigationRoot (for
|
|
372
421
|
// transition-based rendering during client navigation).
|
|
373
422
|
//
|
|
374
|
-
//
|
|
423
|
+
// NavigationRoot holds the element in React state and updates via
|
|
375
424
|
// startTransition, so React keeps old UI visible while new Suspense
|
|
376
425
|
// boundaries resolve during navigation. See design/05-streaming.md.
|
|
377
426
|
const navState = getNavigationState();
|
|
@@ -381,7 +430,19 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
381
430
|
element as React.ReactNode
|
|
382
431
|
);
|
|
383
432
|
const wrapped = createElement(TimberNuqsAdapter, null, withNav);
|
|
384
|
-
const rootElement = createElement(
|
|
433
|
+
const rootElement = createElement(NavigationRoot, {
|
|
434
|
+
initial: wrapped,
|
|
435
|
+
topLoaderConfig: _config.topLoader,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
439
|
+
if (!getRouterOrNull()) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
'[timber] hydrateRoot called before initRouter() — bootstrap order violated'
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
385
446
|
_reactRoot = hydrateRoot(document, rootElement, {
|
|
386
447
|
// Suppress recoverable hydration errors from deny/error signals
|
|
387
448
|
// inside Suspense boundaries. The server already handled these
|
|
@@ -401,42 +462,64 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
401
462
|
},
|
|
402
463
|
});
|
|
403
464
|
} else {
|
|
404
|
-
// No RSC payload available
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
|
|
409
|
-
|
|
465
|
+
// No RSC payload available — create a non-hydrated root so client
|
|
466
|
+
// navigation can still render RSC payloads. The initial SSR HTML
|
|
467
|
+
// remains as-is; the first client navigation will replace it with
|
|
468
|
+
// a React-managed tree.
|
|
469
|
+
runPreHydration(null);
|
|
470
|
+
// Defer React root creation until first client navigation (TIM-600).
|
|
471
|
+
//
|
|
472
|
+
// We must NOT call createRoot(document).render() here — that would take
|
|
473
|
+
// React ownership of the entire document and blank the SSR HTML.
|
|
474
|
+
// Instead, installDeferredNavigation sets up one-shot callbacks so the
|
|
475
|
+
// first navigateTransition/transitionRender call creates the root on
|
|
476
|
+
// `document` with the navigated content. After that initial render,
|
|
477
|
+
// NavigationRoot's real startTransition-based callbacks take over.
|
|
478
|
+
//
|
|
479
|
+
// This also fixes TIM-580 (navigation from SSR-only pages) because the
|
|
480
|
+
// deferred callbacks ensure NavigationRoot is mounted before the first
|
|
481
|
+
// navigation completes.
|
|
482
|
+
installDeferredNavigation((initial) => {
|
|
483
|
+
const rootElement = createElement(NavigationRoot, {
|
|
484
|
+
initial,
|
|
485
|
+
topLoaderConfig: _config.topLoader,
|
|
486
|
+
});
|
|
487
|
+
_reactRoot = createRoot(document);
|
|
488
|
+
_reactRoot.render(rootElement);
|
|
489
|
+
});
|
|
410
490
|
}
|
|
411
491
|
|
|
412
492
|
// ── Router initialization (hoisted above hydrateRoot) ────────────────
|
|
413
493
|
// Extracted into a function so both the hydration and createRoot paths
|
|
414
494
|
// can call it. Must run before hydrateRoot so useRouter() works during
|
|
415
495
|
// the initial render. renderRoot uses transitionRender which is set
|
|
416
|
-
// by the
|
|
496
|
+
// by the NavigationRoot component during hydration.
|
|
417
497
|
function initRouter(): void {
|
|
498
|
+
// Feature-detect Navigation API. When available, the navigate event
|
|
499
|
+
// replaces popstate for back/forward and catches external navigations.
|
|
500
|
+
// See design/19-client-navigation.md §"Navigation API Integration"
|
|
501
|
+
const useNavApi = hasNavigationApi();
|
|
502
|
+
|
|
418
503
|
const deps: RouterDeps = {
|
|
419
504
|
fetch: (url, init) => window.fetch(url, init),
|
|
420
505
|
pushState: (data, unused, url) => window.history.pushState(data, unused, url),
|
|
421
506
|
replaceState: (data, unused, url) => window.history.replaceState(data, unused, url),
|
|
507
|
+
navigationApiActive: useNavApi,
|
|
422
508
|
scrollTo: (x, y) => {
|
|
509
|
+
// Scroll the document viewport.
|
|
423
510
|
window.scrollTo(x, y);
|
|
424
511
|
document.documentElement.scrollTop = y;
|
|
425
512
|
document.body.scrollTop = y;
|
|
426
|
-
//
|
|
513
|
+
// Scroll any element explicitly marked as a scroll container.
|
|
514
|
+
// With segment tree merging, shared layouts (sidebars, nav bars)
|
|
515
|
+
// are reconciled in place via cloneElement — React preserves their
|
|
516
|
+
// DOM and scroll state naturally. We no longer auto-detect overflow
|
|
517
|
+
// containers, which previously found the wrong element (e.g.,
|
|
518
|
+
// scrolling a sidebar instead of the main content area).
|
|
519
|
+
// Use `data-timber-scroll-restoration` to opt in specific containers.
|
|
427
520
|
for (const el of document.querySelectorAll('[data-timber-scroll-restoration]')) {
|
|
428
521
|
(el as HTMLElement).scrollTop = y;
|
|
429
522
|
}
|
|
430
|
-
// Auto-detect overflow containers for layouts that scroll inside
|
|
431
|
-
// a fixed-height wrapper (e.g., h-screen + overflow-y-auto).
|
|
432
|
-
// In these layouts, window.scrollY is always 0 and the real scroll
|
|
433
|
-
// lives on the overflow container. Without this, forward navigation
|
|
434
|
-
// between pages that share a layout with parallel route slots won't
|
|
435
|
-
// scroll to top — the router's window.scrollTo(0,0) is a no-op.
|
|
436
|
-
const container = findOverflowContainer();
|
|
437
|
-
if (container) {
|
|
438
|
-
container.scrollTop = y;
|
|
439
|
-
}
|
|
440
523
|
},
|
|
441
524
|
getCurrentUrl: () => window.location.pathname + window.location.search,
|
|
442
525
|
getScrollY,
|
|
@@ -464,15 +547,17 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
464
547
|
}
|
|
465
548
|
},
|
|
466
549
|
|
|
467
|
-
// Render decoded RSC tree via
|
|
550
|
+
// Render decoded RSC tree via NavigationRoot's state-based mechanism.
|
|
468
551
|
// Used for non-navigation renders (popstate cached replay, applyRevalidation).
|
|
469
552
|
// Wraps with NavigationProvider + TimberNuqsAdapter.
|
|
470
553
|
//
|
|
471
554
|
// For navigation renders (navigate, refresh, popstate-with-fetch),
|
|
472
555
|
// navigateTransition is used instead — it wraps the entire navigation
|
|
473
556
|
// in a React transition with useOptimistic for the pending URL.
|
|
474
|
-
|
|
475
|
-
|
|
557
|
+
//
|
|
558
|
+
// navState is passed explicitly by the router — no temporal coupling
|
|
559
|
+
// with getNavigationState().
|
|
560
|
+
renderRoot: (element: unknown, navState: NavigationState) => {
|
|
476
561
|
const withNav = createElement(
|
|
477
562
|
NavigationProvider,
|
|
478
563
|
{ value: navState },
|
|
@@ -488,13 +573,11 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
488
573
|
// commits (atomic with the new tree + params).
|
|
489
574
|
//
|
|
490
575
|
// The perform callback receives a wrapPayload function that wraps the
|
|
491
|
-
// decoded RSC payload with NavigationProvider + NuqsAdapter
|
|
492
|
-
//
|
|
493
|
-
// UPDATED navigation state (set by the router inside perform).
|
|
576
|
+
// decoded RSC payload with NavigationProvider + NuqsAdapter. navState
|
|
577
|
+
// is passed explicitly by the router — no getNavigationState() needed.
|
|
494
578
|
navigateTransition: (pendingUrl: string, perform) => {
|
|
495
579
|
return navigateTransition(pendingUrl, async () => {
|
|
496
|
-
const payload = await perform((rawPayload: unknown) => {
|
|
497
|
-
const navState = getNavigationState();
|
|
580
|
+
const payload = await perform((rawPayload: unknown, navState: NavigationState) => {
|
|
498
581
|
const withNav = createElement(
|
|
499
582
|
NavigationProvider,
|
|
500
583
|
{ value: navState },
|
|
@@ -522,6 +605,62 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
522
605
|
|
|
523
606
|
router = createRouter(deps);
|
|
524
607
|
setGlobalRouter(router);
|
|
608
|
+
|
|
609
|
+
// Set up Navigation API integration after router is created.
|
|
610
|
+
// The navigate event listener delegates to router.navigate and
|
|
611
|
+
// router.handlePopState for external navigations and traversals.
|
|
612
|
+
if (useNavApi) {
|
|
613
|
+
navApiController = setupNavigationApi({
|
|
614
|
+
onExternalNavigate: async (url, { replace, signal, scroll }) => {
|
|
615
|
+
// Navigation intercepted by the Navigation API. Covers both
|
|
616
|
+
// Link <a> clicks (user-initiated) and external navigations.
|
|
617
|
+
// The Navigation API handles the URL update via intercept(),
|
|
618
|
+
// so pass _skipHistory to avoid double pushState.
|
|
619
|
+
await router.navigate(url, {
|
|
620
|
+
replace,
|
|
621
|
+
scroll,
|
|
622
|
+
_signal: signal,
|
|
623
|
+
_skipHistory: true,
|
|
624
|
+
});
|
|
625
|
+
},
|
|
626
|
+
onTraverse: async (url, scrollY, signal) => {
|
|
627
|
+
// Back/forward — delegate to the router's popstate handler.
|
|
628
|
+
await router.handlePopState(url, scrollY, signal);
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Wire the router-navigating flag into RouterDeps.
|
|
633
|
+
// This must be done after setupNavigationApi returns the controller.
|
|
634
|
+
deps.setRouterNavigating = (v) => navApiController!.setRouterNavigating(v);
|
|
635
|
+
deps.saveNavigationEntryScroll = (y) => navApiController!.saveScrollPosition(y);
|
|
636
|
+
deps.completeRouterNavigation = () => navApiController!.completeRouterNavigation();
|
|
637
|
+
deps.navigationNavigate = (url, replace) => navApiController!.navigate(url, replace);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ── Pre-hydration sequence ──────────────────────────────────────────
|
|
642
|
+
// Concentrates the ordering contract: initRouter → setParams/navState.
|
|
643
|
+
// Called before hydrateRoot in the hydration path. The createRoot path
|
|
644
|
+
// calls initRouter() directly (no params to read from server embed).
|
|
645
|
+
function runPreHydration(_element: unknown): void {
|
|
646
|
+
// Step 1: Initialize the router
|
|
647
|
+
initRouter();
|
|
648
|
+
|
|
649
|
+
// Step 2: Read server-embedded params and set navigation state
|
|
650
|
+
const earlyParams = (self as unknown as Record<string, unknown>).__timber_params;
|
|
651
|
+
if (earlyParams && typeof earlyParams === 'object') {
|
|
652
|
+
setCurrentParams(earlyParams as Record<string, string | string[]>);
|
|
653
|
+
setNavigationState({
|
|
654
|
+
params: earlyParams as Record<string, string | string[]>,
|
|
655
|
+
pathname: window.location.pathname,
|
|
656
|
+
});
|
|
657
|
+
delete (self as unknown as Record<string, unknown>).__timber_params;
|
|
658
|
+
} else {
|
|
659
|
+
setNavigationState({
|
|
660
|
+
params: {},
|
|
661
|
+
pathname: window.location.pathname,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
525
664
|
}
|
|
526
665
|
|
|
527
666
|
// Store the initial page in the history stack so back-button works
|
|
@@ -532,9 +671,17 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
532
671
|
headElements: null, // SSR already set the correct head
|
|
533
672
|
});
|
|
534
673
|
|
|
535
|
-
// Initialize
|
|
536
|
-
//
|
|
537
|
-
|
|
674
|
+
// Initialize scroll state for the initial entry.
|
|
675
|
+
// When Navigation API is available, use per-entry state.
|
|
676
|
+
// Otherwise fall back to history.state.
|
|
677
|
+
// Note: navApiController is assigned inside initRouter() which runs
|
|
678
|
+
// synchronously before this point via runPreHydration().
|
|
679
|
+
const navApi = navApiController as NavigationApiController | null;
|
|
680
|
+
if (navApi) {
|
|
681
|
+
navApi.saveScrollPosition(0);
|
|
682
|
+
} else {
|
|
683
|
+
window.history.replaceState({ timber: true, scrollY: 0 }, '');
|
|
684
|
+
}
|
|
538
685
|
|
|
539
686
|
// Populate the segment cache from server-embedded segment metadata.
|
|
540
687
|
// This enables state tree diffing from the very first client navigation.
|
|
@@ -566,28 +713,40 @@ function bootstrap(runtimeConfig: typeof config): void {
|
|
|
566
713
|
}
|
|
567
714
|
|
|
568
715
|
// Register popstate handler for back/forward navigation.
|
|
716
|
+
// When Navigation API is active, the navigate event covers traversals —
|
|
717
|
+
// popstate is a no-op. When unavailable, popstate handles back/forward.
|
|
718
|
+
//
|
|
569
719
|
// Use pathname+search (not full href) to match the URL format used by
|
|
570
720
|
// navigate() — Link hrefs are relative paths like "/scroll-test/page-a".
|
|
571
721
|
// Read scrollY from history.state — the browser maintains per-entry state
|
|
572
722
|
// so duplicate URLs in history each have their own scroll position.
|
|
573
723
|
window.addEventListener('popstate', () => {
|
|
724
|
+
// Navigation API handles traversals via the navigate event.
|
|
725
|
+
if (navApiController) return;
|
|
726
|
+
|
|
574
727
|
const state = window.history.state;
|
|
575
728
|
const scrollY = state && typeof state.scrollY === 'number' ? state.scrollY : 0;
|
|
576
729
|
void router.handlePopState(window.location.pathname + window.location.search, scrollY);
|
|
577
730
|
});
|
|
578
731
|
|
|
579
|
-
// Keep
|
|
732
|
+
// Keep scroll position up to date as the user scrolls.
|
|
580
733
|
// This ensures that when the user presses back/forward, the departing
|
|
581
734
|
// page's scroll position is already saved in its history entry.
|
|
582
|
-
//
|
|
735
|
+
// When Navigation API is available, uses per-entry state via
|
|
736
|
+
// navigation.updateCurrentEntry(). Otherwise falls back to history.state.
|
|
737
|
+
// Debounced to avoid excessive state updates during smooth scrolling.
|
|
583
738
|
let scrollTimer: ReturnType<typeof setTimeout>;
|
|
584
739
|
function saveScrollPosition(): void {
|
|
585
740
|
clearTimeout(scrollTimer);
|
|
586
741
|
scrollTimer = setTimeout(() => {
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
742
|
+
const y = getScrollY();
|
|
743
|
+
if (navApiController) {
|
|
744
|
+
navApiController.saveScrollPosition(y);
|
|
745
|
+
} else {
|
|
746
|
+
const state = window.history.state;
|
|
747
|
+
if (state && typeof state === 'object') {
|
|
748
|
+
window.history.replaceState({ ...state, scrollY: y }, '');
|
|
749
|
+
}
|
|
591
750
|
}
|
|
592
751
|
}, 100);
|
|
593
752
|
}
|
|
@@ -655,8 +814,21 @@ clearStaleReloadFlag();
|
|
|
655
814
|
// If the payload references a module ID from a newer deployment, the error
|
|
656
815
|
// surfaces as an unhandled rejection during React's render/hydration cycle.
|
|
657
816
|
// This handler catches those errors and triggers a full page reload.
|
|
817
|
+
//
|
|
818
|
+
// Also catches chunk load failures (dynamic import of missing assets after
|
|
819
|
+
// a deployment) — these surface as "Failed to fetch dynamically imported module"
|
|
820
|
+
// or "Loading chunk <name> failed" errors. See TIM-446.
|
|
658
821
|
window.addEventListener('unhandledrejection', (event) => {
|
|
659
|
-
if (isStaleClientReference(event.reason)) {
|
|
822
|
+
if (isStaleClientReference(event.reason) || isChunkLoadError(event.reason)) {
|
|
823
|
+
event.preventDefault();
|
|
824
|
+
triggerStaleReload();
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Also catch synchronous errors from chunk loads (some browsers throw
|
|
829
|
+
// TypeError synchronously instead of via unhandled rejection).
|
|
830
|
+
window.addEventListener('error', (event) => {
|
|
831
|
+
if (isChunkLoadError(event.error)) {
|
|
660
832
|
event.preventDefault();
|
|
661
833
|
triggerStaleReload();
|
|
662
834
|
}
|