TUIkit Project Analysis
TUIkit has a substantial implementation and test base, but it is not yet a release-quality SwiftUI-compatible SDK. The current project builds and its 1,170 discovered tests pass on macOS and Swift 6.0 Linux, but the build, test, lint, and DocC paths are not warning-free, and several passing tests encode or miss incorrect runtime behavior. The public API is SwiftUI-inspired rather than SwiftUI-compatible: only 68 atomic declaration fragments matched current SwiftUI after module-name normalization, and no reviewed SwiftUI-named type was fully exact when signatures, conformances, isolation, availability, and semantics were considered.
Contents
- Executive summary
- Scope and method
- Repository baseline
- Verification results
- What is already solid
- Critical findings
- Rendering and performance findings
- SwiftUI API compatibility
- Code quality, reuse, and public surface
- Testing and CI findings
- Documentation findings
- Project generator findings
- Existing GitHub issues
- Remediation roadmap
- Standard remediation template
- Final conclusion
Executive summary
TUIkit has a substantial implementation and test base, but it is not yet a release-quality SwiftUI-compatible SDK. The current project builds and its 1,170 discovered tests pass on macOS and Swift 6.0 Linux, but the build, test, lint, and DocC paths are not warning-free. More importantly, several passing tests encode or miss incorrect runtime behavior:
ForEachrenders an empty buffer outside the specialListpath..taskcannot reliably resume into MainActor state because the app loop blocks that actor.- Lifecycle and task modifiers receive a fresh UUID on every body evaluation.
- State invalidation is split between a local
AppStateand multiple global singleton paths. - Measurement can register live effects.
EquatableViewcache hits can make active descendants disappear from state and effect bookkeeping.AppStoragepersistence writers race and can save snapshots out of order.- The ANSI String buffer cannot represent terminal cells, Unicode width, clipping, styling, and compositing consistently.
- The package contains a C image target despite the hard pure Swift/no-C contract.
The public API is SwiftUI-inspired rather than SwiftUI-compatible. A normalized Swift 6.0 symbol inventory classified 1,424 public declarations. Only 68 atomic declaration fragments matched current SwiftUI after module-name normalization, and no reviewed SwiftUI-named type was fully exact when signatures, conformances, isolation, availability, and semantics were considered.
The remediation has been converted into implementation-ready GitHub issues rather than a local plan. Issues are numbered by dependency order, include evidence, a proposed solution, acceptance criteria, dependencies, parallelization, and appropriate labels. The central execution map is #40.
No production code was changed during this audit.
Scope and method
Primary sources
The current repository was treated as the source of truth. The following were read and checked directly:
Package.swift,.swiftlint.yml, GitHub workflows, Linux scripts, generator scripts, and package documentation.- All repository and project agent rules.
- All three open project plans.
- Runtime, state, rendering, lifecycle, environment, scene, terminal, cache, persistence, image, focus, collection, and representative control implementations.
- Relevant tests, performance fixtures, mocks, and DocC pages.
- Existing GitHub issues and their discussions.
The previous papers/project_analysis.md and open plans were used only as secondary context. Findings were verified against current source before inclusion.
Parallel specialist review
Three independent specialist passes covered:
- SwiftUI API parity and Swift 6 concurrency/isolation.
- Runtime, state, code quality, tests, CI, documentation, and generator behavior.
- Rendering correctness, Unicode/ANSI behavior, layout, caching, images, and performance.
Each specialist then cross-reviewed another domain. The final issue boundaries reflect that review:
- Runtime ownership and dependency injection are one root package, not separate singleton fixes.
ForEachcorrectness is separate from true lazy viewport optimization.- Measurement/commit correctness precedes cache-liveness optimization.
- AppStorage serialization is a correctness issue, not a low-priority persistence enhancement.
- Terminal Unicode correctness and buffer performance share one cell/style/surface model.
- Correctness gates are separate from wall-clock performance gates.
Two different agents then independently reviewed the complete 605-line report and all new issue bodies, labels, dependencies, and existing-issue replies. Their corrections were applied to source paths, issue ownership, the #29/#35 boundary cycle, surface dependencies, missing SwiftUI family assignments, Graphify readiness, and commit discipline.
Reference surface
Current Apple SwiftUI interfaces and official documentation were used as the API reference, including SwiftUI updates, View task APIs, model data, Scene, and WindowGroup. Swift 6 concurrency decisions were checked against the official data-race safety migration guide and Swift Evolution proposals for sending, @isolated(any), and type-level nonisolated.
Limitations
- The macOS host uses Apple Swift 6.4 from Xcode 27 beta, so exact Swift 6.0 compiler behavior was separately checked in the Linux Swift 6.0.3 container.
- The pinned Linux SwiftLint 0.58.2 release binary is x86_64 and could not execute locally on the ARM host. Current SwiftLint 0.65.0 was run locally; the Linux CI version still needs an executable strict gate.
- Local
mainis one commit behindorigin/main. The remote-only change is README Ko-fi casing and does not affect the audited implementation. - No Graphify output or configuration exists. In line with project rules, Graphify was not generated during a nonrelease audit. Its release-scoped introduction is tracked in #39.
- The normalized SwiftUI parity classification was produced during this audit but its normalizer is not checked into the repository. The result is useful evidence, not yet a reproducible gate. Issue #7 must make the inventory and classification deterministic.
Repository baseline
| Metric | Current value |
|---|---|
| Source Swift files | 199 |
| Test Swift files | 93 |
| Source lines | approximately 32,734 |
| Test lines | approximately 19,102 |
| Discovered tests | 1,170 |
| Discovered suites | 158 |
Textual @Test occurrences | 1,172, including 2 DocC comment examples |
| Public declaration records in API inventory | 1,424 |
| Public top-level type names | 180 |
| Top-level names shared with SwiftUI | 69 |
| TUI-specific top-level names | 111 |
| SwiftLint warnings | 85 |
| Source-located symbol records without doc comments | 335 |
The API and documentation scans count different symbol-graph representations. The API inventory normalizes 1,424 exported declaration records. The documentation scan evaluates 4,760 source-located symbol records, including member/case/extension records, of which 4,425 contain documentation comments and 335 do not. These counts must not be compared as one denominator.
Module API inventory
| Module | Public declarations | Notes |
|---|---|---|
TUIkit | 836 | App, scenes, controls, containers, terminal-specific user APIs |
TUIkitCore | 123 | Environment/preferences, key input, frame buffers, geometry |
TUIkitImage | 51 | Decode, conversion, loading, caching |
TUIkitStyling | 232 | Color, palette, appearance, terminal styling |
TUIkitView | 182 | View core, builders, state, environment, rendering infrastructure |
No open declaration was found. Much of the cross-target implementation surface is nevertheless public and can use Swift 6.0 package access instead.
Verification results
Gate summary
| Gate | Result | Diagnostic status |
|---|---|---|
macOS swift build | Pass | Not clean: unhandled DocC catalog warning |
macOS swift test | Pass, 1,170 tests | Not clean: compiler warnings in tests |
swift test list | Pass, exactly 1,170 | README/CI claim 1,172 |
Linux ./scripts/test-linux.sh linux | Pass with Swift 6.0.3 | State wrapper suite is disabled on Linux; test warnings remain |
swiftlint lint --quiet --no-cache | Exit 0 | 85 warnings because strict mode is not used |
| DocC generation | Pass | SwiftPM catalog warning plus 3 link warnings |
| Current remote CI | Green | Non-strict gates allow warnings |
The passing result is useful but does not satisfy the requested zero-warning SDK goal.
Compiler warnings observed during tests
| File | Lines | Finding |
|---|---|---|
Tests/TUIkitTests/ProgressViewTests.swift | 201 | Deprecated ProgressBarStyle |
Tests/TUIkitTests/RenderBottleneckTests.swift | 56, 62, 68 | Unused timing values |
Tests/TUIkitTests/LifecycleManagerTests.swift | 218 | Value written but never read |
Tests/TUIkitTests/SecureFieldTests.swift | 177 | Value written but never read |
Tests/TUIkitTests/FrameDiffWriterTests.swift | 194 | Unused writer |
Tests/TUIkitTests/StatePropertyTests.swift | 35 | Unused initial state value |
SwiftLint inventory
| Rule | Warnings |
|---|---|
identifier_name | 67 |
prefer_self_in_static_references | 5 |
closure_end_indentation | 4 |
cyclomatic_complexity | 4 |
function_body_length | 3 |
superfluous_disable_command | 1 |
type_body_length | 1 |
| Total | 85 |
There are 51 source warnings and 34 test warnings. Mechanical fixes should be applied automatically where semantics do not change; complexity findings require targeted decomposition.
Test-count drift
swift test list reports 1,170 discovered tests. A textual search finds 1,172 @Test occurrences because two are inside DocC comments in Tests/TUIkitTests/Mocks/MockTerminal.swift:20,29. Static annotation-like text is not the same as executed tests, especially once parameterized tests are considered. Two workflows also update the same badge and use text/encoding patterns that do not match the README consistently. This is tracked as part of #6.
Documentation diagnostics
- SwiftPM treats
Sources/TUIkit/TUIkit.doccas an unhandled resource during normal build. - DocC emits three warnings for malformed trailing text after
<doc:...>links inSources/TUIkit/TUIkit.docc/Articles/Localization.md:501-503. - CI builds documentation only for the umbrella target, although submodules are public products.
- Documentation comment presence is 92.96 percent over the source-located scan, but presence alone does not establish accuracy or usefulness.
What is already solid
- The package has a meaningful modular direction with separate Core, Styling, View, Image, umbrella, example, and decoder targets.
- The implementation has broad test coverage by count and already uses the Swift Testing framework.
- The full Linux Swift 6.0.3 build and test path passes, which gives a viable compatibility base.
- Most public user-facing controls use a real
body: some Viewwith private core leaves. The confirmed architectural exception isForEach. TerminalProtocolandMockTerminalalready exist. They are appropriate foundations for injected integration tests and must not be removed as dead code.- The code has explicit Environment, preference, identity, render-context, frame-diff, and state-storage concepts. The work is primarily to unify ownership and semantics, not invent every subsystem from scratch.
- Existing documentation is broad enough to become a useful developer guide once examples and claims are tied to executable contracts.
Critical findings
1. Runtime ownership and invalidation are split
Evidence
AppRunnercreates and observes a localAppStateinSources/TUIkit/App/App.swift:79-139.StateBoxinvalidates global state/cache inSources/TUIkitView/State/StateStorage.swift:213-220.- Observation uses the global path in
Sources/TUIkitView/Rendering/Renderable.swift:180-187. TUIContextresolvesRenderCache.sharedinSources/TUIkit/Environment/TUIContext.swift:226-252.- Mutable global services include
LocalizationService.shared,NotificationService.current,StorageDefaults.backend,URLImageCache.shared, and Environment default instances. ViewRenderercreates a partial context inSources/TUIkit/Rendering/ViewRenderer.swift:49-55; the documented compositerenderOnceexample crashes at the missing state-storage force unwrap.
Impact
State can mutate without invalidating the loop that is actually observed. Periodic timers partially hide the fault. Multiple apps/tests can share state, services, cache entries, storage, notifications, localization, focus, and images.
Remediation
Use one MainActor-owned per-app runtime and inject lower-level invalidation through a small module boundary. Keep terminal/session ownership outside the I/O-free RenderContext. Route normal and one-shot rendering through the same complete contract. See #8.
2. The synchronous MainActor loop breaks task semantics
Evidence
Sources/TUIkit/App/App.swift:104-191 polls synchronously and calls usleep on MainActor. The exact sample from existing issue #1 was reproduced: a task that mutates @State does not resume. Nonisolated work can run, but lifecycle UUID churn causes repeated cancellation/restart.
Impact
SwiftUI-style .task, async control actions, image state, refresh, and future Scene actions cannot inherit and resume actor isolation correctly. Idle apps also wake unnecessarily.
Remediation
Drive rendering from async input, invalidation, signal, clock, and shutdown events. Use current Swift-6.0-supported sending @escaping @isolated(any) task closure semantics and add task(id:priority:). See #9, #10, and #21.
3. Signal and timer state is not Swift 6 safe
Evidence
Sources/TUIkit/App/SignalManager.swift:17-51mutates unprotected Swift global Bool flags from POSIX signal handlers.- Pulse and cursor timers write from global queues while MainActor reads their state.
- The app starts pulse/cursor timers unconditionally.
- Spinner creates a per-instance task near
Sources/TUIkit/Views/Spinner.swift:281-291.
Impact
The implementation relies on undefined cross-thread memory behavior, accepts missed signals, and performs periodic work while idle.
Remediation
Use an async-signal-safe bridge, one cooperative event loop, ContinuousClock, and one visible-only per-app scheduler. See #9 and #26.
4. Structural identity is not stable
Evidence
.onAppear,.onDisappear, and.taskallocate new UUIDs inSources/TUIkit/Extensions/View+Events.swift:181-246.- Lifecycle disappearance is inferred from those transient tokens in
Sources/TUIkit/Environment/TUIContext.swift:59-74. - Disappear callback records are not removed after execution.
@Statehydrates during construction through global unsafe context/counter state inSources/TUIkitView/State/State.swift:155-203,355-364.- Observation subscriptions do not have a stable identity-owned registry.
Impact
Appear/disappear fire at the wrong time, tasks restart, callback tables grow, and inserted/reordered dynamic content can receive another element's state.
Remediation
Bind state, observation, lifecycle, tasks, focus, handlers, preferences, and modifier slots to a committed structural identity with explicit keyed children. See #10.
5. ForEach is functionally empty outside List
Evidence
Sources/TUIkit/Views/ForEach.swift:15-22claims ViewBuilder will flatten it.ForEach.bodyisNeverwithout Renderable/child-provider behavior atForEach.swift:78-85.Sources/TUIkitView/Core/ViewBuilder.swift:77-82handles arrays created by Swiftforstatements, not aForEachexpression.Sources/TUIkit/Views/ListRowExtractor.swift:38-52is a List-only escape path and does not apply distinct render identities.- A direct
VStack { ForEach(0..<3) { Text(...) } }probe rendered size0x0with no lines. Tests/TUIkitTests/ComponentViewTests.swift:185-220treats the missing flattening as expected; performance fixtures time empty output.
Impact
Documented stack/lazy-stack examples silently show nothing. Collection state and performance claims are invalid.
Remediation
Add one keyed dynamic-child model shared by ForEach and List/Table row foundations, then test output and ID preservation before optimization. See #12.
6. Measurement and rendering commit live effects prematurely
Evidence
Sources/TUIkitView/Rendering/ChildInfo.swift:183-210evaluates composite bodies before a pure measurement context is established.- Only selected paths honor
isMeasuring; lifecycle, tasks, handlers, preferences, header/status, and observation can still register. - The first-frame header pass in
Sources/TUIkit/App/RenderLoop.swift:215-223performs a full live render for measurement.
Impact
Correction and abandoned layout passes can start tasks, fire lifecycle callbacks, retain handlers, mutate caches, or publish preferences that do not belong to the visible frame.
Remediation
Use explicit measure, render, and commit phases. Collect effects in scratch transaction state and apply them exactly once from the accepted tree. See #13.
7. EquatableView cache hits lose descendant liveness
Evidence
Sources/TUIkitView/Core/EquatableView.swift:89-102,122-130 marks only the wrapper active on a hit. Sources/TUIkit/App/RenderLoop.swift:310-321,401-410 then prunes per-pass state and registrations.
Impact
Cached descendants can lose State, tasks, Observation, focus, key handlers, preferences, header/status values, animations, and nested cache records even while their buffer remains visible.
Remediation
Disable caching for effectful subtrees until cache entries can reproduce the complete committed liveness/effect manifest and dependency set. See #14.
8. AppStorage can persist stale data
Evidence
Sources/TUIkit/State/AppStorage.swift:122-140,172-190 launches detached saves, reads mutable cache state without one immutable snapshot, and allows writers to complete out of order. synchronize() does not reliably wait for all already-started writes. The backend is a mutable global at AppStorage.swift:194-210.
Impact
An older snapshot can overwrite newer values. Tests and multiple apps cannot isolate persistence, and failures are silently discarded.
Remediation
Use immutable snapshots and one ordered writer actor/queue, make synchronize a real barrier, surface failures, and inject the backend per app. See #15.
9. The terminal buffer cannot model cells correctly
Evidence
- A grapheme made from a base scalar plus combining mark is reported as width 2.
- ANSI parsing handles only a partial CSI subset and does not establish safe OSC/DCS/control behavior.
- Text and TextField mix String count, Character count, and terminal-cell width.
Sources/TUIkitCore/Rendering/FrameBuffer.swift:126-140repeatedly copies growing arrays.FrameBuffer.swift:202-240,282-312reparses styled strings for compositing.- ZStack ignores alignment and overlay replacement does not match documented cell semantics.
Impact
Unicode clipping, cursor positions, overlays, style resets, untrusted content safety, and wide-character rendering are inconsistent. Several paths are unnecessarily quadratic.
Remediation
Represent graphemes, width/continuation cells, styles, and transparency in an internal surface. Encode ANSI only at the terminal boundary and build/composite linearly. See #11.
10. The pure Swift contract is false
Evidence
Package.swift:33-39 includes the C target CSTBImage, while README, CONTRIBUTING, and DocC claim a pure Swift/no-C SDK.
Impact
The package violates a hard project constraint and its stated portability/dependency model.
Remediation
Replace the C decoder with a bounded, deterministic pure Swift decoder and malformed-input tests, then remove all native target wiring. See #16.
Rendering and performance findings
Lazy stacks are not lazy
Sources/TUIkit/Views/LazyStacks.swift:77-124,228-275 resolves/renders all children and crops the finished buffer. Offscreen bodies, measurement, rendering, and effect registration still occur. True viewport behavior requires keyed incremental child access, variable-size bookkeeping, overscan, scroll anchors, pinned headers, and defined offscreen lifecycle semantics. This is tracked separately from ForEach correctness in #25.
Frame diffing is not the main complexity source
FrameDiffWriter.computeChangedRows is already linear over rows. The larger avoidable work comes from repeated FrameBuffer array copies, styled-string parsing, full-screen formatting before diff output, and periodic full renders while idle. Optimizing the old String model separately would create throwaway work; the cell/surface migration in #11 must come first.
Text editing needs one cell index model
TextField/SecureField transformations repeatedly bridge character indices and terminal widths. This risks quadratic behavior and cursor/selection errors for combining marks, emoji, and wide glyphs. Controls should consume the same grapheme/cell mapping as Text and FrameBuffer.
Terminal write errors need explicit handling
Terminal write loops can stop on EINTR or another error without completing or surfacing the failure. The async event/terminal boundary must retry interrupted writes and report nonrecoverable errors with enough context for tests and cleanup.
Current performance gates are not trustworthy
- Absolute
Datethresholds run on shared CI. - Several tests only print statistics.
- Full AppRunner/RenderLoop/commit paths are not represented.
- ForEach benchmarks currently render no content.
- Documentation claims about write reduction/syscall count are not tied to reproducible fixtures.
Correctness and deterministic work counters must precede release-mode median/P95 baselines. See #38.
SwiftUI API compatibility
Inventory result
| Classification | Public declarations |
|---|---|
| Atomic fragments matching after module normalization | 68 |
| Same owner/title, different signature | 196 |
| Extra members on SwiftUI-named types | 221 |
| TUI-specific types or global APIs | 939 |
| Total | 1,424 |
The 68 matching fragments include isolated cases such as basic EnvironmentKey/PreferenceKey requirements, some Alignment constants, ButtonRole cases, visibility values, and simple projections. They do not establish whole-type parity.
Representative divergences
| Area | Current TUIkit shape | SwiftUI-compatible direction |
|---|---|---|
FocusState | Nongeneric class | Generic DynamicProperty property wrapper with projected binding |
Button | Nongeneric, String label, configuration in initializer | Button<Label: View>, role/action/label builders, Environment/styles |
ButtonStyle | Concrete struct | Externally conformable protocol |
ViewModifier | Buffer mutation methods | body(content:) and ModifiedContent |
ViewBuilder | Missing empty block/ContentBuilder; differing optional/availability forms | Exact Swift-6.0-compatible builder surface |
Binding | No DynamicProperty/dynamic-member/transaction collection surface | Exact initializer/projection/conformance families |
AppStorage | Generic Codable wrapper and global backend | SwiftUI overload families on ordered injected persistence |
ForEach | Body Never, no dynamic-content contract | Keyed DynamicViewContent-like behavior and exact signatures |
List | Extra Footer generic and terminal initializer options | SwiftUI generic base; TUI additions as modifiers/styles |
Table | Table<Value> | Current rows/columns/content generic model |
Menu | Nongeneric [MenuItem] controller | Generic Label/Content container |
Alert | Generic View with terminal styling | Binding-driven presentation modifiers; TUI styling additive |
| Layout spacing | Public Int | Public CGFloat?; internal deterministic cell quantization |
| Alignment/Edge | Closed enums and Edge OptionSet | Extensible alignment values; Edge with nested Set |
| Shared modifiers | Missing disabled, foregroundColor, tag/id, shared submit | Environment-driven exact modifier families |
Scene | No composable modifiers | ModifiedScene/environment propagation |
The detailed public core migration is tracked in #17 through #24, with content/presentation in #28.
Swift 6.0 and current SwiftUI isolation
swift-tools-version: 6.0remains normative.sending,@isolated(any), andany TaskExecutorcompile with Swift 6.0.3 and can be used for current task closure semantics.- Current SDK type-level
nonisolatedstarts in Swift 6.1 and does not compile with Swift 6.0.3. Under the hard compiler floor, use representable type/member isolation and record this as a manifest exception. - Swift 6.2 default-isolation changes and
@concurrentmust not leak into the Swift 6.0 base. - In the current Apple SwiftUI SDK,
ContentBuilderis an alias to ViewBuilder and is representable under Swift 6.0. TUIkit does not currently declare that alias; adding it belongs to #17. - The newer
@Statemacro layer needs a separate compiler-floor decision. Exact property-wrapper semantics come first.
Inclusion rule for a TUI
Include a SwiftUI API when at least one condition applies:
- It models structure, state, data flow, focus, navigation, keyboard input, text layout, lifecycle, or semantic actions independently of pixels.
- It can be mapped deterministically to terminal cells.
- It is needed to port normal SwiftUI view hierarchies.
- TUIkit already has equivalent behavior under an incompatible name.
Keep the public signature exact when only presentation differs. For example, a CGFloat spacing remains public and is quantized internally to cells.
TUI-specific APIs remain valid when they use clearly TUI-specific names, do not redefine SwiftUI names/overloads incompatibly, and prefer additive modifiers/styles over extra parameters in SwiftUI initializers.
Terminal-meaningful missing surface
This table is a reviewed set of known gaps, not yet an exhaustive proof. Issue #7 must enumerate the complete current SwiftUI surface, decide TUI relevance for every API, and assign every included API to exactly one issue.
| Group | Missing or incomplete APIs | Tracking |
|---|---|---|
| Scrolling/navigation | ScrollView/Reader/position, NavigationStack/Link/Path/destination, TabView | #31 |
| Structured UI | Form, Group, GroupBox, DisclosureGroup, OutlineGroup | #32 |
| Grids/input | Grid/GridRow, lazy grids/GridItem, Label, LabeledContent, ControlGroup, Gauge, TextEditor | #32 |
| Selection | Picker and terminal-native styles | #23 |
| Data flow/focus | DynamicProperty, Bindable, compatible FocusState/focused/defaultFocus | #18, #22 |
| Data ownership/storage | ObservedObject, StateObject, EnvironmentObject, SceneStorage | #18 |
| Focused values | FocusedValue, FocusedBinding and focus-scope propagation | #22 |
| Shared modifiers | disabled/isEnabled, foregroundColor/style, id/tag, onSubmit/submitLabel, task(id:) | #21 |
| Content/presentation | Font/text layout, AsyncImage, alert/sheet/confirmation/dismiss | #28 |
| App semantics | ScenePhase, OpenURLAction, DismissAction, keyboardShortcut, Commands | #19, #33, #5 |
| Links/toolbars | Link, ToolbarContent, ToolbarItem, applicable placements mapped to terminal regions | #33, #5 |
| Semantic interaction | Accessibility metadata, searchable, refreshable | #33 |
| Time/animation | Animation, Transaction, transitions, TimelineView | #34 |
Explicitly excluded unless a terminal model is defined
- Canvas, Path, Shape rasterization, Metal, shaders, materials, glass, and GPU effects.
- 2D/3D pixel transforms, blur, mask, and compositor-only effects.
- UIKit/AppKit/WatchKit representables.
- visionOS volumes and immersive spaces.
- Haptics, Pencil, and touch/spatial-only gestures.
- Safe-area, home-indicator, window-chrome, and screen-coordinate concepts.
- Camera, Photos, Maps, Quick Look, and other unrelated system/domain views.
- Multiwindow/DocumentGroup while TUIkit intentionally owns one terminal session.
- Platform pasteboard drag/drop until a real portable terminal contract exists.
Code quality, reuse, and public surface
Public internals
RenderContext, RenderCache, StateStorage, StateBox, hydration types, child traversal, and preference storage are public largely because targets/tests need access. Swift 6.0 package access can retain modularity without promising these internals as SDK API. A symbol-by-symbol migration and major-version guide is tracked in #35.
Reuse opportunities
- List and Table both configure selection/viewport behavior around an existing shared handler but still duplicate container setup. Consolidate while migrating keyed collections and public container signatures, not as an isolated code-moving refactor.
- Runtime invalidation, cache/service ownership, terminal/session injection, and standalone rendering share one root contract and must not become parallel competing containers.
- Pulse, cursor, spinner, and notification deadlines should use one scheduler.
- Text, TextField, FrameBuffer, clipping, and diffing must share one grapheme/cell representation.
- Compile/runtime documentation fixtures should feed both compatibility checks and DocC examples.
Confirmed dead code
The following production declarations have no current call site:
EdgeInsets.containerDefaultEdgeInsets.dialogDefaultFocusManager.hasSectionsFocusSection.clearFocusablesBorderRenderer.focusIndicatorWidthSplitViewPage.visibilityLabel
Removal, including now-empty files/registrations, is tracked in #36. MockTerminal is intentionally excluded from this list because it is required by missing integration coverage.
Testing and CI findings
Aggregate-only tests hide module leaks
Package.swift defines one TUIkitTests target against the umbrella product. All 93 Swift test files import the aggregate rather than Core, Styling, View, or Image in isolation. This permits accidental upward imports and public visibility added only for tests. Split targets and boundary checks are tracked in #29.
Linux skips central State coverage
Tests/TUIkitTests/StatePropertyTests.swift:17-21 disables the State wrapper suite on Linux. The package builds/tests there, but one of the most important runtime contracts is not actually cross-platform tested.
Behavioral gaps
Required end-to-end characterization includes:
- Two per-app runtimes in one process with no state/service leakage.
- MainActor
.task,task(id:), appearance, cancellation, and removal. - ForEach output and keyed insert/delete/reorder state.
- Measure/render/commit effect traces.
- Cached effect-bearing subtree equivalence.
- Ordered AppStorage persistence and restart.
- Unicode cell/golden rendering and terminal control sanitization.
- Async images with injected transport, cache, cancellation, limits, and stale responses.
- Idle wakeups and visible-only animations using a fake clock.
CI quality policy
The target state is:
- Pin Swift 6.0 and one SwiftLint version for both macOS and Linux.
- Treat compiler warnings as errors for build and tests.
- Run SwiftLint strict.
- Build DocC for all public products and fail on warnings.
- Assert correct output before performance.
- Use one badge workflow and one authoritative test-count definition.
- Keep performance baselines separate from deterministic correctness gates.
This is tracked in #6.
Documentation findings
The documentation has useful breadth but is not an executable developer contract:
- Pure Swift/no-C claims conflict with
CSTBImage. - README and DocC use missing
.foregroundColor. - LazyStack pages use missing ScrollView.
- Getting Started declares an outdated deployment target.
- The documented composite
renderOncecrashes. - AppStorage documentation describes UserDefaults behavior while the implementation defaults to JSON.
- Localization examples use unsupported syntax.
- Architecture pages name or describe outdated APIs and singleton behavior.
- CONTRIBUTING's line-length description differs from
.swiftlint.yml. - Test-count text and badges disagree with
swift test list. - Public submodule documentation is not built comprehensively in CI.
Every implementation issue requires accompanying docs. The final symbol/comment quality sweep, executable code samples, runtime smoke examples, and cross-document truth check are tracked in #37.
Project generator findings
The project-template/tuikit tool is not reliably cross-platform:
- Uninstall assumes
/usr/local/bin. swiftly install latestviolates the Swift 6.0 floor.- Linux download naming assumes Ubuntu artifacts.
- Xcode
defaults, prompts, andopenbehavior run on Linux paths. - Project names are interpolated into Swift identifiers, targets, and paths without a complete validation/derivation model.
- README claims
.swiftpm, SQLite.swift, and a generated scheme while the script uses GRDB and does not generate the other promised artifacts.
The generator needs isolated temporary-directory fixtures that build/test generated projects on both platforms. See #30.
Existing GitHub issues
| Issue | Audit result | Integration |
|---|---|---|
| #1 .task Modifier Does Not Fire | Reproduced; MainActor loop and transient lifecycle identity are root causes | Runtime #8, event loop #9, identity #10, public overloads #21 |
| #2 .palette Modifier Cannot be Applied to WindowGroup | Reproduced at compile time; palette is View-only while WindowGroup is Scene | Scene composition #19 |
| #5 Application Menu System | Fits the terminal-useful SwiftUI scope | Scene #19, actions/shortcuts #33, then Commands implementation in #5 |
The three issues now have priority labels and concise technical replies linking the verified remediation. Delayed replies include a brief apology, use varied wording, and address the reporter directly.
Remediation roadmap
The authoritative dependency and conflict map is #40. The compact execution order is:
GitHub Project state
All 38 open TUIkit Project items have an explicit Project Priority consistent with their labels: critical is P0, high is P1, and medium is P2. Exactly five dependency-safe foundation items are in Ready; the other 33 remain in Backlog:
| Ready order | Issue | Project Priority | Reason |
|---|---|---|---|
| 1 | #6 strict correctness | P0 | Establishes diagnostics and characterization gates |
| 2 | #7 parity manifest | P0 | Governs every public API decision |
| 3 | #8 per-app runtime | P0 | Central architecture root for dependent runtime work |
| 4 | #16 pure Swift decoder | P0 | Independent hard-contract correction |
| 5 | #29 module-isolated tests | P1 | Independent package-boundary foundation |
Dependent #9 and #10 intentionally remain in Backlog until #8 stabilizes the runtime boundary. No implementation, branch, or In progress transition was started.
| Phase | Serial foundation | Parallel work after foundation |
|---|---|---|
| 0 | #6 strict correctness | #7 parity manifest, #16 pure Swift decoder, #29 module tests, #30 generator, #36 dead helpers |
| 1 | #8 per-app runtime | #9 event loop, #10 identity, #11 surface, #15 AppStorage |
| 1 continued | #12 keyed collections after #10 -> #13 render transaction -> #14 cache liveness | Full #11 surface migration and ordered AppStorage integration continue in parallel; #12/#13 only coordinate a minimal render-result boundary |
| 2 | #17 View/builder/modifier core | #18 data flow, #19 Scene, #20 layout |
| 2 continued | #21 shared modifiers -> #22 focus | #23 controls, #24 containers, #27 images, #28 content/presentation |
| 2 final | #25 lazy viewport -> #26 animation scheduler | API-family work with nonoverlapping file ownership |
| 3 | Terminal-useful feature expansion | #31 navigation, #32 structured UI, #33 semantic actions, #34 animation, #5 commands |
| 4 | #35 public surface | #37 documentation, then #38 performance and release-scoped #39 Graphify |
Conflict rules
App.swift, RenderLoop, SignalManager, and terminal session ownership stay with the event-loop package after runtime interfaces settle.- Identity, DynamicProperty hydration, lifecycle, and task slots stay together; API issues add public signatures rather than reimplementing behavior.
- ForEach/List/Table keyed traversal lands before public container migration.
- FrameBuffer, terminal width, ANSI, compositing, and diff adapters stay with the surface package.
- ViewBuilder and ViewModifier changes serialize before data-flow/layout/control branches.
- ForEach correctness precedes lazy viewport work.
- Render transaction precedes cache-liveness optimization.
- Every issue updates its own documentation; the final documentation issue verifies the complete public story.
Standard remediation template
Each implementation issue should be executed with this sequence:
- Confirm dependencies are complete and reserve its listed file ownership.
- Plan small, thematic, independently revertible commits. An issue is not a commit boundary and may contain many commits.
- Keep every commit buildable and green. Do not publish an intentionally failing test commit; use local/known-issue characterization or land the regression test with the smallest fix.
- Separate characterization/fixtures, mechanical moves or renames, semantic changes, and documentation/migration updates whenever each intermediate commit remains green.
- Re-run or add the issue's characterization/compile fixture.
- Implement the smallest architecture change that satisfies the proposed contract.
- Add behavior, cross-platform, concurrency, and negative/error tests.
- Update compatibility manifest, symbol expectations, DocC, README/examples, and migration notes as applicable.
- Run targeted gates during development.
- Before integration, run Swift 6.0 macOS/Linux build and tests, SwiftLint strict, DocC warning gate, and API fixtures.
- Record any unavoidable terminal/compiler divergence in the manifest rather than silently changing a SwiftUI signature.
No issue is complete while any relevant error or warning remains.
Final conclusion
TUIkit has enough implementation depth to justify a systematic migration rather than a rewrite. The exception is the current example application only if a separately approved example-app replacement design requires it; no example code was changed in this audit.
The immediate release blockers are correctness and ownership, not missing visual polish:
- Establish strict executable gates and a compatibility contract.
- Unify the per-app runtime and cooperative event loop.
- Stabilize identity, keyed content, render commit, cache liveness, persistence, and terminal cells.
- Migrate the public core and API families to exact SwiftUI-compatible contracts.
- Add the terminal-meaningful missing SwiftUI surface.
- Close documentation, public-surface, performance, Graphify, and final zero-warning release gates.
At the audited revision, the SDK is buildable and testable, but it is not warning-free, not fully behaviorally correct, and not SwiftUI API-compatible. The GitHub roadmap now makes those gaps actionable in dependency order without requiring agents to infer sequencing or conflicting file ownership.