feat!: array-element field identity via @solidjs/signals store - #6
Open
LouisHaftmann wants to merge 1 commit into
Open
feat!: array-element field identity via @solidjs/signals store#6LouisHaftmann wants to merge 1 commit into
LouisHaftmann wants to merge 1 commit into
Conversation
Replace the @vue/reactivity engine and the hand-written on-change array mutation mirror with the @solidjs/signals store as form-core's reactive engine. Array-element identity is now maintained by the engine: a field rides along with its datum across splice/sort/reverse/unshift/move, so its key, dirty state, and errors follow the element instead of the index. - Field cache re-keyed by store-node identity (WeakMap<node>); primitive leaves remain positional (accepted hybrid). Removes the nested `_array` cache, getFieldCachePath, and the on-change onValidate/onChange mirror. - `form.data` becomes a writable path-tracking facade over the store; `form.setData(recipe)` applies a batch atomically with one validation. - Write contract is now asynchronous (microtask-batched); a hidden `form['~'].flush()` escape hatch forces the pending batch current. - Whole-form validation is driven by a single `deep(store)` effect; per-field issue filtering by path is unchanged. - reset()/non-dirty sourceValues updates use `reconcile` (positional by default) with an optional per-form `reconcileKey` resolver. - React adapter re-renders via a Solid effect bridge; Vue adapter bridges store reactivity to Vue and keeps `field.model` as a writable computed. BREAKING CHANGE: form-core now depends on @solidjs/signals instead of @vue/reactivity; the write/read contract is asynchronous (use `form['~'].flush()` in imperative code); `field.key`'s format changed and `array.delete(key)` resolves by identity rather than parsing an index; the `@falcondev-oss/form-core/reactive` entrypoint was removed. Closes #4 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Closes #4.
What & why
Replaces
@vue/reactivityand the hand-writtenon-changearray-mutation mirror with the@solidjs/signalsstore (SolidJS 2.0,next) as form-core's reactive engine. Array-element identity is now maintained by the engine, so aFormFieldtravels with its datum: reorder /splice/sort/reverse/unshift/ move keep each element'skey, dirty state, and errors attached to the element, not the index. This removes the fragile per-method mirror the issue set out to kill.Key changes
WeakMap<node, { self, children }>). Primitive-leaf array elements stay positional (the accepted hybrid). Deleted the nested_arraycache,getFieldCachePath, theon-changeonValidate/onChangemirror, and thedata-path → cache-pathtransform.form.datafacade — a thin path-tracking proxy over the store; reads pass through (reactive, identity-stable), writes route into the engine setter. Newform.setData(recipe)applies a batch atomically with a single validation.settledpromise; a hiddenform['~'].flush()escape hatch forces the pending batch current (used by imperative/test code and the framework bindings).deep(store)effect (replacing the on-change global callback); per-field issue filtering by path is unchanged; on-change/on-blur/on-submit timing preserved.reset()/ non-dirtysourceValuesupdates usereconcile— positional by default (reproduces prior reset behavior, keeps fields stable), with an optional per-formreconcileKeyresolver to preserve identity across an id-keyed external refresh. Dirty-guard unchanged.useSyncExternalStore; Vue bridges store reactivity into Vue and keepsfield.modelas a writablev-modelcomputed. Migrated the React hook tests off the React-19-incompatible@testing-library/react-hooks.snapshot()feeds validation/submit);field.schema, translate, discriminator, hooks unchanged.Implementation note: identity + the beta store
The beta store clones an element that still carries a pending write-override when its array is reordered, which would sever the field's identity after an edit. To honor the headline user story (drag-reorder of edited rows keeps focus/dirty), each array-restructuring op first bakes the store (
reconcile(snapshot(store))) to collapse the override layer — transparent to values and node identity. It's anO(n)step at form scale, pinned to@solidjs/signals@2.0.0-beta.24; the pre-release dependency and its blast radius were accepted in the issue.Breaking changes (pre-1.0)
@solidjs/signalsinstead of@vue/reactivity; the@falcondev-oss/form-core/reactiveentrypoint was removed.awaita tick or callform['~'].flush().field.key's string format changed;array.delete(key)resolves the element by identity instead of parsing an index.Tests
Core (49), React (2), Vue (2) all green;
tsc --build, ESLint/Prettier, andpnpm buildclean. Identity is asserted through the public handle only (samekey, preserved dirty/errors, correctat(i)after a move), including the edit-then-reorder case; primitive-array positional behavior,delete(key)after reorder,setDatasingle-validation,reset, dirty-guard, and the key resolver are all covered.🤖 Generated with Claude Code