TUIkit Project Analysis

TUIkit
18 July 2026
Audit date: 2026-07-18
Repository: phranck/TUIkit
Audited revision: local main at d6bb8bb
Remediation tracker: GitHub issue #40
PAP-006

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

  1. Executive summary
  2. Scope and method
  3. Repository baseline
  4. Verification results
  5. What is already solid
  6. Critical findings
  7. Rendering and performance findings
  8. SwiftUI API compatibility
  9. Code quality, reuse, and public surface
  10. Testing and CI findings
  11. Documentation findings
  12. Project generator findings
  13. Existing GitHub issues
  14. Remediation roadmap
  15. Standard remediation template
  16. 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:

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:

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:

  1. SwiftUI API parity and Swift 6 concurrency/isolation.
  2. Runtime, state, code quality, tests, CI, documentation, and generator behavior.
  3. Rendering correctness, Unicode/ANSI behavior, layout, caching, images, and performance.

Each specialist then cross-reviewed another domain. The final issue boundaries reflect that review:

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

Repository baseline

MetricCurrent value
Source Swift files199
Test Swift files93
Source linesapproximately 32,734
Test linesapproximately 19,102
Discovered tests1,170
Discovered suites158
Textual @Test occurrences1,172, including 2 DocC comment examples
Public declaration records in API inventory1,424
Public top-level type names180
Top-level names shared with SwiftUI69
TUI-specific top-level names111
SwiftLint warnings85
Source-located symbol records without doc comments335

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

ModulePublic declarationsNotes
TUIkit836App, scenes, controls, containers, terminal-specific user APIs
TUIkitCore123Environment/preferences, key input, frame buffers, geometry
TUIkitImage51Decode, conversion, loading, caching
TUIkitStyling232Color, palette, appearance, terminal styling
TUIkitView182View 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

GateResultDiagnostic status
macOS swift buildPassNot clean: unhandled DocC catalog warning
macOS swift testPass, 1,170 testsNot clean: compiler warnings in tests
swift test listPass, exactly 1,170README/CI claim 1,172
Linux ./scripts/test-linux.sh linuxPass with Swift 6.0.3State wrapper suite is disabled on Linux; test warnings remain
swiftlint lint --quiet --no-cacheExit 085 warnings because strict mode is not used
DocC generationPassSwiftPM catalog warning plus 3 link warnings
Current remote CIGreenNon-strict gates allow warnings

The passing result is useful but does not satisfy the requested zero-warning SDK goal.

Compiler warnings observed during tests

FileLinesFinding
Tests/TUIkitTests/ProgressViewTests.swift201Deprecated ProgressBarStyle
Tests/TUIkitTests/RenderBottleneckTests.swift56, 62, 68Unused timing values
Tests/TUIkitTests/LifecycleManagerTests.swift218Value written but never read
Tests/TUIkitTests/SecureFieldTests.swift177Value written but never read
Tests/TUIkitTests/FrameDiffWriterTests.swift194Unused writer
Tests/TUIkitTests/StatePropertyTests.swift35Unused initial state value

SwiftLint inventory

RuleWarnings
identifier_name67
prefer_self_in_static_references5
closure_end_indentation4
cyclomatic_complexity4
function_body_length3
superfluous_disable_command1
type_body_length1
Total85

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

What is already solid

Critical findings

1. Runtime ownership and invalidation are split

Evidence

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

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

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

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

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

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

Correctness and deterministic work counters must precede release-mode median/P95 baselines. See #38.

SwiftUI API compatibility

Inventory result

ClassificationPublic declarations
Atomic fragments matching after module normalization68
Same owner/title, different signature196
Extra members on SwiftUI-named types221
TUI-specific types or global APIs939
Total1,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

AreaCurrent TUIkit shapeSwiftUI-compatible direction
FocusStateNongeneric classGeneric DynamicProperty property wrapper with projected binding
ButtonNongeneric, String label, configuration in initializerButton<Label: View>, role/action/label builders, Environment/styles
ButtonStyleConcrete structExternally conformable protocol
ViewModifierBuffer mutation methodsbody(content:) and ModifiedContent
ViewBuilderMissing empty block/ContentBuilder; differing optional/availability formsExact Swift-6.0-compatible builder surface
BindingNo DynamicProperty/dynamic-member/transaction collection surfaceExact initializer/projection/conformance families
AppStorageGeneric Codable wrapper and global backendSwiftUI overload families on ordered injected persistence
ForEachBody Never, no dynamic-content contractKeyed DynamicViewContent-like behavior and exact signatures
ListExtra Footer generic and terminal initializer optionsSwiftUI generic base; TUI additions as modifiers/styles
TableTable<Value>Current rows/columns/content generic model
MenuNongeneric [MenuItem] controllerGeneric Label/Content container
AlertGeneric View with terminal stylingBinding-driven presentation modifiers; TUI styling additive
Layout spacingPublic IntPublic CGFloat?; internal deterministic cell quantization
Alignment/EdgeClosed enums and Edge OptionSetExtensible alignment values; Edge with nested Set
Shared modifiersMissing disabled, foregroundColor, tag/id, shared submitEnvironment-driven exact modifier families
SceneNo composable modifiersModifiedScene/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

