Repository navigation
Conversation
Move the useForm/useIslandForm contracts that every adapter promises into core form-presentation.ts and export them from void/pages-client: - FormStatus: error, pending, hasChanges, wasSuccessful, recentlySuccessful (both hooks, all adapters) - FormState<TData>: data plus field-typed errors, reset, setError, clearErrors, clearError (useForm, all adapters) - FormSubmitOptions: router options for option-taking submit methods Adapters keep their framework idioms local: React's FormData action methods, setData in React and Solid, and submit(url, method) on Vue and Svelte island forms. A local FormReturn<TData> keeps each adapter's typed overload and implementation signature in sync. React, Vue and Solid useForm state fields are now readonly, matching Svelte and every island form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover data, errors, error, hasChanges, wasSuccessful and recentlySuccessful alongside pending in each adapter, so dropping readonly from any shared state field fails the typecheck. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yangshun
marked this pull request as draft
November 2, 2026 12:43
Add IslandFormState<TData> next to FormState so useIslandForm's data, errors, reset, clearErrors, clearError and URL-taking submit methods cannot drift between adapters. React and Solid add setData; Vue and Svelte add submit(url, method). Svelte's useIslandForm is now generic like the other adapters, so form.data and reset() field names are typed from the defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- FormState and IslandFormState type errors as read-only, so writing form.errors.x no longer compiles; setError and clearErrors remain the way to change them. In Solid this write mutated the server errors shared by every form. - React and Solid compose the shared state with Readonly<TData>: form.data.x = … compiled but skipped setData (React submitted the value without re-rendering; Solid stores reject direct writes). Vue and Svelte keep writable data for v-model and bind:value. - Island errors are Partial, so a missing field error reads as undefined instead of a string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mutability of data and errors is a framework idiom, not part of the shared contract. FormState and IslandFormState take the errors type as a parameter and default both data and errors to editable in place, which is how Vue and Svelte update reactive state. React and Solid, whose state changes go through setters, pass Readonly data and errors. This restores direct edits such as form.errors.title = '…' on Vue and Svelte island forms, which have no setError. Top-level fields stay read-only everywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
|
Is this intentionally still a draft? |
Collaborator
Author
|
Yes still needs a bit more review, there were some framework-specific mutability concerns that AI seemed to be back-and-forth about. Let me get to it. |
The shared form types took the errors type as a free parameter, so React and Solid restated the error-key rule at each call site and a new adapter got editable fields by default. Core now defines data and errors once and each adapter picks an editing mode: 'in-place' (Vue, Svelte) or 'setters' (React, Solid). Methods are read-only in both form contracts, FormModel takes pending and error from FormStatus, and Vue's useIslandForm is checked against the contract instead of cast to it. Typecheck tests now pin that useForm() fits <Form form>, which adapters have setData or submit(), each adapter's submit signature, and that island errors accept any key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Summary
Moves the form contracts that every adapter promises into core (
packages/void/src/pages/form-presentation.ts, next toFormModel/FormMethod). Only contracts that must not drift between adapters are shared. Framework idioms stay in the adapters.Shared in core (
FormState,IslandFormStateandFormSubmitOptionsare exported fromvoid/pages-client)FormStatuserror,pending,hasChanges,wasSuccessful,recentlySuccessfuluseFormanduseIslandFormin all four adapters;FormModeltakespendinganderrorfrom itFormState<TData, TEditing>FormStatus+data,errors(field-keyed),reset(with the{ initial }overload that core'sparseFormResetArgsparses),setError,clearErrors,clearErroruseForm, all four adaptersIslandFormState<TData, TEditing>FormStatus+data,errors(keyed by any name the action returns),reset(field-typed),clearErrors,clearError,post/put/patch/delete(url)useIslandForm, all four adaptersFormSubmitOptionsOmit<VisitOptions, 'method' | 'data'> & { data? }, previously byte-identical in Vue, Svelte and SoliduseFormin Vue, Svelte and SolidCore defines the
dataanderrorstypes once, including the rule for error keys. Each adapter must pick an editing mode, and there is no default:'in-place'for Vue and Svelte: apps editdataanderrorsdirectly.'setters'for React and Solid:dataanderrorsareReadonlyand change throughsetData,setErrorandclearErrors.Kept in adapters (agreed API decisions; no behavior change)
post/put/patch/delete(formData: FormData)stay native<form action>callbacks. Vue, Svelte and Solid keeppost(opts?: FormSubmitOptions).setDatastays in React and Solid only. Vue and Svelte bind toform.datadirectly.submit(url, method)stays in Vue and Svelte only, and islandsetDatastays in React and Solid only.Each adapter has a local
FormReturn<TData>alias. The typed overload and the implementation signature both use it, so they can no longer drift. It replaces each adapter'sUntypedFormand its duplicated typed return object. Vue'suseIslandFormnow uses the same overload pattern. Its returned object is checked against the island contract; before, it was cast to it. Svelte'suseIslandFormis now generic like the other three adapters, soform.dataandreset()field names are typed from the defaults instead ofRecord<string, unknown>.TypeScript compatibility (release note)
Form types now follow each framework's idiom. Runtime behavior is unchanged.
useForm()anduseIslandForm():data,errors,error,pending,hasChanges,wasSuccessfulandrecentlySuccessful. Writingform.pending = trueor replacingform.data = { … }no longer compiles, because a replaceddataorerrorsobject is no longer the one the form submits and updates. Form methods are read-only too. This is new foruseForm()in@void/react,@void/vueand@void/solid; Svelte and island forms already had read-only fields.dataanderrorsare read-only (both hooks).form.data.email = …andform.errors.email = …no longer compile; usesetData(),setError()andclearErrors(). In React these writes changed the submitted data without re-rendering. Solid stores ignore them. In Solid, a write intoform.errorsalso changed the server errors that every form shares.dataanderrorsstay editable in place (v-model,bind:value,form.errors.title = …). No setters are required.errorsvalues arestring | undefinedin every adapter, matchinguseForm().form.errors.title.lengthnow needs a guard; before, it compiled and then crashed when the field had no error.useIslandForm()infers its data type fromdefaults, like the other adapters. These now fail to compile: reading a field that is not indefaults, passing an unknown field toreset(), and assigning a value of a different type. Anulldefault needs its full type, for example{ photo: null as File | null }, as React, Vue and Solid already require.Risk
useIslandFormdropping anascast. The emitted JavaScript is unchanged.readonlywhen checking assignability, so<Form form={form}>and other consumers are unaffected. The typecheck tests now assert this.void/pages-clientis adapter-facing, and the documented form API is unchanged.Verification
vp run check:sdk: 12/12 packages pass. Each adapter'sform-errors.typecheck.tsandisland-form.typecheck.tspin:dataanderrorsare editable in Vue and Svelte and read-only in React and Solid;useForm()is accepted by<Form form>(FormModel);setDataorsubmit();undefinedand accept any key;dataandresetfields.*-pagesplaygrounds typecheck against the rebuilt adapters.vp run testpassed 515 tests with 1 skipped. The 27 platform-unit failures came from a missing generatedplatform/packages/dashboard/.void/tsconfig.jsonin a fresh worktree; after generating it, all 27 pass.vp test runpassed 759 of 764 files with 1 skipped. Four failed only under full-suite load and all pass when rerun on their own: the astro dev server start timeout, and timeouts indb-template,deploy-direct-r2andzero-trust-service.vp lintandvp fmt --checkare clean on the changed files.main(including refactor(pages): share the Link contract across framework adapters #672 and refactor(pages): share adapter build-plugin code that must stay in sync #673, which share Link and build-plugin code through the samevoid/pages-cliententry), there were no conflicts.vp run check:sdkpasses 12/12, the four*-pagesplaygrounds typecheck, andvp test run form islandpasses 357 files (7349 tests, 22 skipped).🤖 Generated with Claude Code