Imported from bpisano/SwiftTUI (
AGENTS.md). Install upstream withnpx skills add bpisano/SwiftTUI. Copyright stays with the author.
SwiftTUI Agent Context
This repository is a SwiftUI-inspired terminal UI engine. Most architectural questions in this repo are easier to answer by first comparing the local implementation with OpenSwiftUI and OpenAttributeGraph.
Mandatory Reference Projects
Before changing any of the following areas, read the reference projects first:
ViewListForEachLayoutViewSubgraphAttribute- dynamic view lifetime / removal / reorder behavior
Reference repositories:
../../OpenSwiftUI../../OpenAttributeGraph
Recommended starting points in the reference code:
../../OpenSwiftUI/Sources/OpenSwiftUICore/View/Input/ViewList.swift../../OpenSwiftUI/Sources/OpenSwiftUICore/View/DynamicViewContent/ForEach.swift../../OpenSwiftUI/Sources/OpenSwiftUICore/Layout/Dynamic/DynamicLayoutView.swift../../OpenAttributeGraph/Sources/OpenAttributeGraph/Graph/Subgraph.swift
Do not make architectural changes to the local view list or graph model without checking how those concepts are represented in the two reference repos.
Package Structure
The package is split into a few layers:
Sources/AttributeGraphA lightweight attribute graph engine used by the UI system.Sources/GeometryShared geometry primitives such asPoint,Size,Rect.Sources/TerminalTerminal-specific primitives and rendering infrastructure.Sources/SwiftTUICoreThe view system, layout system, modifiers, view lists, dynamic properties, and most framework logic.Sources/SwiftTUIHigher-level app/runtime integration built on top ofSwiftTUICoreandTerminal.Sources/AppDemoManual executable demo.
Tests are mainly in:
Tests/SwiftTUICoreTestsTests/AttributeGraphTestsTests/GeometryTests
Isolation Model
Most targets use .defaultIsolation(MainActor.self) in Package.swift.
Assume the framework is conceptually main-actor driven:
- avoid introducing background-thread assumptions
- be careful with API shapes that interact badly with main-actor isolation
- if a design looks odd from a pure Swift perspective, first check whether it exists because of graph/lifetime constraints
Core Rendering Pipeline
The core pipeline is:
- A
Viewis lowered throughmakeViewandmakeViewList. ViewInputscarries runtime inputs:- position
- size
- phase
- storage
ViewListOutputsdescribes the child structure of a view.ViewListturns that structure into[ViewOutputs].ViewOutputscontains:layoutComputerdisplayList
- Layout containers compute child geometries and feed remapped
ViewInputsto children.
Important files:
Sources/SwiftTUICore/Core/View/View.swiftSources/SwiftTUICore/Core/View/ViewInputs.swiftSources/SwiftTUICore/Core/View/ViewOutputs.swiftSources/SwiftTUICore/Core/View/ViewListOutputs.swiftSources/SwiftTUICore/Core/ViewList/ViewList.swiftSources/SwiftTUICore/Views/LayoutView.swift
View Lists
ViewListOutputs is a central type. It preserves structure before the system commits to a runtime ViewList.
Current local model:
.staticList([any ViewElement]).dynamicList(Attribute<any ViewList>)
That distinction is fundamental. Bugs around onAppear, ForEach, removal, or repeated makeView calls are often caused by accidentally turning a static structure into a dynamic one too early.
When working in this area:
- preserve static structure as long as possible
- only box into
Attribute<any ViewList>when runtime dynamism is actually needed - compare with OpenSwiftUI's handling of
_ViewListOutputs, static lists, dynamic lists, and modifiers applied to lists
Layout
LayoutView is the bridge between child ViewOutputs and a Layout.
Layout itself is intentionally simple:
sizeThatFits(proposal:subviews:)placeSubviews(in:subviews:)
Layout.layoutComputer(for:) creates a LayoutComputer that:
- asks each child for its size
- lets the layout place subviews
- records child geometries
Important files:
Sources/SwiftTUICore/Core/Layout/Layout.swiftSources/SwiftTUICore/Views/LayoutView.swiftSources/SwiftTUICore/Views/RootLayout.swiftSources/SwiftTUICore/Views/HStack.swiftSources/SwiftTUICore/Views/VStack.swiftSources/SwiftTUICore/Views/ZStack.swift
If layout bugs involve dynamic children, compare with OpenSwiftUI's dynamic layout path before refactoring.
View Modifiers
Modifiers are implemented as their own lowering pipeline and can affect both makeView and makeViewList.
Important files:
Sources/SwiftTUICore/Core/ViewModifier/ViewModifier.swiftSources/SwiftTUICore/Core/ViewModifier/ViewModifierContent.swiftSources/SwiftTUICore/Core/ViewModifier/UnaryViewModifier.swiftSources/SwiftTUICore/ViewModifiers
When behavior differs between a direct child and the same child inside a list, check the modifier's makeViewList path.
AttributeGraph Model
The local AttributeGraph is a lightweight reimplementation, not a full copy of Apple's internals.
Key concepts:
Attribute<T>Stores a value or rule and tracks dependencies on read.GraphRegisters nodes, records dependencies, invalidates dependents, and tracks transactions.SubgraphGroups attribute lifetimes so dynamic structures such asForEachitems can be created and cleaned as a unit.
Important files:
Sources/AttributeGraph/Attribute/Attribute.swiftSources/AttributeGraph/Graph/Graph.swiftSources/AttributeGraph/Subgraph/Subgraph.swift
If a bug involves stale nodes, repeated reevaluations, or removal crashes, inspect the graph model before changing view code.
Current ForEach Architecture
ForEach is currently implemented as a dynamic view list.
Important files:
Sources/SwiftTUICore/Views/ForEach/ForEach.swiftSources/SwiftTUICore/Views/ForEach/ForEachState.swiftSources/SwiftTUICore/Views/ForEach/ForEachViewList.swift
Current local design:
ForEach.makeViewListreturns a.dynamicListForEachStatekeeps items keyed by explicit data IDs- each item currently stores:
childViewviewListOutputsviewSubgraphviewOutputsSubgraph- cached
[ViewOutputs]
This is an important area of ongoing architectural work. Before changing it:
- compare with OpenSwiftUI's item lifetime and subgraph ownership
- compare how OpenSwiftUI represents per-item child structure
- verify behavior on:
- insertion
- update
- reorder
- removal
Testing Conventions
Always use Swift Testing (import Testing, @Test, #expect, #require) for all tests — except performance benchmarks, which must use XCTest (import XCTest, measure { ... }).
Name test functions using backtick syntax to allow natural-language descriptions with spaces and special characters:
@Test
func `Two siblings get consecutive implicit IDs`() throws { ... }
@Test
func `ForEach items shift position when preceding sibling grows`() async throws { ... }
See Tests/SwiftTUICoreTests/Views/VStackTests.swift for examples.
Performance Workflow
Performance validation is mandatory after every code change, not only after graph changes.
- Run the benchmark suites with:
swift test --filter 'HStackPerformanceTests|VStackPerformanceTests|PerformanceBenchmarkTests' - Compare the new measurements against the latest Markdown baseline under
benchmarks/Performance. - If there is no newer file, use
benchmarks/Performance/2026-04-10-performance-baseline.mdas the comparison point. - Call out any regression before considering the work complete.
- When the new numbers are acceptable, update or add a Markdown baseline file with readable tables so future runs have a stable comparison target.
If the change touches AttributeGraph, Subgraph, ViewList, ForEach, LayoutView, or dynamic child lifetime, treat the performance comparison as especially high priority and do not skip it.
Debugging and Validation
Useful tests:
Tests/SwiftTUICoreTests/Views/ForEachTests.swiftTests/SwiftTUICoreTests/DebugTests.swiftTests/SwiftTUICoreTests/OnAppearTests.swiftTests/SwiftTUICoreTests/OnDisappearTests.swiftTests/SwiftTUICoreTests/RootLayoutTests.swift
Useful commands:
swift test --filter ForEachTestsswift test --filter OnAppearTestsswift test
For graph debugging, the project often uses:
Graph.current.digraphcopyToClipboard(...)in debug tests
Working Rules For Agents
- Read the local implementation first.
- For any architectural change, read the matching OpenSwiftUI/OpenAttributeGraph implementation before proposing a design.
- Do not change tests casually. Fix the architecture or runtime behavior instead.
- Preserve the static vs dynamic list distinction.
- Treat
ForEach,LayoutView,ViewListOutputs, andSubgraphas tightly coupled concepts. - Prefer small, reviewable refactors over sweeping rewrites unless the change really requires it.