Implement incremental layout with dirty bit propagation
Add a dirty-bit system to the layout engine so that only modified subtrees
are re-laid-out instead of recomputing the entire layout tree each frame.
- Add `dirty` flag and `cached_available_width` to `LayoutBox`
- Add `DirtyTracker` struct with `mark_dirty()` (propagates to ancestors),
`mark_all_dirty()`, and `clear()` APIs
- `compute_layout()` skips clean subtrees, offsetting position if needed
- `pre_collapse_margins()` skips clean subtrees (already collapsed)
- Add `layout_incremental()` entry point that rebuilds the box tree,
transfers cached layout for clean nodes, and only runs expensive
layout computation on dirty subtrees
- Add layout counter (`layout_count()` / `reset_layout_count()`) for
metrics and testing
- Add tests: dirty tracker propagation, incremental skip verification,
correctness invariant (incremental == full layout), empty/all-dirty
edge cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>