Skip to content

[P0-05] Bind state, observation, lifecycle, and tasks to stable structural identity #10

Description

@phranck

Problem

Effect and state identity is reconstructed rather than preserved:

  • .onAppear, .onDisappear, and .task allocate a new UUID on each body evaluation in Sources/TUIkit/Extensions/View+Events.swift:181-246.
  • LifecycleManager therefore treats a still-visible view as removed, restarts tasks, and retains disappear callbacks.
  • @State hydrates during construction through global unsafe context/counter state in Sources/TUIkitView/State/State.swift:155-203,355-364, before final keyed child identity is known.
  • Observation subscriptions are recreated without a stable per-identity registry.
  • Current .task lacks id: behavior and does not preserve current Swift actor-inheritance semantics.

Proposed solution

  • Derive identity from the committed structural path plus stable modifier slots and explicit child keys.
  • Bind DynamicProperty storage, Observation subscriptions, lifecycle callbacks, tasks, focus, handlers, and preferences to that identity.
  • Remove subscriptions and callbacks on true unmount.
  • Define internal lifecycle/task slots, ID-change behavior, cancellation, and Swift-6.0-supported actor inheritance. Exact public lifecycle and .task(id:priority:) signatures plus compile fixtures are owned by [P1-16] Add environment-driven shared View modifiers with exact signatures #21.
  • Define and diagnose duplicate keyed identities.

Acceptance criteria

  • Identity survives body reconstruction and unchanged rerenders.
  • onAppear occurs once per mounted identity; onDisappear occurs only on true removal.
  • A task stays alive across unchanged rerenders and restarts once on appearance or ID change.
  • Insert, delete, and reorder preserve state by explicit key; removed identities are cleaned up.
  • Observation subscriptions and callback/task tables do not grow over repeated render cycles.
  • Actor isolation of a task closure is preserved on Swift 6.0.
  • Existing issue .task Modifier Does Not Fire #1 is covered by end-to-end macOS and Linux tests.

Dependencies

Depends on the per-app runtime contract from P0-03 and characterization tests from P0-01.

Parallelization

After runtime boundaries are fixed, this can run beside P0-04 and the terminal surface issue. It should own identity, DynamicProperty hydration, lifecycle, and task-slot files.

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