Skip to content

Fix: Align View, ViewBuilder, ViewModifier, and ModifiedContent with SwiftUI - #67

Merged
phranck merged 6 commits into
mainfrom
issue/17-view-modifier-alignment
Jul 22, 2026
Merged

Fix: Align View, ViewBuilder, ViewModifier, and ModifiedContent with SwiftUI#67
phranck merged 6 commits into
mainfrom
issue/17-view-modifier-alignment

Conversation

@phranck

@phranck phranck commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Closes #17.

Summary

  • ViewBuilder matches SwiftUI's result surface: buildBlock() -> EmptyView, tuple-typed TupleView<(repeat each Content)>, buildIf (was buildOptional), buildEither -> _ConditionalContent (renamed from ConditionalView), buildLimitedAvailability -> AnyView, and the optional-expression overload SwiftUI does not expose is gone; buildArray/ViewArray stays as a documented additive convenience
  • TupleView takes SwiftUI's shape (TupleView<T> with public var value: T): the builder captures children on a reflection-free fast path, the public initializer resolves them via Mirror, and an unconditional best-effort Equatable keeps .equatable() working on multi-child containers (Swift 6.0 cannot constrain tuple elements; non-comparable children safely degrade to cache misses)
  • ViewModifier adopts body(content:) with the _ViewModifier_Content placeholder; .modifier(_:) returns the new public ModifiedContent value (conditional View/Equatable conformances). Procedural buffer transformations moved behind the package-internal BufferViewModifier/BufferModifiedView layer; PaddingModifier/BackgroundModifier are internal now
  • Isolation: @preconcurrency @MainActor on View and ViewModifier per the pinned SwiftUI reference; ViewBuilder stays @MainActor as a documented compiler-floor exception (Swift 6.0 infers main-actor isolation onto conforming types' initializers)
  • AnyView gains init(erasing:)
  • Compile fixture for issue 17 extended (custom views, conditional/availability builders, generic content, custom modifiers, nested modified content); docs rewritten; manifest and policy regenerated from fresh 6.0.3 snapshots

Deviations from the issue text

  • No ContentBuilder: the pinned SwiftUI reference snapshots (Xcode 26.6, SwiftUI + SwiftUICore, all platforms) contain no such symbol; the alias appears to be a spec artifact. Not added.

Test plan

  • 1348 tests green on macOS (Swift 6.0.3) and Linux (Docker 6.0.3-noble) via ./scripts/test-linux.sh
  • New suites: ViewBuilderAlignmentTests (builder surface, tuple typing, runtime equality, availability erasure), ViewModifierContentTests (contract, environment propagation, nesting, value semantics, padding regression)
  • verify-compatibility-manifest.sh passes against the regenerated manifest
  • DocC builds warning-free after the symbol-link fixes

phranck added 6 commits July 22, 2026 12:03
- Add buildBlock() -> EmptyView for empty builder blocks
- Reshape TupleView to SwiftUI's tuple-generic TupleView<T> with a
  public value property; the builder captures children on a fast path
  and the public initializer resolves them by reflection
- Give TupleView an unconditional best-effort Equatable so .equatable()
  keeps working on multi-child containers (Swift 6.0 cannot constrain
  tuple elements); non-comparable children safely degrade to cache
  misses
- Rename ConditionalView to SwiftUI's _ConditionalContent spelling and
  rename buildOptional to buildIf
- Erase limited-availability content to AnyView and add AnyView's
  labeled init(erasing:)
- Drop the optional-expression overload SwiftUI does not expose
- Replace the buffer-level public ViewModifier protocol with SwiftUI's
  body(content:) contract, including the _ViewModifier_Content
  placeholder that resolves to the wrapped view at its body position
- Add the public ModifiedContent value type (unconstrained pair,
  conditional View and Equatable conformances) and return it from
  View.modifier(_:)
- Move procedural buffer transformations behind the package-internal
  BufferViewModifier layer with BufferModifiedView; PaddingModifier and
  BackgroundModifier become internal and .padding()/.background() route
  through the internal wrapper
- Cover the new contract with rendering, environment-propagation,
  nesting, and value-semantics tests plus a padding regression test
- Add _ViewModifier_Content to the type_name exclusion list
- Add @preconcurrency to the View and ViewModifier protocols per the
  pinned SwiftUI reference surface
- Document why ViewBuilder stays @mainactor as a compiler-floor
  exception: Swift 6.0 infers main-actor isolation onto conforming
  types' initializers, which the builder must call
- Extend the issue-17 compile fixture with custom views, conditional
  and availability builder forms, generic content, a custom modifier,
  and nested modified content
- Rewrite the modifier sections in CustomViews, Architecture, and
  RenderCycle for body(content:), ModifiedContent, and the internal
  buffer layer
- Replace links to removed or underscored symbols (ModifiedView,
  modify(buffer:context:), _ConditionalContent) that DocC can no longer
  resolve
- Drop overrides for removed symbols: the buffer-level ViewModifier
  requirements, ModifiedView, ConditionalView, the pack-generic
  TupleView members, buildOptional, and the internalized padding and
  background modifiers
- Add overrides for the new surface: ModifiedContent, the
  body(content:) contract, tuple-typed TupleView members, buildIf,
  buildBlock(), AnyView(erasing:), and the AnyView-erasing
  buildLimitedAvailability
- Regenerate the manifest with TUIkitAPICheck against fresh 6.0.3
  macOS and Linux snapshots
@phranck
phranck merged commit 428ce3a into main Jul 22, 2026
6 checks passed
@phranck
phranck deleted the issue/17-view-modifier-alignment branch July 22, 2026 10:58
wadetregaskis added a commit to wadetregaskis/TUIkit that referenced this pull request Sep 2, 2026
The tail — storage, view contracts, data flow, scenes — judged at branch
granularity, since by this point each branch's character was clear from
its first commit and per-commit detail would not have changed a verdict.

PR phranck#65's serialised writes are already ours (NSLock plus a serial save
queue). Its error reporting is not: we drop persistence failures on the
floor, so a read-only config directory looks like a successful save.
Deferred — real, small, unforced.

PRs phranck#67, phranck#69 and phranck#70 are deferred as a batch and explicitly NOT as
upstream-review work. They are all one thing — align the framework's
shape with SwiftUI's — and CLAUDE.md requires that to be answered against
the real SDK, which is how SwiftUI-compatibility.md was built. Reading
what upstream happened to write is the wrong instrument. Some of it is
already shipped here; DynamicProperty as a public protocol is not. The
next step is an audit pass over the data-flow and view-modifier surfaces
with these branches as one input among several, which is a piece of work
rather than triage.

Also fixes the ledger's row order: recording 8a73ec3 out of sequence
made `verify` report a gap, which is the check doing its job.

Final spread across 205 commits: 56 n-a, 37 noise, 35 have, 35 defer,
17 adapt, 16 reject, 5 queued, 4 inspired.

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1-12] Align View, ContentBuilder, ViewModifier, and ModifiedContent with SwiftUI

1 participant