@timber-js/app 0.2.0-alpha.211 → 0.2.0-alpha.213

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 (183) hide show
  1. package/agent-skill.md +10 -5
  2. package/dist/_chunks/{actions-Rjk4htmA.js → actions-CEootpB1.js} +49 -12
  3. package/dist/_chunks/actions-CEootpB1.js.map +1 -0
  4. package/dist/_chunks/{als-registry-DaxkVjt5.js → als-registry-BZqHCtq-.js} +2 -4
  5. package/dist/_chunks/als-registry-BZqHCtq-.js.map +1 -0
  6. package/dist/_chunks/{cache-api-DGdYfNJn.js → cache-api-LA3sBpUS.js} +5 -5
  7. package/dist/_chunks/{cache-api-DGdYfNJn.js.map → cache-api-LA3sBpUS.js.map} +1 -1
  8. package/dist/_chunks/{chains-DGX9zmg9.js → chains-BfoPFraI.js} +2 -2
  9. package/dist/_chunks/{chains-DGX9zmg9.js.map → chains-BfoPFraI.js.map} +1 -1
  10. package/dist/_chunks/{classify-QwG5rxKI.js → classify-BT66U83D.js} +2 -2
  11. package/dist/_chunks/{classify-QwG5rxKI.js.map → classify-BT66U83D.js.map} +1 -1
  12. package/dist/_chunks/{cli-check-ajNY3B2e.js → cli-check-BfQ54-UJ.js} +3 -3
  13. package/dist/_chunks/{cli-check-ajNY3B2e.js.map → cli-check-BfQ54-UJ.js.map} +1 -1
  14. package/dist/_chunks/{cli-schema-sync-D2eI8jEg.js → cli-schema-sync-czh2dsLs.js} +2 -2
  15. package/dist/_chunks/{cli-schema-sync-D2eI8jEg.js.map → cli-schema-sync-czh2dsLs.js.map} +1 -1
  16. package/dist/_chunks/{client-dep-entries-2HCF09no.js → client-dep-entries-CQwpb8dI.js} +2 -2
  17. package/dist/_chunks/{client-dep-entries-2HCF09no.js.map → client-dep-entries-CQwpb8dI.js.map} +1 -1
  18. package/dist/_chunks/{convention-lint-DLmhGsRS.js → convention-lint-BEVW4EID.js} +3 -3
  19. package/dist/_chunks/{convention-lint-DLmhGsRS.js.map → convention-lint-BEVW4EID.js.map} +1 -1
  20. package/dist/_chunks/{dev-server-TFpEwm3H.js → dev-server-FKxptbnI.js} +2 -2
  21. package/dist/_chunks/{dev-server-TFpEwm3H.js.map → dev-server-FKxptbnI.js.map} +1 -1
  22. package/dist/_chunks/{error-boundary-DsNScGRM.js → error-boundary-9g_Lb2na.js} +3 -3
  23. package/dist/_chunks/{error-boundary-DsNScGRM.js.map → error-boundary-9g_Lb2na.js.map} +1 -1
  24. package/dist/_chunks/{json-lossy-check-CVuRs2hG.js → json-lossy-check-C8zBY2uZ.js} +2 -2
  25. package/dist/_chunks/{json-lossy-check-CVuRs2hG.js.map → json-lossy-check-C8zBY2uZ.js.map} +1 -1
  26. package/dist/_chunks/{live-graph-cNuWMYQI.js → live-graph-C_4v-fHv.js} +4 -4
  27. package/dist/_chunks/{live-graph-cNuWMYQI.js.map → live-graph-C_4v-fHv.js.map} +1 -1
  28. package/dist/_chunks/{logger-BP0LN6vP.js → logger-CbLdcy-W.js} +2 -2
  29. package/dist/_chunks/{logger-BP0LN6vP.js.map → logger-CbLdcy-W.js.map} +1 -1
  30. package/dist/_chunks/{poison-scan-CfQ3unZR.js → poison-scan-C92liMAr.js} +2 -2
  31. package/dist/_chunks/{poison-scan-CfQ3unZR.js.map → poison-scan-C92liMAr.js.map} +1 -1
  32. package/dist/_chunks/{scanner-DmqdxzbW.js → scanner-CQt12vE2.js} +2 -2
  33. package/dist/_chunks/{scanner-DmqdxzbW.js.map → scanner-CQt12vE2.js.map} +1 -1
  34. package/dist/_chunks/{sizeof-BM1409x2.js → sizeof-QPE5nd3u.js} +2 -2
  35. package/dist/_chunks/{sizeof-BM1409x2.js.map → sizeof-QPE5nd3u.js.map} +1 -1
  36. package/dist/_chunks/{walkers-Czu2jXFq.js → walkers-DAT4avhZ.js} +3 -3
  37. package/dist/_chunks/{walkers-Czu2jXFq.js.map → walkers-DAT4avhZ.js.map} +1 -1
  38. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  39. package/dist/analyze/crawl-entry.js +3 -3
  40. package/dist/analyze/graph-command.js +2 -2
  41. package/dist/cache/index.js +2 -2
  42. package/dist/cache/stores/memory.js +1 -1
  43. package/dist/cdn/workers-cache-purge.js +1 -1
  44. package/dist/cli.js +3 -3
  45. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  46. package/dist/client/browser-entry/action-queue.d.ts +1 -0
  47. package/dist/client/browser-entry/action-queue.d.ts.map +1 -1
  48. package/dist/client/browser-entry/form-state.d.ts +22 -0
  49. package/dist/client/browser-entry/form-state.d.ts.map +1 -0
  50. package/dist/client/browser-entry/hydrate.d.ts +9 -1
  51. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  52. package/dist/client/browser-entry/index.d.ts +2 -0
  53. package/dist/client/browser-entry/index.d.ts.map +1 -1
  54. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  55. package/dist/client/error-boundary.js +1 -1
  56. package/dist/client/form.d.ts +17 -64
  57. package/dist/client/form.d.ts.map +1 -1
  58. package/dist/client/index.d.ts +3 -2
  59. package/dist/client/index.d.ts.map +1 -1
  60. package/dist/client/index.js +111 -51
  61. package/dist/client/index.js.map +1 -1
  62. package/dist/client/internal.js +33 -32
  63. package/dist/client/internal.js.map +1 -1
  64. package/dist/client/navigation-api.d.ts +25 -63
  65. package/dist/client/navigation-api.d.ts.map +1 -1
  66. package/dist/client/navigation-transition.d.ts +11 -2
  67. package/dist/client/navigation-transition.d.ts.map +1 -1
  68. package/dist/client/router-effects.d.ts +7 -3
  69. package/dist/client/router-effects.d.ts.map +1 -1
  70. package/dist/client/router-lifecycle.d.ts +17 -11
  71. package/dist/client/router-lifecycle.d.ts.map +1 -1
  72. package/dist/client/router-pipeline.d.ts +3 -2
  73. package/dist/client/router-pipeline.d.ts.map +1 -1
  74. package/dist/client/router-types.d.ts +24 -44
  75. package/dist/client/router-types.d.ts.map +1 -1
  76. package/dist/client/router.d.ts.map +1 -1
  77. package/dist/client/use-form-field.d.ts +39 -0
  78. package/dist/client/use-form-field.d.ts.map +1 -0
  79. package/dist/config-types.d.ts +2 -1
  80. package/dist/config-types.d.ts.map +1 -1
  81. package/dist/index.js +6 -7
  82. package/dist/index.js.map +1 -1
  83. package/dist/plugins/shims.d.ts.map +1 -1
  84. package/dist/routing/index.js +2 -2
  85. package/dist/rsc-runtime/rsc.d.ts +1 -1
  86. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  87. package/dist/server/action-client.d.ts +20 -7
  88. package/dist/server/action-client.d.ts.map +1 -1
  89. package/dist/server/action-handler.d.ts +27 -8
  90. package/dist/server/action-handler.d.ts.map +1 -1
  91. package/dist/server/als-registry.d.ts +22 -2
  92. package/dist/server/als-registry.d.ts.map +1 -1
  93. package/dist/server/client-error-message.d.ts +12 -0
  94. package/dist/server/client-error-message.d.ts.map +1 -0
  95. package/dist/server/flight-scripts.d.ts +9 -0
  96. package/dist/server/flight-scripts.d.ts.map +1 -1
  97. package/dist/server/form-data.d.ts +13 -4
  98. package/dist/server/form-data.d.ts.map +1 -1
  99. package/dist/server/form-state-flight.d.ts +32 -0
  100. package/dist/server/form-state-flight.d.ts.map +1 -0
  101. package/dist/server/index.d.ts +0 -2
  102. package/dist/server/index.d.ts.map +1 -1
  103. package/dist/server/index.js +41 -60
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/server/internal.js +10 -6
  106. package/dist/server/internal.js.map +1 -1
  107. package/dist/server/logger.d.ts +1 -0
  108. package/dist/server/logger.d.ts.map +1 -1
  109. package/dist/server/pipeline.d.ts +20 -6
  110. package/dist/server/pipeline.d.ts.map +1 -1
  111. package/dist/server/request-context.d.ts +27 -2
  112. package/dist/server/request-context.d.ts.map +1 -1
  113. package/dist/server/route-element-builder.d.ts.map +1 -1
  114. package/dist/server/rsc-entry/action-dispatcher.d.ts +6 -5
  115. package/dist/server/rsc-entry/action-dispatcher.d.ts.map +1 -1
  116. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  117. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  118. package/dist/server/rsc-entry/render-route.d.ts +2 -0
  119. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  120. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  121. package/dist/server/rsc-entry/ssr-renderer.d.ts +6 -0
  122. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  123. package/dist/server/ssr-bridge-types.d.ts +10 -0
  124. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  125. package/dist/server/ssr-entry.d.ts.map +1 -1
  126. package/dist/server/ssr-form-state.d.ts +30 -0
  127. package/dist/server/ssr-form-state.d.ts.map +1 -0
  128. package/dist/server/ssr-render.d.ts +3 -0
  129. package/dist/server/ssr-render.d.ts.map +1 -1
  130. package/dist/shared/form-state-flight.d.ts +36 -0
  131. package/dist/shared/form-state-flight.d.ts.map +1 -0
  132. package/docs/api/31-api-client.mdx +31 -3
  133. package/docs/api/34-api-config.mdx +1 -1
  134. package/docs/learn/08-forms-and-actions.mdx +116 -36
  135. package/package.json +1 -1
  136. package/src/client/browser-entry/action-dispatch.ts +104 -24
  137. package/src/client/browser-entry/action-queue.ts +1 -1
  138. package/src/client/browser-entry/form-state.ts +48 -0
  139. package/src/client/browser-entry/hydrate.ts +10 -1
  140. package/src/client/browser-entry/index.ts +25 -7
  141. package/src/client/browser-entry/router-init.ts +7 -20
  142. package/src/client/form.tsx +33 -98
  143. package/src/client/index.ts +3 -2
  144. package/src/client/navigation-api.ts +47 -173
  145. package/src/client/navigation-transition.ts +15 -4
  146. package/src/client/router-effects.ts +8 -4
  147. package/src/client/router-lifecycle.ts +40 -20
  148. package/src/client/router-pipeline.ts +10 -12
  149. package/src/client/router-types.ts +24 -48
  150. package/src/client/router.ts +49 -56
  151. package/src/client/use-form-field.ts +132 -0
  152. package/src/config-types.ts +2 -1
  153. package/src/plugins/shims.ts +0 -2
  154. package/src/rsc-runtime/rsc.ts +3 -0
  155. package/src/rsc-runtime/vendor-types.d.ts +14 -0
  156. package/src/server/action-client.ts +77 -64
  157. package/src/server/action-handler.ts +133 -95
  158. package/src/server/als-registry.ts +23 -9
  159. package/src/server/client-error-message.ts +18 -0
  160. package/src/server/flight-scripts.ts +13 -0
  161. package/src/server/form-data.ts +62 -10
  162. package/src/server/form-state-flight.ts +67 -0
  163. package/src/server/index.ts +0 -4
  164. package/src/server/logger.ts +6 -1
  165. package/src/server/pipeline.ts +27 -8
  166. package/src/server/request-context.ts +39 -2
  167. package/src/server/route-element-builder.ts +12 -1
  168. package/src/server/rsc-entry/action-dispatcher.ts +40 -34
  169. package/src/server/rsc-entry/error-renderer.ts +2 -1
  170. package/src/server/rsc-entry/index.ts +1 -0
  171. package/src/server/rsc-entry/render-route.ts +16 -0
  172. package/src/server/rsc-entry/rsc-stream.ts +20 -11
  173. package/src/server/rsc-entry/ssr-renderer.ts +11 -0
  174. package/src/server/ssr-bridge-types.ts +10 -0
  175. package/src/server/ssr-entry.ts +16 -2
  176. package/src/server/ssr-form-state.ts +58 -0
  177. package/src/server/ssr-render.ts +5 -0
  178. package/src/shared/form-state-flight.ts +74 -0
  179. package/dist/_chunks/actions-Rjk4htmA.js.map +0 -1
  180. package/dist/_chunks/als-registry-DaxkVjt5.js.map +0 -1
  181. package/dist/server/form-flash.d.ts +0 -78
  182. package/dist/server/form-flash.d.ts.map +0 -1
  183. package/src/server/form-flash.ts +0 -89
