You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reactivity rebuilt as a single dependency graph: state, computeds, effects, and bindings are now nodes in one unified graph resolved in a single pass, replacing the split between the reactive state manager and a parallel binding-context system. No API change and nothing to migrate; the result is a smaller core, faster reads of unchanged values, leaner scheduling and dependency re-tracking, and one place where dependency tracking is defined.
List rendering rebuilt on targeted updates: data-list no longer creates a reactive effect per row; each list registers its rows' bound fields on one per-list dispatcher, and an in-place field change routes to exactly the bindings that read it, down to a single direct DOM write for plainly bound fields. No API change and nothing to migrate: roughly 20 to 25% less memory per row on lists whose templates use item-level computed properties, faster targeted updates and removals, and one update path where there used to be four, which let this cycle's unification surface and fix several latent inconsistencies before they could ever ship.
Added
wildflower.unregister(name) unified component/store teardown: removes a component definition (destroying its live instances and disposing their reactive resources) and/or disposes a store of that name, freeing the name for a fresh registration. Fills the gap where registration was first-write-wins with no way to replace a definition. Useful for live-preview and HMR teardown, dynamic apps that swap components, and tests. Safe for an unknown name; handles a name used by both a component and a store.
Dev-mode warning (WF-215) when a component or store is re-registered with a different definition: re-registering an existing name is silently skipped (the original kept); when the incoming definition actually differs, development builds now warn and point at wildflower.unregister. Identical re-registrations stay quiet; the check compares method source, not just shape. Development builds only.
Iteration dependencies are tracked: a computed or binding that iterates a reactive object's keys (Object.keys, for...in, spread) now re-runs when a key is added or removed. Previously only reassigning the object woke iteration readers. Updating an existing key's value still does not re-run keys-only readers, so hot-path field writes stay quiet.
Dev-mode warning (WF-214) when a zero-arg computed in a list row reads an item property via this: item-level computeds receive the item as their first argument; a computed declared without parameters evaluates at component scope, so this reads of item fields silently resolve undefined. The warning fires only when the read misses on the component but the current item has that property, names the computed and property, and suggests the (item) signature. Development builds only; fires once per component and computed.
The data-csp-safe script attribute: on pages with a strict Content Security Policy, add data-csp-safe to the framework script tag and WildflowerJS starts directly in CSP-safe mode, never attempting dynamic code evaluation, so the page produces zero CSP violations and zero report-uri reports. Without it the framework still auto-detects and falls back, at the cost of one benign violation report from the startup capability probe.
Custom directives and lifecycle hooks now ship in every build: the declarative data-* custom-directive API and component lifecycle hooks, previously bundled only with the plugin system, are now available in all five build variants including lite and mini. plugin() and dependency injection stay gated to the full / spa / standard builds.
DevTools timeline observability: development-build per-frame timeline recording (microtask drains, effect runs, render-sweep duration). Stripped entirely from production builds; surfaces in the companion DevTools extension.
Dev-mode warning when a computed reads pool.length / pool.size: these are non-reactive getters, so a computed that reads one caches a single value and never re-runs, leaving the bound UI silently stale. The warning names the computed and suggests the mirror-in-tick() fix. Development builds only; fires at most once per pool.
Breaking Changes
data-bind-attr renders a non-boolean attribute bound to false as the literal attr="false" instead of removing it. Previously the component binding path removed a non-boolean attribute when its bound value was false; it now writes the literal string, matching the list path and the documented contract that non-boolean attributes hold their literal value. Boolean attributes are unchanged: false still removes them, since for a boolean attribute presence is the value. For most bindings the new behavior is more correct. aria-expanded, aria-hidden, aria-checked and similar now emit ="false" instead of vanishing, which is the accessible form (an absent aria-expanded conveys no state; aria-expanded="false" says "collapsed"). The one place it can bite is a CSS attribute-presence selector used as a flag. [data-active] { ... } matches whenever the attribute exists regardless of its value, so an element bound { 'data-active': isActive } with isActive === false now matches [data-active] (it renders data-active="false") where before the attribute was absent and did not match. Migration: if you relied on a falsey bind removing such an attribute, bind the value as null or undefined (both still remove the attribute) instead of false, or select on the value ([data-active="true"]) rather than on presence.
Fixed
wildflower.config({ forceCSPMode: true }) now takes effect at runtime: the documented call updated the option without switching the live evaluator, and a switched evaluator would then have failed on its first compile; expressions compiled after the call now use the CSP-safe parser. For zero-violation pages, prefer the new data-csp-safe attribute.
A nested item prop read only through an expression binding now reacts: data-bind-class="user.active ? 'on' : 'off'" (and style/attr/show/render expressions) where no other binding co-read the nested leaf did not update on mutation, because only root identifiers were registered, not the full dotted path.
Reactivity gaps closed in item-level-computed and data-render paths in lists: a per-item computed reading component-level state behind a && short-circuit now wakes when that state mutates; a stale render-cache read no longer immediately undoes a per-item data-render insert; a data-render placeholder comment node no longer triggers a swallowed TypeError.
data-pool binding errors surface in dev instead of failing silently: a data-bind / -class / -style / -attr expression in a pool template that threw was caught and skipped on every flush with nothing logged. Development builds now warn once per offending binding, naming the pool and the error; production builds carry zero runtime cost.
data-show toggles the .wf-show class on every path, not only inside reactive contexts: the documented anti-flash rule [data-show]:not(.wf-show) { display: none } relied on the .wf-show class being present once an element is visible, but data-show inside components and list rows toggled display without ever adding the class, so the CSS guard kept those elements hidden. The class is now written wherever a data-show verdict is applied, so the anti-flash contract holds in components and lists, not just context-bound elements.
data-bind-attr clears keys dropped from a bound attribute object on the component path: a key removed from a bound attribute object now removes its attribute from the element instead of leaving the previous value behind. The list path already did this; the component effect path did not, so a stale attribute could linger after the bound object stopped including it. (The related change to how a non-boolean false value renders is listed under Breaking Changes.)
data-bind-style applies !important and CSS custom properties on every path: style values assigned through element.style[prop] drop !important priority and no-op for CSS custom properties (--x). Both the component update path and the list-row writers now route through setProperty, so !important survives reactive updates and custom properties apply; a property dropped from the bound object on a later update is cleared instead of left in place.
subscribe store-wait timeout is bounded by elapsed time, not poll count: the timeout for a component waiting on a subscribed store counted polling iterations rather than wall-clock time, so on a busy page where the poll ran less often the effective wait could stretch past the configured subscribeTimeout. It now fires at the configured millisecond bound regardless of how frequently the poll runs.
Memory leaks on list clear and row removal closed: clearing a list left scope-captured references to the old array alive, and a retired row's update-dispatch entry was not released when the row was removed; both are now freed, so repeated build-and-clear cycles no longer accumulate memory.
$this / $item primitive lists now render: a data-list over an array of primitives (strings or numbers) referenced with $this or $item threw and rendered nothing; these lists now render their values.
data-list / data-pool create path rebuilt (clone + setter): rows are built by cloning a cached row prototype and writing each text binding to textContent, batched into one DocumentFragment, replacing the previous serialize-all-rows-to-HTML-then-reparse path. Substantially faster row creation, most visibly on large lists.
Second pass on the data-list / data-pool create path: each row reads its values from the underlying raw array instead of through the reactive proxy (skipping a per-row proxy and its access traps), resolves its bound child elements by walking element node pointers instead of indexing a live element collection, and copies only the item properties its class expressions reference. Builds without server-side rendering also drop an unused legacy list path. Most visible when building or replacing large lists.
Single-text-binding update fast-path: a targeted single-prop change to an item prop bound to exactly one plain text node writes textContent directly, skipping the generic per-item bind dispatch. Once such a field is identified, later writes to it update the text node directly at assignment time, bypassing the update-batching step entirely.
Nested-path targeted rebind: a deep item-prop change (rows[i].user.name) now skips the unaffected bindings on the row instead of rebinding the whole row. Shallow (flat) item-prop updates are unchanged.
Leaner data-list update path: redundant per-item class re-evaluation is skipped when the changed prop is not referenced by any class binding, and the per-update DOM re-scan for nested [data-list] elements is eliminated, dropping the per-update querySelectorAll calls to zero.
Faster item insertion and removal on reactive lists: adding items with push, unshift, or splice and removing them now operate on the underlying raw array, skipping a layer of reactive-proxy traversal, and single-item removal drops a redundant proxy lookup while re-indexing the remaining rows.
Faster cross-store computed reads and writes on shallow chains: direct property access in the proxy set traps (instead of receiver-form Reflect), single-proxy cross-store reads (eliminating a double-proxy hop), and skipping dependency re-tracking on lean re-evaluation of static-dependency cross-store computeds.
Reactive updates flush on the microtask: pending effects drain after a state change rather than waiting for the next animation frame, removing up to a frame of latency before the DOM reflects an update.
Lower per-row memory and allocation on large lists: per-object reactive bookkeeping moved off the row objects into a side table, repeated per-row metadata was de-duplicated, and the per-row text writer is now shared.