Describe the bug
Reconciling a plain store can hold an unrelated field's rendered update behind a pending action when a separate in expression observes a property that the action deleted. The incoming snapshot leaves that property absent, matching its already-staged presence.
Reproduction
One-button playground — preview 3ed3810, Tailwind disabled.
Click Build, then Run reproduction. The sequence is:
// Initial store: { a: { value: 0 }, b: { failed: true } }
const removeFlag = action(function* () {
setState(draft => { delete draft.b.failed; });
yield gate;
});
// In a later event, while removeFlag is pending:
setState(reconcile({ a: { value: 1 }, b: {} }));
The component separately renders state.a.value and "failed" in state.b; no memo combines them. The playground releases the action after 2.5 seconds.
Expected: Value: 1 appears while the deletion action remains pending.
Actual: Value: 0 remains until that action finishes, then becomes 1.
Controls and investigation
Verified in Chromium and Firefox, in development and production builds:
- Removing the separate presence expression allows the value to update immediately.
- Assigning
failed = false instead of deleting it, and reconciling the same false, allows it to update immediately.
- Writing
a.value = 1 directly instead of reconciling allows it to update immediately.
The suspected cause is the presence notification in notifyFold and notifyFoldTail. Value notifications compare old/new values, but the presence loops call setSignal for every observed key without comparing presence. The core joins the node's holding transaction before rejecting an equal value, so repeating false can entangle the reconciliation with the deletion action.
Adding an old/new presence comparison to both loops in an isolated test bundle prevents the hold. This is diagnostic evidence, not a fully validated patch.
Actual presence changes must still notify: deleting a prop override can select another merge source even when the resulting value is equal. This reproduction has no merge or provider switch; the suspected problem is the repeated absence-to-absence notification.
Version
solid-js and @solidjs/web from https://pkg.pr.new/mizulu/solid2/...@3ed3810 (Solid commit 3ed3810097bfc5e5d15829277b40315bbd2b5f74).
Describe the bug
Reconciling a plain store can hold an unrelated field's rendered update behind a pending action when a separate
inexpression observes a property that the action deleted. The incoming snapshot leaves that property absent, matching its already-staged presence.Reproduction
One-button playground — preview
3ed3810, Tailwind disabled.Click Build, then Run reproduction. The sequence is:
The component separately renders
state.a.valueand"failed" in state.b; no memo combines them. The playground releases the action after 2.5 seconds.Expected:
Value: 1appears while the deletion action remains pending.Actual:
Value: 0remains until that action finishes, then becomes1.Controls and investigation
Verified in Chromium and Firefox, in development and production builds:
failed = falseinstead of deleting it, and reconciling the samefalse, allows it to update immediately.a.value = 1directly instead of reconciling allows it to update immediately.The suspected cause is the presence notification in
notifyFoldandnotifyFoldTail. Value notifications compare old/new values, but the presence loops callsetSignalfor every observed key without comparing presence. The core joins the node's holding transaction before rejecting an equal value, so repeatingfalsecan entangle the reconciliation with the deletion action.Adding an old/new presence comparison to both loops in an isolated test bundle prevents the hold. This is diagnostic evidence, not a fully validated patch.
Actual presence changes must still notify: deleting a prop override can select another merge source even when the resulting value is equal. This reproduction has no merge or provider switch; the suspected problem is the repeated absence-to-absence notification.
Version
solid-jsand@solidjs/webfromhttps://pkg.pr.new/mizulu/solid2/...@3ed3810(Solid commit3ed3810097bfc5e5d15829277b40315bbd2b5f74).