@@ -8,7 +8,7 @@ import { t as getSsrData } from "../_chunks/ssr-data-nA3I70_n.js";
8
8
  import { t as getLinkCodec } from "../_chunks/codec-registry-oOUxugz3.js";
9
9
  import { i as useNavigationContext, n as useSegmentContext } from "../_chunks/segment-context-xtPUGfdq.js";
10
10
  import { n as useQueryStates } from "../_chunks/use-query-states-I3JMng6J.js";
11
- import React, { createContext, useActionState as useActionState$1, useContext, useRef, useState, useTransition } from "react";
11
+ import React, { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState, useTransition } from "react";
12
12
  import { jsx } from "react/jsx-runtime";
13
13
  //#region src/client/use-link-status.ts
14
14
  /**
@@ -577,53 +577,14 @@ function useSelectedLayoutSegments(parallelRouteKey) {
577
577
  /**
578
578
  * Client-side form utilities for server actions.
579
579
  *
580
- * Exports a typed `useActionState` that understands the action builder's result shape.
581
- * Result is typed to:
582
- * { data: T } | { validationErrors: Record<string, string[]> } | { serverError: { code, data? } } | null
583
- *
584
- * The action builder emits a function that satisfies both the direct call signature
585
- * and React's `(prevState, formData) => Promise<State>` contract.
580
+ * Forms use React's own `useActionState`: an action built with
581
+ * `createActionClient` already has the `(prevState, payload)` signature it
582
+ * calls, and the result is typed from it. `parseFormErrors` reads the errors
583
+ * out of that result.
586
584
  *
587
585
  * See design/08-forms-and-actions.md §"Client-Side Form Mechanics"
588
586
  */
