@timber-js/app 0.2.0-alpha.212 → 0.2.0-alpha.214

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.
Files changed (134) hide show
  1. package/dist/_chunks/{actions-CCdnVtWm.js → actions-CUh3cClk.js} +43 -8
  2. package/dist/_chunks/{actions-CCdnVtWm.js.map → actions-CUh3cClk.js.map} +1 -1
  3. package/dist/_chunks/{canonicalize-CgHoscYO.js → canonicalize-BAWkWiKK.js} +28 -2
  4. package/dist/_chunks/{canonicalize-CgHoscYO.js.map → canonicalize-BAWkWiKK.js.map} +1 -1
  5. package/dist/_chunks/{chains-BfoPFraI.js → chains-CjK1Eu6a.js} +2 -2
  6. package/dist/_chunks/{chains-BfoPFraI.js.map → chains-CjK1Eu6a.js.map} +1 -1
  7. package/dist/_chunks/{classify-BT66U83D.js → classify-GAdt6aiA.js} +2 -2
  8. package/dist/_chunks/{classify-BT66U83D.js.map → classify-GAdt6aiA.js.map} +1 -1
  9. package/dist/_chunks/{cli-check-BfQ54-UJ.js → cli-check-DOzjlycg.js} +3 -3
  10. package/dist/_chunks/{cli-check-BfQ54-UJ.js.map → cli-check-DOzjlycg.js.map} +1 -1
  11. package/dist/_chunks/{cli-schema-sync-czh2dsLs.js → cli-schema-sync-B9wDaQvW.js} +2 -2
  12. package/dist/_chunks/{cli-schema-sync-czh2dsLs.js.map → cli-schema-sync-B9wDaQvW.js.map} +1 -1
  13. package/dist/_chunks/{client-dep-entries-CQwpb8dI.js → client-dep-entries-DyDqXOF9.js} +2 -2
  14. package/dist/_chunks/{client-dep-entries-CQwpb8dI.js.map → client-dep-entries-DyDqXOF9.js.map} +1 -1
  15. package/dist/_chunks/{convention-lint-BEVW4EID.js → convention-lint-B3QEGJX7.js} +2 -2
  16. package/dist/_chunks/{convention-lint-BEVW4EID.js.map → convention-lint-B3QEGJX7.js.map} +1 -1
  17. package/dist/_chunks/{dev-server-FKxptbnI.js → dev-server-_9L4KWC-.js} +3 -3
  18. package/dist/_chunks/{dev-server-FKxptbnI.js.map → dev-server-_9L4KWC-.js.map} +1 -1
  19. package/dist/_chunks/{error-boundary-DsNScGRM.js → error-boundary-xxxLtXt6.js} +38 -5
  20. package/dist/_chunks/{error-boundary-DsNScGRM.js.map → error-boundary-xxxLtXt6.js.map} +1 -1
  21. package/dist/_chunks/{live-graph-C_4v-fHv.js → live-graph-Cd3YHvrH.js} +4 -4
  22. package/dist/_chunks/{live-graph-C_4v-fHv.js.map → live-graph-Cd3YHvrH.js.map} +1 -1
  23. package/dist/_chunks/{poison-scan-C92liMAr.js → poison-scan-BnJjBOkn.js} +2 -2
  24. package/dist/_chunks/{poison-scan-C92liMAr.js.map → poison-scan-BnJjBOkn.js.map} +1 -1
  25. package/dist/_chunks/{scanner-CQt12vE2.js → scanner-CKAT5gRx.js} +2 -2
  26. package/dist/_chunks/{scanner-CQt12vE2.js.map → scanner-CKAT5gRx.js.map} +1 -1
  27. package/dist/_chunks/{walkers-DAT4avhZ.js → walkers-DwCXEyRu.js} +3 -3
  28. package/dist/_chunks/{walkers-DAT4avhZ.js.map → walkers-DwCXEyRu.js.map} +1 -1
  29. package/dist/adapters/nitro-preview.d.ts.map +1 -1
  30. package/dist/adapters/nitro.js +11 -2
  31. package/dist/adapters/nitro.js.map +1 -1
  32. package/dist/analyze/crawl-entry.js +3 -3
  33. package/dist/analyze/graph-command.js +2 -2
  34. package/dist/cli.js +3 -3
  35. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  36. package/dist/client/browser-entry/action-queue.d.ts +1 -0
  37. package/dist/client/browser-entry/action-queue.d.ts.map +1 -1
  38. package/dist/client/browser-entry/form-state.d.ts +22 -0
  39. package/dist/client/browser-entry/form-state.d.ts.map +1 -0
  40. package/dist/client/browser-entry/hydrate.d.ts +9 -1
  41. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  42. package/dist/client/browser-entry/index.d.ts +2 -0
  43. package/dist/client/browser-entry/index.d.ts.map +1 -1
  44. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  45. package/dist/client/error-boundary.js +1 -1
  46. package/dist/client/index.d.ts +1 -0
  47. package/dist/client/index.d.ts.map +1 -1
  48. package/dist/client/index.js +90 -2
  49. package/dist/client/index.js.map +1 -1
  50. package/dist/client/internal.js +26 -10
  51. package/dist/client/internal.js.map +1 -1
  52. package/dist/client/navigation-transition.d.ts +11 -2
  53. package/dist/client/navigation-transition.d.ts.map +1 -1
  54. package/dist/client/router-effects.d.ts +70 -8
  55. package/dist/client/router-effects.d.ts.map +1 -1
  56. package/dist/client/router-pipeline.d.ts +3 -1
  57. package/dist/client/router-pipeline.d.ts.map +1 -1
  58. package/dist/client/router-types.d.ts +12 -1
  59. package/dist/client/router-types.d.ts.map +1 -1
  60. package/dist/client/router.d.ts.map +1 -1
  61. package/dist/client/use-form-field.d.ts +39 -0
  62. package/dist/client/use-form-field.d.ts.map +1 -0
  63. package/dist/config-types.d.ts +2 -1
  64. package/dist/config-types.d.ts.map +1 -1
  65. package/dist/index.js +5 -5
  66. package/dist/index.js.map +1 -1
  67. package/dist/routing/index.js +2 -2
  68. package/dist/server/action-client.d.ts +11 -2
  69. package/dist/server/action-client.d.ts.map +1 -1
  70. package/dist/server/canonicalize.d.ts +22 -0
  71. package/dist/server/canonicalize.d.ts.map +1 -1
  72. package/dist/server/flight-scripts.d.ts +9 -0
  73. package/dist/server/flight-scripts.d.ts.map +1 -1
  74. package/dist/server/form-data.d.ts +13 -4
  75. package/dist/server/form-data.d.ts.map +1 -1
  76. package/dist/server/form-state-flight.d.ts +32 -0
  77. package/dist/server/form-state-flight.d.ts.map +1 -0
  78. package/dist/server/index.js +37 -23
  79. package/dist/server/index.js.map +1 -1
  80. package/dist/server/internal.js +54 -3
  81. package/dist/server/internal.js.map +1 -1
  82. package/dist/server/pipeline-helpers.d.ts +31 -0
  83. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  84. package/dist/server/pipeline.d.ts.map +1 -1
  85. package/dist/server/rsc-entry/action-dispatcher.d.ts.map +1 -1
  86. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  87. package/dist/server/rsc-entry/render-route.d.ts +2 -0
  88. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  89. package/dist/server/rsc-entry/ssr-renderer.d.ts +6 -0
  90. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  91. package/dist/server/ssr-bridge-types.d.ts +7 -5
  92. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  93. package/dist/server/ssr-entry.d.ts.map +1 -1
  94. package/dist/server/ssr-form-state.d.ts +30 -0
  95. package/dist/server/ssr-form-state.d.ts.map +1 -0
  96. package/dist/shared/form-state-flight.d.ts +36 -0
  97. package/dist/shared/form-state-flight.d.ts.map +1 -0
  98. package/docs/api/31-api-client.mdx +22 -0
  99. package/docs/api/34-api-config.mdx +1 -1
  100. package/docs/learn/08-forms-and-actions.mdx +109 -22
  101. package/package.json +1 -1
  102. package/src/adapters/nitro-preview.ts +10 -1
  103. package/src/client/browser-entry/action-dispatch.ts +34 -10
  104. package/src/client/browser-entry/action-queue.ts +1 -1
  105. package/src/client/browser-entry/form-state.ts +48 -0
  106. package/src/client/browser-entry/hydrate.ts +9 -18
  107. package/src/client/browser-entry/index.ts +25 -7
  108. package/src/client/browser-entry/router-init.ts +3 -2
  109. package/src/client/index.ts +1 -0
  110. package/src/client/navigation-transition.ts +13 -2
  111. package/src/client/router-effects.ts +99 -10
  112. package/src/client/router-pipeline.ts +7 -3
  113. package/src/client/router-types.ts +12 -1
  114. package/src/client/router.ts +35 -6
  115. package/src/client/use-form-field.ts +132 -0
  116. package/src/config-types.ts +2 -1
  117. package/src/server/action-client.ts +68 -53
  118. package/src/server/canonicalize.ts +29 -0
  119. package/src/server/flight-scripts.ts +13 -0
  120. package/src/server/form-data.ts +62 -10
  121. package/src/server/form-state-flight.ts +67 -0
  122. package/src/server/pipeline-helpers.ts +61 -0
  123. package/src/server/pipeline.ts +13 -1
  124. package/src/server/rsc-entry/action-dispatcher.ts +3 -0
  125. package/src/server/rsc-entry/index.ts +1 -0
  126. package/src/server/rsc-entry/render-route.ts +16 -0
  127. package/src/server/rsc-entry/ssr-renderer.ts +12 -14
  128. package/src/server/ssr-bridge-types.ts +7 -6
  129. package/src/server/ssr-entry.ts +16 -3
  130. package/src/server/ssr-form-state.ts +58 -0
  131. package/src/shared/form-state-flight.ts +74 -0
  132. package/dist/server/form-state-embed.d.ts +0 -32
  133. package/dist/server/form-state-embed.d.ts.map +0 -1
  134. package/src/server/form-state-embed.ts +0 -63
