Skip to content

[FIX] owl-core: signal collections track per-key, not per-signal#1903

Merged
mcm-odoo merged 3 commits into
masterfrom
master-collections-fix-ged
Jul 2, 2026
Merged

[FIX] owl-core: signal collections track per-key, not per-signal#1903
mcm-odoo merged 3 commits into
masterfrom
master-collections-fix-ged

Conversation

@ged-odoo

Copy link
Copy Markdown
Contributor

Reading .has(k) / .get(k) on a signal.Set / signal.Map / signal.WeakMap was subscribing to the signal atom instead of a per-key atom, so mutating any key (e.g. signal.Set(...).add(2)) re-ran every observer of the collection — even one that only read has(1).

The collection helpers in proxy.ts now pass null to onReadTargetKey / onWriteTargetKey, restoring per-key tracking. Shallow inner-value wrapping still respects the signal atom via possiblyReactive, so the existing "X item is not reactive" tests keep passing.

Closes #1902

@ged-odoo ged-odoo force-pushed the master-collections-fix-ged branch 2 times, most recently from 6861c35 to 8a5076c Compare May 28, 2026 11:36
ged-odoo added 3 commits July 1, 2026 15:24
Reading `.has(k)` / `.get(k)` on a `signal.Set` / `signal.Map` /
`signal.WeakMap` was subscribing to the signal atom instead of a per-key
atom, so mutating *any* key (e.g. `signal.Set(...).add(2)`) re-ran every
observer of the collection — even one that only read `has(1)`.

The collection helpers in proxy.ts now pass `null` to onReadTargetKey /
onWriteTargetKey, restoring per-key tracking. Shallow inner-value
wrapping still respects the signal atom via possiblyReactive, so the
existing "X item is not reactive" tests keep passing.

Closes #1902
Spell out what signal.Array / signal.Object / signal.Set / signal.Map
actually do: the proxy wrapping, the .set(newValue) replacement API, the
shallow caveat, and — newly — the tracking granularity asymmetry.

signal.Array and signal.Object invalidate the whole signal on any write;
signal.Set and signal.Map track per-key, matching the behavior of
proxy(). The latter was a documented-elsewhere assumption that confused
users (cf. #1902); it now has a concrete example.
…ment

signal.Array / signal.Object / signal.Set / signal.Map now default to an
empty collection when no initial value is passed, so the common case of
"reactive bag, fill it later" reads more naturally:

  const items = signal.Array<Todo>();
  const cache = signal.Map<string, User>();

Suggested by seb-odoo in #1902 (Note 2).
@ged-odoo ged-odoo force-pushed the master-collections-fix-ged branch from 8a5076c to 7792e74 Compare July 1, 2026 14:04
@mcm-odoo mcm-odoo merged commit 84aea7c into master Jul 2, 2026
2 checks passed
@mcm-odoo mcm-odoo deleted the master-collections-fix-ged branch July 2, 2026 06:12
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.

signal.Set "has" observes too much

2 participants