589
587
  /**
590
- * Typed wrapper around React 19's `useActionState` that understands
591
- * the timber action builder's result shape.
592
- *
593
- * @param action - A server action created with createActionClient or a raw 'use server' function.
594
- * @param initialState - Initial state, typically `null`. Pass `getFormFlash()` for no-JS
595
- * progressive enhancement — the flash seeds the initial state so the form has a
596
- * single source of truth for both with-JS and no-JS paths.
597
- * @param permalink - Optional permalink for progressive enhancement (no-JS fallback URL).
598
- *
599
- * @example
600
- * ```tsx
601
- * 'use client'
602
- * import { useActionState } from '@timber-js/app/client'
603
- * import { createTodo } from './actions'
604
- *
605
- * export function NewTodoForm({ flash }) {
606
- * const [result, action, isPending] = useActionState(createTodo, flash)
607
- * return (
608
- * <form action={action}>
609
- * <input name="title" />
610
- * {result?.validationErrors?.title && <p>{result.validationErrors.title}</p>}
611
- * <button disabled={isPending}>Add</button>
612
- * </form>
613
- * )
614
- * }
615
- * ```
616
- */
617
- function useActionState(action, initialState, permalink) {
618
- const [result, formAction, isPending] = useActionState$1(action, initialState, permalink);
619
- return [
620
- result,
621
- formAction,
622
- isPending,
623
- deriveFormErrors(result)
624
- ];
625
- }
626
- /**
627
588
  * Hook for calling a server action imperatively (not via a form).
628
589
  * Returns [execute, isPending] where execute accepts the input directly.
629
590
  *
@@ -638,20 +599,31 @@ function useActionState(action, initialState, permalink) {
638
599
  function useFormAction(action) {
639
600
  const [isPending, startTransition] = useTransition();
640
601
  const execute = (input) => {
641
- return new Promise((resolve) => {
602
+ return new Promise((resolve, reject) => {
642
603
  startTransition(async () => {
643
- resolve(await action(input));
604
+ try {
605
+ resolve(await action(input));
606
+ } catch (error) {
607
+ reject(error);
608
+ }
644
609
  });
645
610
  });
646
611
  };
647
612
  return [execute, isPending];
648
613
  }
649
614
  /**
650
- * Derive FormErrorsResult from an action result.
651
- * Used internally by useActionState 4th tuple element.
652
- * @internal — exported for test access only.
615
+ * Read the errors out of an action result — the state `useActionState`
616
+ * returns. `_root` validation errors are form-level; every other key is a
617
+ * field.
618
+ *
619
+ * @example
620
+ * ```tsx
621
+ * const [result, action, isPending] = useActionState(createTodo, null)
622
+ * const errors = parseFormErrors(result)
623
+ * errors.getFieldError('title') // first message for the field, or null
624
+ * ```
653
625
  */