@@ -31,6 +31,37 @@ import type { PhaseName } from './pipeline-outcome.ts';
31
31
  * See design/08-forms-and-actions.md §"CSRF Protection".
32
32
  */
33
33
  export declare function csrfGate(req: Request, canonicalPath: string, csrf: CsrfConfig, matchRoute: RouteMatcher): Response | null;
34
+ /**
35
+ * The redirect for a safe request whose URL is not canonical, or null to
36
+ * serve it.
37
+ *
38
+ * A GET for `/admin/` used to render with a 200 at that URL. The server
39
+ * rendered with the canonical `/admin` while the browser hydrated with the
40
+ * raw `/admin/`, so anything branching on `usePathname()` hydrated
41
+ * mismatched (TIM-1571). Redirecting makes the address bar — the client's
42
+ * copy of the URL — the canonical one, and leaves one URL per page for
43
+ * caches and crawlers.
44
+ *
45
+ * Only GET and HEAD are redirected. Every other method — an action's POST,
46
+ * a CORS preflight — is served at the canonical path, as before: a
47
+ * redirected POST is re-sent by the browser, which an action must not
48
+ * depend on.
49
+ *
50
+ * Runs inside proxy.ts's `next()`, not before it, so proxy.ts still runs
51
+ * on every request and its headers land on the redirect. A cross-origin
52
+ * `GET /api/items/` needs proxy.ts's CORS headers on the 308 itself:
53
+ * browsers apply the CORS check to a redirect response.
54
+ *
55
+ * A document request gets a relative 308 (never built from `Host`). A page
56
+ * navigation gets the router's soft redirect, decided exactly as for a
57
+ * `redirect()` outcome: an RSC request that does not match a route.ts
58
+ * handler (`buildRedirectResponse`). The framework's `_rsc` cache key is
59
+ * dropped from the soft target; the router appends a fresh one when it
60
+ * fetches the canonical URL.
61
+ *
62
+ * See design/07-routing.md §"Non-Canonical URLs Redirect".
63
+ */
64
+ export declare function canonicalRedirect(req: Request, canonicalPath: string, stripTrailingSlash: boolean, matchRoute: RouteMatcher): Response | null;
34
65
  /**
35
66
  * Resolver closure produced once at pipeline construction. The lazy variant
36
67
  * still calls `loader()` per-request (HMR relies on re-importing), but the
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-helpers.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAElF,OAAO,EAAsC,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAMvD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,YAAY,GACvB,QAAQ,GAAG,IAAI,CAWjB;AAID;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;AAErE;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,GAAG,WAAW,GAAG,SAAS,GAC3C,aAAa,GAAG,IAAI,CAatB;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAahF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAO1E;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAMpE;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAIhD;AAID;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,OAAO,GAClB,QAAQ,CAOV;AAID;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,WAAW,EACnB,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CAOf;AAmED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AAeD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAA;CAAO,GAC5E,IAAI,CAIN;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,OAAO,EACZ,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,GACxC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAwBzC;AAiBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAC5B,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAC5B,OAAO,CAAC,IAAI,CAAC,CAef"}
1
+ {"version":3,"file":"pipeline-helpers.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-helpers.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"action-dispatcher.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/action-dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIxD;;;;;;;;GAQG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAC;AAE7E,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,UAAU,EAAE,UAAU,CAAC;IACvB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxC,oEAAoE;IACpE,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,8DAA8D;IAC9D,uBAAuB,EAAE,yBAAyB,CAAC;IACnD;;;;;;OAMG;IACH,UAAU,EAAE,YAAY,CAAC;CAC1B;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,oBAAoB,GACzB,CACD,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,QAAQ,CAAC,KACnE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA8D5B"}
1
+ {"version":3,"file":"action-dispatcher.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/action-dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIxD;;;;;;;;GAQG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAC;AAE7E,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,UAAU,EAAE,UAAU,CAAC;IACvB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxC,oEAAoE;IACpE,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,8DAA8D;IAC9D,uBAAuB,EAAE,yBAAyB,CAAC;IACnD;;;;;;OAMG;IACH,UAAU,EAAE,YAAY,CAAC;CAC1B;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,oBAAoB,GACzB,CACD,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,QAAQ,CAAC,KACnE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAiE5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AA8ClE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAgVD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAItE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,wBAAwB,EAAE,cAAc,GACvD,OAAO,CAAC,OAAO,wBAAwB,EAAE,cAAc,CAAC,CAE1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,wBAAwB,EAAE,uBAAuB,GAChE,OAAO,CAAC,OAAO,wBAAwB,EAAE,uBAAuB,CAAC,CAQnE;8BAwCoB,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AA8ClE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAiVD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAItE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,wBAAwB,EAAE,cAAc,GACvD,OAAO,CAAC,OAAO,wBAAwB,EAAE,cAAc,CAAC,CAE1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,wBAAwB,EAAE,uBAAuB,GAChE,OAAO,CAAC,OAAO,wBAAwB,EAAE,uBAAuB,CAAC,CAQnE;8BAwCoB,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC"}
@@ -31,6 +31,8 @@ export interface RenderRouteDeps {
31
31
  clientSegmentCache: boolean;
32
32
  renderDenyFallback: DenyFallbackRenderer;
33
33
  buildManifest: BuildManifest;
34
+ /** The resolved `renderTimeoutMs`: bounds encoding a no-JS form state. */
35
+ renderTimeoutMs: number;
34
36
  globalError?: {
35
37
  load: () => Promise<unknown>;
36
38
  filePath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAS1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAM/D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,QAAQ,CAAC,CA4JnB"}
1
+ {"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAS1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAQ/D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,aAAa,EAAE,aAAa,CAAC;IAC7B,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAwKnB"}
@@ -60,6 +60,12 @@ interface SsrRenderOptions {
60
60
  /** Tier 2 global-error.tsx file, if present in app/. */
61
61
  globalError?: GlobalErrorFile;
62
62
  slotSkipInfo?: SlotSkipEntry[];
63
+ /**
64
+ * The Flight bytes of the no-JS action's form state this page answers
65
+ * (`encodeFormState`); absent on every other render. Fizz and hydrateRoot
66
+ * both decode them.
67
+ */
68
+ formState?: Uint8Array | null;
63
69
  }
64
70
  /**
65
71
  * Render the RSC stream to HTML via SSR.
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAMlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA8BzD;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAiB,CAAC;AAEhF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAqB,CAAC;AAEtF,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsRjF"}
1
+ {"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAMlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA8BzD;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAiB,CAAC;AAEhF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAqB,CAAC;AAEtF,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgRjF"}
@@ -49,7 +49,6 @@
49
49
  * See `design/02-rendering-pipeline.md` §"RSC → SSR → Client Hydration"
50
50
  * and `CLAUDE.md` §"RSC and SSR Are Separate Vite Environments".
51
51
  */
52
- import type { ReactFormState } from 'react-dom/client';
53
52
  /**
54
53
  * Navigation context passed from the RSC environment to SSR.
55
54
  *
@@ -105,11 +104,14 @@ export interface NavContext {
105
104
  cookies?: Map<string, string>;
106
105
  /**
107
106
  * The form state of the no-JS action this page answers (React's
108
- * `decodeFormState`), passed to Fizz so the `useActionState` hook that
109
- * submitted renders with the action's result. Absent on every other
110
- * render. See design/08-forms-and-actions.md §"No-JS Result Round-Trip".
107
+ * `decodeFormState`), as Flight bytes. SSR decodes them for Fizz and,
108
+ * once they decoded, embeds the same bytes for hydration
109
+ * (server/ssr-form-state.ts), so the `useActionState` hook that submitted
110
+ * renders with the value the browser hydrates with. Absent
111
+ * on every other render. See design/08-forms-and-actions.md §"No-JS Result
112
+ * Round-Trip".
111
113
  */
112
- formState?: ReactFormState;
114
+ formState?: Uint8Array;
113
115
  /**
114
116
  * Lazy thunk that produces the `<script>self.__timber_segments=…</script>`
115
117
  * tag. Invoked by the SSR entry AFTER `renderSsrNodeStream` /
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-bridge-types.d.ts","sourceRoot":"","sources":["../../src/server/ssr-bridge-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,SAAS,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC;;;0DAGsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;4DACwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAAC,EAAE,MAAM,MAAM,CAAC;IAEpC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,UAAU,CAAC;IACpB;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,EAAE,OAAO,CAAC;CAC/B"}
1
+ {"version":3,"file":"ssr-bridge-types.d.ts","sourceRoot":"","sources":["../../src/server/ssr-bridge-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,SAAS,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC;;;0DAGsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;4DACwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAAC,EAAE,MAAM,MAAM,CAAC;IAEpC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,UAAU,CAAC;IACpB;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,EAAE,OAAO,CAAC;CAC/B"}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA+BH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiDzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,eAAe,CAAC,CA4L1B;eAEc,SAAS"}
1
+ {"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiCH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiDzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,eAAe,CAAC,CAuM1B;eAEc,SAAS"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The no-JS action form state on the SSR side (TIM-1572, design/08
3
+ * §"No-JS Result Round-Trip").
4
+ *
5
+ * SSR is the one owner of both consumers of the Flight bytes the RSC
6
+ * environment encoded (`NavContext.formState`): it decodes them for Fizz,
7
+ * and emits the script that embeds the same bytes for `hydrateRoot` only
8
+ * when that decode succeeded. A browser must never hydrate with a state Fizz
9
+ * did not render, or the hook that submitted changes state during
10
+ * hydration. If SSR cannot decode them, neither side gets them.
11
+ */
12
+ import type { ReactFormState } from 'react-dom/client';
13
+ import { type FormStateFlightDecoder } from '../shared/form-state-flight.ts';
14
+ export interface SsrFormState {
15
+ /** For Fizz's `formState` option. */
16
+ formState: ReactFormState | undefined;
17
+ /** The `<script>` that embeds the same bytes for hydration, or `''`. */
18
+ script: string;
19
+ }
20
+ export declare function formStateForSsr(bytes: Uint8Array | undefined, options: {
21
+ /**
22
+ * Whether the page hydrates: its RSC payload is inlined. Without it the
23
+ * browser never calls `hydrateRoot`, so nothing is embedded.
24
+ */
25
+ hydrates: boolean;
26
+ nonce: string | undefined;
27
+ decode: FormStateFlightDecoder;
28
+ timeoutMs: number;
29
+ }): Promise<SsrFormState>;
30
+ //# sourceMappingURL=ssr-form-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssr-form-state.d.ts","sourceRoot":"","sources":["../../src/server/ssr-form-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAI9F,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,OAAO,EAAE;IACP;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,YAAY,CAAC,CAmBvB"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The no-JS action form state on its way to the two renders that need it
3
+ * (TIM-1572, design/08-forms-and-actions.md §"No-JS Result Round-Trip").
4
+ *
5
+ * The RSC environment serializes the form state with Flight
6
+ * (server/form-state-flight.ts). SSR decodes those bytes for Fizz, and the
7
+ * browser decodes the same bytes, embedded in the page, for `hydrateRoot`.
8
+ * Both sides call `decodeFormState` below with their own environment's
9
+ * Flight client, so the two renders get the same value, and the value a
10
+ * `useActionState` result has with JS (Flight, through the action response):
11
+ * a `Date` stays a `Date`, a `Map` a `Map`, and an `undefined` field is
12
+ * absent on both, because the Flight client drops it.
13
+ *
14
+ * The page carries the bytes as base64: Flight may write binary rows (a typed
15
+ * array in a result), which a text chunk would corrupt, and base64 has no
16
+ * character that can end a `<script>` element.
17
+ */
18
+ import type { ReactFormState } from 'react-dom/client';
19
+ /**
20
+ * A Flight client's `createFromReadableStream`, SSR's or the browser's, typed
21
+ * to the form state tuple it decodes. React builds `ReactFormState`
22
+ * (`decodeFormState` on the server) and only React reads it; the Flight
23
+ * client's type parameter is the only place that type is asserted.
24
+ */
25
+ export type FormStateFlightDecoder = (stream: ReadableStream<Uint8Array>) => PromiseLike<ReactFormState>;
26
+ /**
27
+ * Decode the form state's Flight bytes. Rejects if they do not decode within
28
+ * `timeoutMs`. Every byte is in hand, so only a Flight client bug could
29
+ * stall it, and the caller falls back to rendering without form state.
30
+ */
31
+ export declare function decodeFormState(bytes: Uint8Array, decode: FormStateFlightDecoder, timeoutMs: number): Promise<ReactFormState>;
32
+ /** The form state's Flight bytes as the base64 the page embeds. */
33
+ export declare function formStateToBase64(bytes: Uint8Array): string;
34
+ /** The embedded base64 back to the Flight bytes. */
35
+ export declare function formStateFromBase64(text: string): Uint8Array;
36
+ //# sourceMappingURL=form-state-flight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-state-flight.d.ts","sourceRoot":"","sources":["../../src/shared/form-state-flight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,KAC/B,WAAW,CAAC,cAAc,CAAC,CAAC;AAEjC;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,sBAAsB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,CAmBzB;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAI3D;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAK5D"}
@@ -143,6 +143,28 @@ errors.serverError; // { code, data? } | null
143
143
  errors.getFieldError('title'); // string | null
144
144
  ```
145
145
 
146
+ ### `useFormField(defaultValue)`
147
+
148
+ State for a form field that needs JavaScript, such as a combobox or a chip list, that follows the form's reset like a native input with a `defaultValue`. React resets a form after every action, so use this instead of `useState` for any field inside a form. See [Keeping What the User Typed](/docs/forms-and-actions#keeping-what-the-user-typed).
149
+
150
+ ```tsx
151
+ import { useFormField } from '@timber-js/app/client';
152
+
153
+ // draft = toDraft(result?.submittedValues ?? post): the form's defaults
154
+ const [tags, setTags, ref] = useFormField(draft.tags);
155
+
156
+ <div ref={ref}>
157
+ <input type="hidden" name="tags" value={tags.join(',')} />
158
+ <TagPicker value={tags} onChange={setTags} />
159
+ </div>
160
+ ```
161
+
162
+ - `value`: `defaultValue` until `setValue` is called, then the edit. An unedited field follows a new `defaultValue`.
163
+ - `setValue`: like a `useState` setter; accepts a value or an updater.
164
+ - `ref`: attach to any element inside the `<form>`. When the form resets, the edit is dropped and `value` is `defaultValue` again, unless the form cancels the `reset` event.
165
+
166
+ An edit dispatches a bubbling `change` event from the `ref` element, so a native `change` listener on the form sees it. React's `onChange` prop does not: it only reports inputs, selects and textareas. Render the value into the form data yourself, usually with a hidden input.
167
+
146
168
  ### `useFormAction(action)`
147
169
 
148
170
  Bind a server action to a form with pending state.
@@ -153,7 +153,7 @@ limits: {
153
153
  - **Type:** `boolean | string[]`
154
154
  - **Default:** `true`
155
155
 
156
- Strip sensitive fields (passwords, tokens, CVV, SSN, etc.) from the `submittedValues` echoed back on validation failure. `true` uses the built-in deny-list; an array adds extra field names to the built-in list; `false` disables stripping (never do this in production). See [Forms & Server Actions](/docs/forms-and-actions) for the full pattern list.
156
+ Strip sensitive fields (passwords, tokens, CVV, SSN, etc.) from the `submittedValues` echoed back when an action fails. `true` uses the built-in deny-list; an array adds extra field names to the built-in list; `false` disables stripping (never do this in production). See [Forms & Server Actions](/docs/forms-and-actions) for the full pattern list.
157
157
 
158
158
  ### `pageExtensions`
159
159
 
@@ -95,45 +95,132 @@ errors.hasErrors; // boolean
95
95
  errors.getFieldError('title'); // string | null (first error for field)
96
96
  ```
97
97
 
98
- ## After Mutation
98
+ ## Keeping What the User Typed
99
99
 
100
- | Pattern | When to use |
101
- | ------------------------- | ------------------------------------------------ |
102
- | `redirect('/path')` | User should land somewhere new |
103
- | `revalidatePath('/path')` | Current page needs fresh data |
104
- | `useOptimistic` | Instant UI update, action confirms in background |
100
+ React resets a form after every action: each field goes back to its `defaultValue`. Build forms on that reset, and they keep the user's input after a failure and show the saved values after a save, with or without JavaScript:
105
101
 
106
- ## Forms Without JavaScript
102
+ 1. Leave fields **uncontrolled**: a `name` and a `defaultValue`, not `value` + `onChange`.
103
+ 2. Take defaults from the result, then from the page: `result?.submittedValues ?? pageData`.
104
+ 3. On success, `redirect()` (or `revalidatePath()`), so the page renders the saved data.
107
105
 
108
- The same form works with JavaScript disabled, with no extra code. When a no-JS submission's action returns instead of redirecting, timber renders the page again and React hands the result to the `useActionState` hook that submitted it. `state` holds the result on both paths, so errors and repopulated fields render the same way:
106
+ ```ts title="app/events/[id]/actions.ts"
107
+ 'use server';
109
108
 
110
- ```tsx title="app/todos/todo-form.tsx"
109
+ import { z } from 'zod/v4';
110
+ import { coerce, redirect } from '@timber-js/app/server';
111
+ import { action } from '@/lib/action';
112
+
113
+ declare const db: { events: { update(data: unknown): Promise<void> } };
114
+
115
+ export const saveEvent = action
116
+ .schema(
117
+ z.object({
118
+ id: z.string(),
119
+ title: z.string().min(1, 'Title is required'),
120
+ category: z.string(),
121
+ ticketed: z.preprocess(coerce.checkbox, z.boolean()),
122
+ // sets.0.name, sets.1.name… arrive as an array
123
+ sets: z.array(z.object({ name: z.string().min(1) })).default([]), // no rows: no key
124
+ })
125
+ )
126
+ .action(async ({ input }) => {
127
+ await db.events.update(input);
128
+ redirect(`/events/${input.id}`);
129
+ });
130
+ ```
131
+
132
+ `submittedValues` is the raw form: strings, lists as arrays, an unchecked box absent. Page data is typed. One small, lenient function reads either into what the fields render:
133
+
134
+ ```ts title="app/events/[id]/draft.ts"
135
+ export function toDraft(values: Record<string, unknown>) {
136
+ const sets: { name?: unknown }[] = Array.isArray(values.sets) ? values.sets : [];
137
+ return {
138
+ title: String(values.title ?? ''),
139
+ category: String(values.category ?? ''),
140
+ // Submitted: "on", or absent when unchecked. Saved: a boolean.
141
+ ticketed: values.ticketed === true || values.ticketed === 'on',
142
+ sets: sets.map((set) => ({ name: String(set?.name ?? '') })),
143
+ };
144
+ }
145
+ ```
146
+
147
+ ```tsx title="app/events/[id]/event-form.tsx"
111
148
  'use client';
112
149
 
113
150
  import { useActionState } from 'react';
114
151
  import { parseFormErrors } from '@timber-js/app/client';
115
- import { createTodo } from './actions';
152
+ import { saveEvent } from './actions';
153
+ import { toDraft } from './draft';
116
154
 
117
- export function TodoForm() {
118
- const [state, action, pending] = useActionState(createTodo, null);
119
- const errors = parseFormErrors(state);
155
+ type Event = { id: string; title: string; category: string; ticketed: boolean; sets: { name: string }[] };
156
+
157
+ export function EventForm({ event }: { event: Event }) {
158
+ const [result, action, pending] = useActionState(saveEvent, null);
159
+ const errors = parseFormErrors(result);
160
+ // After a failure: what the user submitted. Otherwise: the saved event.
161
+ const d = toDraft(result?.submittedValues ?? event);
120
162
 
121
163
  return (
122
164
  <form action={action}>
123
- <input name="title" defaultValue={(state?.submittedValues?.title as string) ?? ''} />
124
- {errors.getFieldError('title') && (
125
- <p className="text-red-600">{errors.getFieldError('title')}</p>
126
- )}
127
- <button type="submit" disabled={pending}>
128
- {pending ? 'Adding...' : 'Add Todo'}
129
- </button>
165
+ <input type="hidden" name="id" value={event.id} />
166
+ <input name="title" defaultValue={d.title} />
167
+ {errors.getFieldError('title')}
168
+
169
+ {/* React applies a select's defaultValue only on mount: key it on the default */}
170
+ <select key={d.category} name="category" defaultValue={d.category}>
171
+ <option value="meetup">Meetup</option>
172
+ <option value="social">Social</option>
173
+ </select>
174
+
175
+ <input type="checkbox" name="ticketed" defaultChecked={d.ticketed} />
176
+
177
+ {d.sets.map((set, i) => (
178
+ <input key={i} name={`sets.${i}.name`} defaultValue={set.name} />
179
+ ))}
180
+
181
+ {errors.serverError && <p>Could not save. Please try again.</p>}
182
+ <button disabled={pending}>Save</button>
130
183
  </form>
131
184
  );
132
185
  }
133
186
  ```
134
187
 
135
- The result goes only to the hook that submitted, so two forms on one page never see each other's results. Sensitive fields (password, apiKey, cvv, etc.) are automatically stripped from `submittedValues`.
188
+ When a form submission fails — a validation error, an `ActionError`, or an unexpected error — the result carries `submittedValues`, so the user's input survives any of them.
189
+
190
+ Three things break the model:
191
+
192
+ - **Controlled fields.** A field whose value lives in state keeps showing that state after the reset has put the DOM back to its default, and the next submit sends what the DOM holds, not what the user sees. If a form must be controlled, cancel the reset: `<form action={action} onReset={(e) => e.preventDefault()}>`.
193
+ - **An unkeyed `<select>`.** React ignores a changed `defaultValue` on a mounted `<select>`. Key it on its default, as above. Inputs, checkboxes and textareas don't need this.
194
+ - **`useState` for a field that needs JavaScript.** A combobox or a chip list can't be a native input. Hold its state in `useFormField` instead, which follows the reset like a native input:
195
+
196
+ ```tsx
197
+ import { useFormField } from '@timber-js/app/client';
198
+
199
+ function TagsField({ defaultValue }: { defaultValue: string[] }) {
200
+ const [tags, setTags, ref] = useFormField(defaultValue);
201
+ return (
202
+ <div ref={ref}>
203
+ <input type="hidden" name="tags" value={tags.join(',')} />
204
+ <TagPicker value={tags} onChange={setTags} />
205
+ </div>
206
+ );
207
+ }
208
+ ```
209
+
210
+ ## After Mutation
211
+
212
+ | Pattern | When to use |
213
+ | ------------------------- | ------------------------------------------------ |
214
+ | `redirect('/path')` | User should land somewhere new |
215
+ | `revalidatePath('/path')` | Current page needs fresh data |
216
+ | `useOptimistic` | Instant UI update, action confirms in background |
217
+
218
+ ## Forms Without JavaScript
219
+
220
+ The same form works with JavaScript disabled, with no extra code. When a no-JS submission's action returns instead of redirecting, timber renders the page again and React hands the result to the `useActionState` hook that submitted it. `result` holds the result on both paths, so errors and the fields' defaults render the same way, and the server writes the user's input back into the HTML.
221
+
222
+ The result goes only to the hook that submitted, so two forms on one page never see each other's results. It has the same types on both paths: a `Date` your action returns is a `Date` in `state` whether or not JavaScript ran. Sensitive fields (password, apiKey, cvv, etc.) are automatically stripped from `submittedValues`.
136
223
 
137
224
  A form that doesn't use `useActionState` gets no result back, just as with JavaScript, where React discards a plain `<form action>`'s return value. Such an action should `redirect()` to a page that shows the new state (post-redirect-get). An action that throws renders the nearest `error.tsx`: with JavaScript the action rejects to the nearest error boundary, and without it the page answers from the same `error.tsx` with a 500; return errors from `createActionClient` actions instead.
138
225
 
139
- For advanced topics — coercion helpers, `parseFormData`, sensitive field stripping configuration, and the full end-to-end example — see [Advanced Forms](/docs/advanced-forms).
226
+ For advanced topics — coercion helpers, `parseFormData`, indexed lists, and sensitive field stripping configuration — see [Advanced Forms](/docs/advanced-forms).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.212",
3
+ "version": "0.2.0-alpha.214",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -326,7 +326,16 @@ const server = createServer(async (req, res) => {
326
326
  // connection it would be attacker-controlled. The CSRF layer honors it
327
327
  // separately for reverse-proxy deployments.
328
328
  const scheme = req.socket?.encrypted ? 'https' : 'http';
329
- const url = new URL(req.url || '/', \`\${scheme}://\${host}:\${boundPort}\`);
329
+ // An origin-form target ('/path') is concatenated, not resolved against
330
+ // the base: resolved as a relative reference, '//evil.com/' names another
331
+ // host, and the pipeline would see '/' instead of the path it must
332
+ // redirect to '/evil.com' (TIM-1571). Any other form — 'OPTIONS *', an
333
+ // absolute-form target — is resolved as before: concatenating '*' makes
334
+ // an invalid URL, and this callback has no catch around it, so the
335
+ // rejection would take the preview process down.
336
+ const origin = \`\${scheme}://\${host}:\${boundPort}\`;
337
+ const target = req.url || '/';
338
+ const url = target.startsWith('/') ? new URL(origin + target) : new URL(target, origin);
330
339
 
331
340
  // The runtime's own request-boundary canonicalizer (_canonicalize.mjs,
332
341
  // compiled from server/canonicalize.ts): one decode, and the same 400s for
@@ -23,7 +23,7 @@ import { getClientDeploymentId, DEPLOYMENT_ID_HEADER, RELOAD_HEADER } from '../r
23
23
  import { markClientStale } from '../stale-client.ts';
24
24
  import { RSC_CONTENT_TYPE } from '../../shared/rsc-media-type.ts';
25
25
  import { CSRF_REJECT_HEADER, CSRF_REJECT_VALUE } from '../../shared/csrf-reject.ts';
26
- import { createActionQueue } from './action-queue.ts';
26
+ import { createActionQueue, DEFAULT_HOLD_TIMEOUT_MS } from './action-queue.ts';
27
27
 
28
28
  export function setupServerActions(): void {
29
29
  const queue = createActionQueue({
@@ -46,10 +46,10 @@ export function setupServerActions(): void {
46
46
  },
47
47
  });
48
48
 
49
- // An action that redirects settles — ending `useActionState`'s pending
50
- // state — a full round trip before the destination is on screen, and the
51
- // page that submitted stays mounted until then with its button enabled
52
- // again. A second submit would run the mutation again (TIM-1568). So the
49
+ // The page that submitted a redirecting action stays mounted until the
50
+ // destination commits. Its own useActionState stays pending until the
51
+ // hand-off (below), but other forms and effects on it can still dispatch,
52
+ // and a second submit would run the mutation again (TIM-1568). So the
53
53
  // departing page may not dispatch: `departing` is set from the redirect
54
54
  // response until the router is next idle, and a call made
55
55
  // meanwhile is never sent. It resolves at once with `undefined` — what the
@@ -163,10 +163,26 @@ export function setupServerActions(): void {
163
163
  }
164
164
 
165
165
  // Redirects apply unconditionally — the server mutated and told
166
- // the client where to go. Return undefined to React NOW so the
167
- // action scope settles — awaiting the commit inline would deadlock:
168
- // React entangles the commit lane with the action scope, so the
169
- // commit waits on the action while the action waits on the commit.
166
+ // the client where to go. The action settles (returning undefined)
167
+ // once the destination's tree has been handed to React, not before
168
+ // and not after:
169
+ //
170
+ // - Not after its commit: React entangles the commit with the action
171
+ // scope, so the commit would wait on the action while the action
172
+ // waited on the commit.
173
+ // - Not before the hand-off: React would commit the action alone —
174
+ // useActionState's result, and React's reset of the submitting form
175
+ // onto the defaults the *departing* page renders — and the form
176
+ // would show the pre-save values until the destination committed
177
+ // (TIM-1573).
178
+ //
179
+ // At the hand-off the destination's render is already entangled with
180
+ // the action, so settling commits both together: the form resets onto
181
+ // the destination's defaults. The wait always ends: the navigation
182
+ // hands off, or fails, or a navigation the user starts supersedes it
183
+ // (its promise settles either way), or DEFAULT_HOLD_TIMEOUT_MS
184
+ // passes. Every transition waits while an action is pending, so this
185
+ // must not hang on a stalled fetch.
170
186
  //
171
187
  // Set before returning, so React never sees the action settle while
172
188
  // the departing page can still dispatch (see `departing`).
@@ -190,7 +206,9 @@ export function setupServerActions(): void {
190
206
  // still suspended — can commit afterwards, and must not release
191
207
  // the latch: it is the departing page again (codex on #1211).
192
208
  const redirectSeq = r.epoch().seq + 1;
193
- void r.navigate(wrapper._redirect);
209
+ let handOff!: () => void;
210
+ const handedOff = new Promise<void>((resolve) => (handOff = resolve));
211
+ r.navigate(wrapper._redirect, { onHandOff: handOff }).then(handOff, handOff);
194
212
  // Released once the departing page can no longer be on screen:
195
213
  // when any navigation's tree commits (a streamed destination
196
214
  // commits its shell before its stream finishes, and is live from
@@ -213,6 +231,12 @@ export function setupServerActions(): void {
213
231
  // A navigation that failed before taking the router never makes
214
232
  // it busy, so no idle transition would come.
215
233
  if (r.epoch().idle) arrive();
234
+ let timer: ReturnType<typeof setTimeout> | undefined;
235
+ await Promise.race([
236
+ handedOff,
237
+ new Promise<void>((resolve) => (timer = setTimeout(resolve, DEFAULT_HOLD_TIMEOUT_MS))),
238
+ ]);
239
+ clearTimeout(timer);
216
240
  } catch (e) {
217
241
  console.debug(
218
242
  '[timber] action redirect: router not ready, using full navigation',
@@ -31,7 +31,7 @@ export interface ActionQueueDeps {
31
31
  holdTimeoutMs?: number;
32
32
  }
33
33
 
34
- const DEFAULT_HOLD_TIMEOUT_MS = 30_000;
34
+ export const DEFAULT_HOLD_TIMEOUT_MS = 30_000;
35
35
 
36
36
  const noop = (): void => {};
37
37
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The form state a page answering a no-JS action embeds for hydration
3
+ * (`self.__timber_form_state`, server/form-state-flight.ts).
4
+ *
5
+ * `hydrateRoot` needs the value Fizz rendered with, or the `useActionState`
6
+ * hook that submitted hydrates back to its initial state. The embed is the
7
+ * form state's Flight bytes, the same bytes SSR decoded for Fizz, so the
8
+ * browser decodes them with its own Flight client before it hydrates. Only
9
+ * this page waits for that; a page without the embed hydrates at once
10
+ * (TIM-1297: hydration does not wait for the payload root).
11
+ *
12
+ * See design/08-forms-and-actions.md §"No-JS Result Round-Trip".
13
+ */
14
+
15
+ import type { ReactFormState } from 'react-dom/client';
16
+ import { createFromReadableStream } from '../../rsc-runtime/browser.ts';
17
+ import { decodeFormState, formStateFromBase64 } from '../../shared/form-state-flight.ts';
18
+
19
+ /**
20
+ * Every byte is already in the document, so decoding takes microtasks. The
21
+ * bound only keeps a Flight client bug from holding hydration forever.
22
+ */
23
+ const DECODE_TIMEOUT_MS = 5_000;
24
+
25
+ /**
26
+ * Take the embedded form state, and remove it. `null` when the page has
27
+ * none, which is every page but one answering a no-JS action. Otherwise a
28
+ * promise of the decoded state, or of `null` if it could not be decoded: the
29
+ * page then hydrates as though it had none, which resets that one hook.
30
+ */
31
+ export function takeEmbeddedFormState(): Promise<ReactFormState | null> | null {
32
+ const embedded: unknown = Reflect.get(self, '__timber_form_state');
33
+ Reflect.deleteProperty(self, '__timber_form_state');
34
+ if (typeof embedded !== 'string') return null;
35
+ // Inside the chain, so malformed base64 falls back like a failed decode.
36
+ return Promise.resolve(embedded)
37
+ .then((text) =>
38
+ decodeFormState(
39
+ formStateFromBase64(text),
40
+ (stream) => createFromReadableStream(stream),
41
+ DECODE_TIMEOUT_MS
42
+ )
43
+ )
44
+ .catch((error: unknown) => {
45
+ console.warn('[timber] the form state of a no-JS action did not decode:', error);
46
+ return null;
47
+ });
48
+ }