feat(forms): graduate forms toward Stable — progressive-enhancement actions + optimistic updates (#139, #140) - #156
Merged
Merged
Conversation
…ctions + optimistic updates (#139, #140) Implements both open `forms` tickets in one branch. #139 — Promote `forms` to Stable (freeze the 1.13 surface): - Document the `'manual'` validationStrategy default as a deliberate contract: handleSubmit() always runs the full validation pass; the strategy only gates automatic per-change/per-blur validation. (Behaviour unchanged; clarified.) - Make the SSR serialization boundary a guaranteed contract: serializeFormState() now deterministically drops functions, File/Blob/FileList, bigint, and symbol via an explicit replacer instead of relying on incidental JSON.stringify. - Validate the createFieldArray() stable-key contract: new optional `getKey` enforces present, unique keys on every structural mutation and throws a descriptive error naming the offending key; adds keys()/keyAt(). Positional (no-getKey) behaviour is unchanged. - Freeze + document the public surface; add a Stability section with the exit-criteria checklist and frozen-surface reference; module header updated to "targeting Stable in 1.15.0". #140 — Progressive-enhancement form actions + optimistic updates: - formAction(target, options): binds a form to a server action (endpoint URL or function). enhance(form) sets the native action/method (+ optional hidden CSRF field) so it POSTs without JS, then intercepts submit for a fetch-based, optimistic-aware submit with reactive pending/error/result state. Non-OK responses throw FormActionError (status/response). Composes with server csrf(). - useFormStatus(action): read-only readonly() views of the action's signals (React 19 parity). - optimistic(base, reducer): optimistic-update primitive whose reactive value folds pending drafts over the base; add()/run()/clear() + pending/drafts. Tests: tests/forms-stable.test.ts, tests/forms-actions.test.ts (28 new cases). Full suite 2882 pass / 0 fail; tsc, eslint, check-full-bundle, doc-exports (forms 46/46), and lib/types/umd builds all green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Collected PR for both open
formstickets, implemented on one branch.Closes #139
Closes #140
#139 — Promote
formsto Stable (freeze the 1.13 surface)The
formssurface expanded materially in 1.13.0 and has documented sharp edges. This freezes the surface for one minor cycle and settles those edges into guaranteed, tested contracts:validationStrategydefault reviewed + documented. The default stays'manual'(the least-surprising choice for "validate on submit"). The contract is now explicit:handleSubmit()always runs the full validation pass, regardless of strategy —validationStrategyonly gates automatic per-change/per-blur validation. Behaviour is unchanged; the surprise is removed by documentation.serializeFormState()deterministically drops functions,File/Blob/FileList,bigint, andsymbolvia an explicit replacer, instead of relying on incidentalJSON.stringifybehaviour. Re-attach blobs on the client after hydration.createFieldArray()stable-key contract validated with clear errors. New optionalgetKeyenforces present, unique keys on every structural mutation and throws a descriptive error naming the offending key (e.g. "requires stable, unique item keys, but getKey returned "a" for both index 0 and index 1"). Addskeys()/keyAt(index). WithoutgetKeythe array stays positional — no behaviour change.#140 — Progressive-enhancement form actions + optimistic updates
The headline React-19-parity feature: forms that work before/without JS and reconcile optimistically.
formAction(target, options)→ reactivepending/error/result/submitCount/submittedAt;enhance(form)sets the nativeaction/method(+ optional hidden CSRF field) for the no-JS path, then interceptssubmitfor a fetch-based, optimistic-aware submit; programmaticsubmit(formData);reset().targetis an endpoint URL or a function. Non-OK responses throwFormActionError(carryingstatus/response). Composes with the validation pipeline and theservermodule'scsrf()([Feature]: First-party session, auth, and middleware primitives forserver#132). Native forms only support GET/POST, so PUT/PATCH/DELETE degrade to a native POST (the enhanced fetch keeps the real verb).useFormStatus(action)→ read-onlyreadonly()views of the action's signals, mirroring React 19'suseFormStatus.optimistic(base, reducer)→ an optimistic-update primitive whose reactivevaluefolds pending drafts over the base and reverts automatically.add(draft)→ handle withremove();run(draft, task)applies the overlay around an async task;pending/draftsreactive;clear().Quality
tests/forms-stable.test.ts(field-array key contract, SSR boundary, validationStrategy timing) andtests/forms-actions.test.ts(formAction function + string targets, CSRF, enhance/PE, optimistic composition, useFormStatus).tsc --noEmit,eslint,check-full-bundle(in sync),check-doc-exports(forms 46/46), andbuild:lib/build:types/build:umdall green.formAction,useFormStatus,optimistic,FormActionError,FieldArrayKeyFn+ action/optimistic types) are wired intosrc/full.tsand documented in the guide.🤖 Generated with Claude Code