Skip to content

[P0-08] Separate measurement, rendering, and effect commit #13

Description

@phranck

Problem

Layout measurement evaluates composite bodies before a pure measurement context is established in Sources/TUIkitView/Rendering/ChildInfo.swift:183-210. Only some paths honor isMeasuring; lifecycle, tasks, observation, state cleanup, handlers, preferences, header, and status-bar registration can still mutate live runtime state.

The first-frame header sizing pass in Sources/TUIkit/App/RenderLoop.swift:215-223 performs a full render without marking it as measurement. Correction or abandoned passes can therefore start effects or leave registrations that were never part of the visible tree.

Proposed solution

  • Replace the ambient Bool with explicit .measure, .render, and .commit phases.
  • Collect effects in scratch transaction state while evaluating and laying out a candidate tree.
  • Commit lifecycle, task, observation, focus, handler, preference, header/status, cache-liveness, and terminal effects exactly once from the final tree.
  • Discard all effects from failed, correction, or superseded passes.
  • Make header/status measurement use isolated scratch collectors.

Acceptance criteria

  • Framework-controlled measurement starts no tasks, registers no live handlers/subscriptions, and commits no State, storage, cache, lifecycle, or terminal mutation. Arbitrary user side effects inside body are unsupported and must be documented or diagnosed; the framework cannot prevent arbitrary user code from performing them.
  • An abandoned frame has no lifecycle or terminal side effects.
  • Only commit changes terminal output and visible runtime records.
  • Buffer and committed effects are identical across single-pass and correction-pass layouts.
  • Deterministic phase traces cover header, status bar, preferences, focus, task, and Observation paths.
  • Swift 6.0 macOS/Linux gates and DocC complete without diagnostics.

Dependencies

Depends on stable identity (#10) and keyed child traversal (#12). It requires a stable render-result boundary, not completion of the full cell-surface migration in #11.

Parallelization

Serial critical-path work because it touches RenderLoop, RenderContext, ChildInfo, effect registration, and cache lifecycle.

Commit structure

  • Split this issue into small, thematic, independently revertible commits wherever the work can remain coherent.
  • Every commit must build and keep its applicable tests/gates green. Do not commit an intentionally failing regression test; use local/known-issue characterization or land the test with the smallest fix.
  • Keep characterization/fixtures, mechanical renames or moves, semantic changes, and documentation/migration updates separate when each step remains green.
  • The issue boundary is not a commit boundary; multiple commits are expected for independently reversible changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: criticalBlocks runtime correctness or the release-quality gate

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions