Render ForEach and keyed dynamic collections correctly#54
Merged
Conversation
- route generated content through a real private view core - derive child render identities from explicit collection keys - replace the empty-output known issue with regression coverage
- expand child providers between static tuple siblings - preserve structural scopes around type-erased children - cover horizontal ForEach composition
- preserve row state across collection reorder - confirm removed keyed state is released after the render pass - expose isolated harness state counts for runtime assertions
- flatten active conditional branches into parent layouts - expand nested providers produced by native builder arrays - verify regular and lazy stack composition
- derive each extracted row context from its ForEach ID - preserve row State across list reorder - cover the List-specific rendering path
- move the focused cursor with reordered rows - recover onto a selectable row after removal - keep List and Table selection actions bound to the focused ID
- materialize keyed collection entries with stable occurrence identities - emit deterministic per-runtime duplicate ID diagnostics - verify duplicate ForEach output and diagnostics
- render table rows from the shared keyed snapshot - preserve raw IDs for focus and selection tracking - report duplicate table IDs without dropping rows
- drive characterization renders through the complete runtime pass - prove lifecycle callbacks survive reorder and clean up on removal - prove focus and actions follow collection IDs
- suppress lifecycle and task effects during measurement passes - verify tasks, Observation, lifecycle, and focus follow item IDs - confirm removed collection identities release runtime records
- enable the collection performance fixtures - assert exact regular and lazy stack layout before timing - preserve linear scaling and comparison checks
- exercise the public collection render path - require an empty buffer without builder substitutes
- preserve state and runtime effects when new IDs are inserted - keep focus and selection attached through insert plus reorder - verify inserted task and Observation records clean up
- outer ForEach over groups, inner ForEach over per-group items - assert all items of all groups render in order inside a VStack - closes the nested-builders gap in issue #12 acceptance criteria
- drive Table+ForEach through insert, reorder, delete, and re-add - verify the selection marker stays on the keyed ID across mutations - confirm the binding is not cleared when the selected ID is removed - extend issue #12 selection coverage to the full render path
phranck
added a commit
that referenced
this pull request
Jul 20, 2026
- move the cancel signal and trace out of withTaskCancellationHandler.onCancel - onCancel is scheduled as a separate job and is not guaranteed to run before the test awaits the signal under CI load, causing a 60s timeout on macOS and Linux - Task.sleep throws CancellationError when the row task is cancelled, so recording after try? await Task.sleep with a Task.isCancelled check is deterministic - fixes CI failure on PR #54 for issue #12
phranck
added a commit
that referenced
this pull request
Jul 20, 2026
- move the cancel signal and trace out of withTaskCancellationHandler.onCancel into the task body - onCancel is scheduled as a separate job and is not guaranteed to run before the test awaits the signal under CI load, causing a 60s timeout on macOS and Linux - replace Task.sleep(nanoseconds: UInt64.max) with a bounded 1e12 ns duration: on the Swift 6.0.3 CI toolchain, UInt64.max overflows the internal Duration conversion and the sleep returns immediately, so the task body completes before the row is cancelled and the cancel signal is never recorded - Task.sleep throws CancellationError when the row task is cancelled, so recording after try? await Task.sleep with a Task.isCancelled check is deterministic - fixes CI failure on PR #54 for issue #12
phranck
force-pushed
the
issue/12-keyed-dynamic-collections
branch
from
July 20, 2026 21:01
ce0bbb3 to
ec03210
Compare
- the .task closure inherits @mainactor from the render path, so Task.sleep suspends on the main actor and the cancellation notification can be starved under heavy main-actor load from parallel tests in CI, causing a 60s timeout on macOS and Linux - hop to a nonisolated async function so the sleep runs on the global executor and cancellation is processed independently of main-actor scheduling - Task.isCancelled checks the current task's flag, which is the same task even when executing a nonisolated function - use a bounded 1e12 ns sleep (1000 s, well within Int64) instead of UInt64.max to avoid Duration conversion overflow on the Swift 6.0.3 toolchain - TraceRecorder and AsyncSignal are both Sendable and thread-safe, so recording the cancel trace and signalling from the global executor is safe - fixes CI failure on PR #54 for issue #12
phranck
force-pushed
the
issue/12-keyed-dynamic-collections
branch
from
July 20, 2026 21:25
ec03210 to
f9ba1f2
Compare
- remove stale ForEach.body Never symbol (body is now some View) - classify new childInfos/childViews symbols on ConditionalView, ViewArray, and ForEach as implementation leaks under issue #35 - add entries to both review-policy.json and compatibility-manifest.json
phranck
force-pushed
the
issue/12-keyed-dynamic-collections
branch
from
July 20, 2026 21:50
0737089 to
baa5b28
Compare
- Fix mangled childViews override: the compiler emits ChildView with the word-substitution form (05ChildG0V), not the fully-spelled 9ChildViewV that was hand-derived from the childInfos symbol. The exact-USR check rejected the stale form as an unknown TUIkit symbol. - Add the ForEach.body implementationLeak override. body changed from Never to some View, but only the old body:Never symbol was removed; the new opaque-result symbol (4bodyQrvp) was left unclassified. - Regenerate compatibility-manifest.json from the tool so it matches the current API snapshots byte-for-byte.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #12.
Problem
ForEachrendered no content outside the special List path becauseForEach.bodywasNever,ViewBuilder.buildArrayonly handled Swiftforstatements, and the renderer silently returned an empty buffer for a standaloneForEachexpression. List and Table row extraction used positional identity, so State, lifecycle, focus, and selection did not follow elements across insert, delete, and reorder.Changes
KeyedCollectionSnapshot) shared byForEach, List row extraction, and Table row identity. Each element gets a stable identity derived from its explicit ID plus an occurrence disambiguator for duplicate-ID diagnostics.ForEacha real private core (_ForEachCore) that conforms toRenderable,ChildInfoProvider, andChildViewProvider, and routes throughRenderContext.withKeyedChildIdentity.TupleView,ConditionalView, andViewArrayby recursing intoChildInfoProvider/ChildViewProviderso nested builders and conditionalForEachflatten correctly.extractListRowsandKeyedCollectionSnapshotso runtime records follow row IDs.ItemListHandlerkeyeditemIDs.RuntimeDiagnosticsfor bothForEachandTable.ForEachoutput before timing it in performance fixtures; restore the previously disabledVStackvsLazyVStackcomparison.Acceptance criteria coverage
ForEachinForEach, List, and Table render every expected element.Test plan
swift buildswift test(expect 1301 passing tests plus the pre-existing [P0-09] Preserve subtree liveness across EquatableView cache hits #14 known issue)swiftlint