654
- function deriveFormErrors(result) {
626
+ function parseFormErrors(result) {
655
627
  const empty = {
656
628
  fieldErrors: {},
657
629
  formErrors: [],
@@ -680,6 +652,94 @@ function deriveFormErrors(result) {
680
652
  };
681
653
  }
682
654
  //#endregion
655
+ //#region src/client/use-form-field.ts
656
+ /**
657
+ * useFormField — state for a form field that needs JavaScript (a combobox, a
658
+ * chip list, a row list), made to behave like a native uncontrolled input.
659
+ *
660
+ * React resets a form after its action settles, so a native field with a
661
+ * `defaultValue` lands on whatever default the page now renders: the action's
662
+ * `submittedValues`, or fresh page data after a redirect. State held in
663
+ * `useState` misses that reset and keeps showing the pre-submit edit. This
664
+ * hook shows `defaultValue` until the user edits, and drops the edit when the
665
+ * form resets, so it lands where the native fields do.
666
+ *
667
+ * See design/08-forms-and-actions.md §"The Form Model".
668
+ */
669
+ /**
670
+ * State for a field that follows its form's reset.
671
+ *
672
+ * Returns `[value, setValue, ref]`. Attach `ref` to any element inside the
673
+ * `<form>`, and render the value into the form data yourself (usually a
674
+ * hidden input). `value` is `defaultValue` until `setValue` is called, and is
675
+ * `defaultValue` again after the form resets, unless the form cancels its
676
+ * `reset` event. An edit dispatches a bubbling `change` event from the `ref`
677
+ * element after it commits, so a native `change` listener on the form sees it
678
+ * as it sees a native input's (React's synthetic `onChange` reports only
679
+ * inputs, selects and textareas). A reset and a new default fire none, as
680
+ * they fire none for a native input.
681
+ *
682
+ * @example
683
+ * ```tsx
684
+ * // draft: the form's defaults, read from result?.submittedValues ?? pageData
685
+ * const [tags, setTags, ref] = useFormField(draft.tags);
686
+ * <div ref={ref}>
687
+ * <input type="hidden" name="tags" value={tags.join(',')} />
688
+ * <TagPicker value={tags} onChange={setTags} />
689
+ * </div>
690
+ * ```
691
+ */
692
+ function useFormField(defaultValue) {
693
+ const [edit, setEdit] = useState(null);
694
+ const value = edit ? edit.value : defaultValue;
695
+ const defaultRef = useRef(defaultValue);
696
+ useLayoutEffect(() => {
697
+ defaultRef.current = defaultValue;
698
+ });
699
+ const pendingResets = useRef([]);
700
+ const takeResets = useCallback(() => {
701
+ const over = pendingResets.current.filter((e) => e.eventPhase === Event.NONE);
702
+ if (over.length === 0) return false;
703
+ pendingResets.current = pendingResets.current.filter((e) => !over.includes(e));
704
+ return over.some((e) => !e.defaultPrevented);
705
+ }, []);
706
+ const setValue = useCallback((next) => {
707
+ const wasReset = takeResets();
708
+ setEdit((latest) => {
709
+ const current = wasReset ? null : latest;
710
+ const base = current ? current.value : defaultRef.current;
711
+ const value = next instanceof Function ? next(base) : next;
712
+ return Object.is(value, base) ? current : { value };
713
+ });
714
+ }, [takeResets]);
715
+ const element = useRef(null);
716
+ const ref = useCallback((el) => {
717
+ element.current = el;
718
+ const form = el?.closest("form");
719
+ if (!form) return;
720
+ const onReset = (event) => {
721
+ pendingResets.current.push(event);
722
+ queueMicrotask(() => {
723
+ if (takeResets()) setEdit(null);
724
+ });
725
+ };
726
+ form.addEventListener("reset", onReset);
727
+ return () => {
728
+ element.current = null;
729
+ pendingResets.current = [];
730
+ form.removeEventListener("reset", onReset);
731
+ };
732
+ }, [takeResets]);
733
+ useEffect(() => {
734
+ if (edit) element.current?.dispatchEvent(new Event("change", { bubbles: true }));
735
+ }, [edit]);
736
+ return [
737
+ value,
738
+ setValue,
739
+ ref
740
+ ];
741
+ }
742
+ //#endregion
683
743
  //#region src/client/params-context.ts
684
744
  /**
685
745
  * Segment params context — the one channel params use to reach the browser.
@@ -769,6 +829,6 @@ function useSegmentParams(segmentPath) {
769
829
  return resolveSegmentParams(paramsContext.params, paramsContext.slotParams, segmentPath);
770
830
  }
771
831
  //#endregion
772
- export { Link, LinkStatusContext, buildLinkProps, interpolateParams, mergePreservedSearchParams, replaceUrl, resolveHref, useActionState, useFormAction, useLinkStatus, usePathname, usePendingNavigation, useQueryStates, useRouter, useSegmentParams, useSelectedLayoutSegment, useSelectedLayoutSegments, validateNavigationHref as validateLinkHref };
832
+ export { Link, LinkStatusContext, buildLinkProps, interpolateParams, mergePreservedSearchParams, parseFormErrors, replaceUrl, resolveHref, useFormAction, useFormField, useLinkStatus, usePathname, usePendingNavigation, useQueryStates, useRouter, useSegmentParams, useSelectedLayoutSegment, useSelectedLayoutSegments, validateNavigationHref as validateLinkHref };
773
833
 
774
834
  //# sourceMappingURL=index.js.map