Skip to content

feat(core)!: array-element field identity via @solidjs/signals store - #10

Open
LouisHaftmann wants to merge 1 commit into
masterfrom
feat/issue-4-solid-store-identity
Open

feat(core)!: array-element field identity via @solidjs/signals store#10
LouisHaftmann wants to merge 1 commit into
masterfrom
feat/issue-4-solid-store-identity

Conversation

@LouisHaftmann

Copy link
Copy Markdown
Contributor

Closes #4

Editing array fields used to desync the two internal data structures (reactive form data and the field cache) on every mutation. The old design stored array items positionally and mirrored each array method (splice, sort, reverse, …) by hand, so a FormField stayed bound to its index: reordering a list left dirty state, errors, and DOM rows with the slot instead of the value.

This swaps the reactive engine to the @solidjs/signals store (next channel). The store keeps one stable node per raw object, so field state is keyed by engine-maintained object identity and the whole mutation-mirror is gone:

  • Reordering, splicing, sorting, or moving elements keeps each element's FormField — key, dirty state, errors, and bound DOM — attached to the element, not the index. array.delete(key) removes by identity.
  • Primitive-array elements stay positional (a primitive has no identity to track; documented limitation).
  • form.data stays directly readable/writable via a facade that routes writes into the store setter; new form.setData(recipe) applies batch edits atomically with one validation.
  • Writes are batched and flushed on a microtask: derived state is guaranteed current after the next flush. Hidden escape hatch for tests/imperative code: form['~'].flush().
  • reset() and non-dirty sourceValues updates apply via reconcile, positionally by default; an optional reconcileKey resolver preserves identity across external refreshes for id-bearing rows.
  • Adapters rewritten: React re-renders from engine notifications (useSyncExternalStore-style subscription), Vue bridges them into Vue reactivity with v-model intact.

Verified end-to-end in the playground (extended with a reorderable editable list): keys travel with rows across reorder, delete-by-key removes the right element after reorder, reset restores positionally without remounting surviving rows.

Reviews

Review Found Fixed
Standards (smells) 7 3
Spec (#4) 6 2
Ponytail ultra 8 4

Notable fixes from review: facades/views are now unwrapped before being stored back into the engine (slot-swap reorders used to create fresh nodes), field.reset() on reference-node fields merges into the live node so identity survives, and the React adapter's stale submit/sourceValues ref sync was restored.

Left unfixed, deliberately: toVueField's explicit per-property getters (type safety over a getter loop), the clone() wrapper (preserves const-generic inference), sync mirror counters and dual listener registries (load-bearing), and JSON.stringify-based deep tracking missing opaque-leaf swaps that stringify identically (edge case, noted for later).

BREAKING CHANGE: field.key format changed to a stable per-node id, array.delete(key) resolves by identity instead of parsing an index, and the read/write contract is asynchronous. The @vue/reactivity helpers exported from form-core/reactive are removed.

Created with AI. Verified by a human.

Replaces the @vue/reactivity engine with the @solidjs/signals store.
The store keeps one stable reactive node per raw object, so the field
cache is keyed by engine-maintained object identity and the hand-written
on-change array-mutation mirror disappears entirely.

- FormFields over object array elements travel with their datum across
  splice/sort/reverse/unshift/reorder: same stable key, preserved dirty
  state and errors. Primitive-array elements stay positional.
- form.data is a writable facade routing writes into the store setter;
  new form.setData(recipe) applies batch edits atomically.
- Writes are batched and flushed on a microtask; derived state is
  current after the next flush. Hidden escape hatch: form['~'].flush().
- reset() and non-dirty sourceValues updates apply via reconcile
  (positional by default, optional reconcileKey resolver).
- React adapter re-renders via subscription, Vue adapter bridges engine
  notifications into Vue reactivity (v-model preserved).

BREAKING CHANGE: field.key format changed to a stable per-node id,
array.delete(key) resolves by identity instead of parsing an index,
and the read/write contract is asynchronous (flush before reading
derived state in tests and imperative code). The @vue/reactivity
helpers exported from form-core/reactive are removed.

Closes #4

Created with AI. Verified by a human.

Co-authored-by: ox-alpha <noreply@falcondev.it>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Array-element field identity via @solidjs/signals store (replace on-change sync)

1 participant