Inclusion rule for a TUI

Include a SwiftUI API when at least one condition applies:

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.

GroupMissing or incomplete APIsTracking
Scrolling/navigationScrollView/Reader/position, NavigationStack/Link/Path/destination, TabView#31
Structured UIForm, Group, GroupBox, DisclosureGroup, OutlineGroup#32
Grids/inputGrid/GridRow, lazy grids/GridItem, Label, LabeledContent, ControlGroup, Gauge, TextEditor#32
SelectionPicker and terminal-native styles#23
Data flow/focusDynamicProperty, Bindable, compatible FocusState/focused/defaultFocus#18, #22
Data ownership/storageObservedObject, StateObject, EnvironmentObject, SceneStorage#18
Focused valuesFocusedValue, FocusedBinding and focus-scope propagation#22
Shared modifiersdisabled/isEnabled, foregroundColor/style, id/tag, onSubmit/submitLabel, task(id:)#21
Content/presentationFont/text layout, AsyncImage, alert/sheet/confirmation/dismiss#28
App semanticsScenePhase, OpenURLAction, DismissAction, keyboardShortcut, Commands#19, #33, #5
Links/toolbarsLink, ToolbarContent, ToolbarItem, applicable placements mapped to terminal regions#33, #5
Semantic interactionAccessibility metadata, searchable, refreshable#33
Time/animationAnimation, Transaction, transitions, TimelineView#34

Explicitly excluded unless a terminal model is defined

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

Confirmed dead code

The following production declarations have no current call site:

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:

CI quality policy

The target state is:

  1. Pin Swift 6.0 and one SwiftLint version for both macOS and Linux.
  2. Treat compiler warnings as errors for build and tests.
  3. Run SwiftLint strict.
  4. Build DocC for all public products and fail on warnings.
  5. Assert correct output before performance.
  6. Use one badge workflow and one authoritative test-count definition.
  7. 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:

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:

The generator needs isolated temporary-directory fixtures that build/test generated projects on both platforms. See #30.

Existing GitHub issues

IssueAudit resultIntegration
#1 .task Modifier Does Not FireReproduced; MainActor loop and transient lifecycle identity are root causesRuntime #8, event loop #9, identity #10, public overloads #21
#2 .palette Modifier Cannot be Applied to WindowGroupReproduced at compile time; palette is View-only while WindowGroup is SceneScene composition #19
#5 Application Menu SystemFits the terminal-useful SwiftUI scopeScene #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 orderIssueProject PriorityReason
1#6 strict correctnessP0Establishes diagnostics and characterization gates
2#7 parity manifestP0Governs every public API decision
3#8 per-app runtimeP0Central architecture root for dependent runtime work
4#16 pure Swift decoderP0Independent hard-contract correction
5#29 module-isolated testsP1Independent 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.

PhaseSerial foundationParallel 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 livenessFull #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 schedulerAPI-family work with nonoverlapping file ownership
3Terminal-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

Standard remediation template

Each implementation issue should be executed with this sequence:

  1. Confirm dependencies are complete and reserve its listed file ownership.
  2. Plan small, thematic, independently revertible commits. An issue is not a commit boundary and may contain many commits.
  3. 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.
  4. Separate characterization/fixtures, mechanical moves or renames, semantic changes, and documentation/migration updates whenever each intermediate commit remains green.
  5. Re-run or add the issue's characterization/compile fixture.
  6. Implement the smallest architecture change that satisfies the proposed contract.
  7. Add behavior, cross-platform, concurrency, and negative/error tests.
  8. Update compatibility manifest, symbol expectations, DocC, README/examples, and migration notes as applicable.
  9. Run targeted gates during development.
  10. Before integration, run Swift 6.0 macOS/Linux build and tests, SwiftLint strict, DocC warning gate, and API fixtures.
  11. 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:

  1. Establish strict executable gates and a compatibility contract.
  2. Unify the per-app runtime and cooperative event loop.
  3. Stabilize identity, keyed content, render commit, cache liveness, persistence, and terminal cells.
  4. Migrate the public core and API families to exact SwiftUI-compatible contracts.
  5. Add the terminal-meaningful missing SwiftUI surface.
  6. 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.