bi-07 — Analysis

Required invariants

  1. A mutation invalidates a superset of what changed, never a subset. Under-invalidation is a correctness bug (stale rendering); over-invalidation is only a performance bug. The engine is therefore permitted to fall back to whole-subtree invalidation, and never permitted to guess low.
  2. The stylesheet is compiled into an index before any element is matched. RuleFeatureSet maps feature → invalidation sets; RuleSet buckets rules by key selector.
  3. Selectors match right-to-left, from the key selector upward.
  4. Identical computed styles are shared. Anything that distinguishes elements defeats it.
  5. Nothing recomputes at mutation time. Recalculation happens at the next rendering opportunity unless something forces a synchronous flush.

The data that replaces folklore

css_properties.json5 declares invalidate: per property. Measured 2026-08-11: 822 property entries, 311 declaring invalidation.

DeclarationCount
["layout", "paint"]95
["paint"]50
["layout"]34
["layout", "scroll-anchor"]13
["compositing"]4

Roughly one property in six triggers layout. The advice "avoid changing CSS in animations" is far too coarse; the table says exactly which.

Note also ["ax-style", ...]: accessibility has its own invalidation category, meaning the accessibility tree is maintained incrementally as a first-class pipeline consumer, not generated on demand for a debugger.

Failure modes

BreakConsequence
Omit sibling invalidation+/~ selectors render stale — the bug the complexity prevents
Always fall back to subtreecorrect and unusable
Defeat style sharing (inline styles on 10k rows)memory and recalc cliff, not slope
:has() used casuallyinverts matching direction; can widen invalidation dramatically
getComputedStyle in a write loopforced synchronous flush per iteration

The cycle that had to be broken

Container queries make style depend on an ancestor's layout, in a pipeline that runs style → layout. The platform resolves it by requiring containment on the queried axis: a query container's size in that axis must not depend on its contents.

That is the pattern to name: when a declarative system risks circularity, the specification adds a restriction that makes the cycle impossible, rather than an iteration limit that makes it terminate. Restrictions keep a system analysable; limits merely keep it from hanging.