Partial Relayout and Damage Model
This is one of the areas where runtime maturity matters the most.
Because complexity accumulates along a chain:
- a signal changes
- a scope becomes dirty
- a subtree is replaced
- some ancestors may need relayout
- some siblings may only need repositioning
- some terminal regions may need redraw
- some may not need any work at all
Why this is hard
This is not one question. It is a chain of related questions.
If the boundaries stay vague, the result is:
- too much relayout
- too much redraw
- oversized terminal patches
- geometry bugs that are hard to debug
Ansiq’s direction
Ansiq already has:
- ancestor-chain relayout
- invalidated region tracking
- dirty path normalization
- clearer layout and render-math protocols
So it is no longer operating as “whole tree relayout -> whole block redraw”.
Damage and layout are not the same
This distinction matters:
- relayout is about geometry
- the damage model is about which terminal regions truly changed
A position change does not automatically mean a whole rectangular area must be redrawn.
What is already tightened today
The chain now explicitly includes:
- dirty scope collection
- dirty path normalization
- ancestor-chain relayout
- invalidated region tracking
- a clearer boundary between full redraw and partial redraw
So Ansiq is already beyond “whole-tree relayout, then redraw a large block”.
normalize_dirty_paths(...): compress dirty paths first
If a parent and one of its descendants are both marked dirty, for example:
[0]
[0, 0]the parent path should dominate.
Logic such as normalize_dirty_paths(...) does two small but important things:
- remove duplicate paths
- let parent paths dominate descendants that are already covered
Without this step, the runtime can end up doing:
- repeated ancestor relayout
- repeated damage computation
- work that is technically correct but harder to reason about
This is why path normalization happens before relayout work begins.
Ancestor-chain relayout: propagate upward, not everywhere
Ansiq is not doing “a node changed, so recompute the whole tree from the root”.
The closer model is:
- identify the dirty subtree
- replace the old subtree with the new one
- walk upward through its ancestors, remeasuring and repositioning
- stop once geometry no longer changes
That is ancestor-chain relayout.
It is much smaller than full-tree relayout, but more correct than “only update the changed node”. When a subtree changes width or height, the first things that become invalid are often:
- how its parent allocates space
- whether siblings need to move
- whether higher containers change total size
Why wrapper-height sync is part of the same problem
Another subtle failure mode looks like this:
- a dirty subtree has already been replaced
- but a wrapper node still carries the old measured height
At that point, ancestors can keep allocating child rects using stale geometry.
So partial relayout is not only about “doing less work”. It is also about:
- making sure new subtree geometry propagates upward correctly
- relayouting only the ancestors that must learn about that change
Why the damage model must follow the same chain
Once ancestor-chain relayout produces new rects, the damage model can answer:
- which parts of the old rect must be cleared
- which regions only need content redraw
- which siblings did not rerender but still moved and therefore must be redrawn
That is why layout invalidation and damage tracking are not two unrelated optimizations. They are two halves of the same update boundary.
Why this directly affects patch emission
The goal is not elegant internals by themselves. The goal is smaller and more predictable terminal patches:
- if relayout is too broad, patching becomes too broad
- if the damage model is too coarse, visual residue and unnecessary redraw become more likely
That is why this topic matters for both performance and correctness.
Where to go next
Continue with: