tca-swiftui — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited tca-swiftui (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Approach: Production-First Iterative Refactoring — This skill is built for production enterprise codebases using The Composable Architecture. Architecture changes are delivered through iterative refactoring — small, focused PRs tracked in a refactoring/ directory. AI tools consistently generate outdated TCA code (pre-1.4 patterns, WithViewStore, Environment, Combine Effects). Every rule here exists to prevent those mistakes.Enterprise-grade skill for The Composable Architecture (point-free/swift-composable-architecture). Opinionated: prescribes @Reducer macro, @ObservableState, struct-of-closures dependencies, delegate actions for child-parent communication, and modern navigation via @Presents/StackState. TCA's API changed substantially across versions — AI tools trained on pre-1.7 code consistently generate WithViewStore, IfLetStore, Environment, and other removed patterns. This skill encodes the modern (1.7+) patterns validated against real enterprise codebases.
If the project uses TCA <1.7, consult references/migration.md for pre-macro patterns and incremental upgrade path.View Layer → SwiftUI Views. Direct store access, NO WithViewStore.
Owns store via let/var. Uses @Bindable for $ bindings.
Reducer Layer → @Reducer struct. State + Action + body. Pure logic.
Returns Effect for async work. ALWAYS runs on main thread.
Effect Layer → .run { send in } for async. .send for sync delegate actions.
Cancellable, mergeable, concatenatable.
Dependency Layer → @DependencyClient structs. Struct-of-closures, NOT protocols.
liveValue (app) / testValue (auto-unimplemented) / previewValue.Does this component have its own screen/view?
├── YES → Own @Reducer struct
└── NO → Does it have business logic you want to unit test?
├── YES → Own @Reducer struct, compose via Scope
└── NO → Is its state optional (not always visible)?
├── YES → Own @Reducer, use ifLet (avoids unnecessary work)
└── NO → Keep logic in parent reducerWhat type of navigation?
├── Modal (sheet/alert/dialog/fullScreenCover)?
│ → Tree-based: @Presents + PresentationAction + ifLet
├── Single drill-down?
│ → Tree-based: optional state + navigationDestination
├── Multi-level push (NavigationStack)?
│ → Stack-based: StackState + StackAction + forEach
├── Deep linking is critical?
│ → Stack-based (easy to construct state array)
└── Modals FROM within a NavigationStack?
→ BOTH: stack for push nav, tree for sheets/alertsChild needs to tell parent something happened?
├── Use delegate action: case delegate(DelegateAction)
│ Parent observes ONLY .delegate cases, never child internals
│
Child needs to share logic between action cases?
├── Use mutating func on State or private helper method
│ NEVER send an action to share logic — it traverses the entire reducer tree unnecessarily,
│ creating hidden coupling and making performance profiling difficult
│
Sibling reducers need to communicate?
└── Delegate up to parent, parent coordinates
NEVER send actions between siblings directly — siblings should not know about each otherWhen: Building a new screen or self-contained feature from scratch.
@ObservableState struct inside @Reducer — conform to Equatable@Reducer — NO Equatable conformance needed (TCA 1.4+)view(ViewAction), internal(InternalAction), delegate(DelegateAction) (rules.md)var body: some ReducerOf<Self> — use Reduce<State, Action> { } for explicit generics (helps Xcode autocomplete)Scope, ifLet, or forEach BEFORE parent Reduce block@DependencyClient macro (dependencies.md)store.property access, @Bindable var store for $ bindingstesting.md)When: A single reducer handles logic for multiple screens, has 800+ lines, or testing is impossible to isolate.
anti-patterns.md)refactoring/ directory with per-feature plan files (refactoring-workflow.md)Scope(state:action:) for always-present children.ifLet(\.$destination, action: \.destination) for optional children — effects auto-cancel on dismissal.forEach(\.path, action: \.path) for stack navigationtesting.md)When: Modernizing pre-1.7 TCA code that uses WithViewStore, IfLetStore, Environment, etc.
@Reducer macro on all reducers (prerequisite)migration.md)@ObservableState to State AND update view simultaneously — never one without the other@PresentationState with @PresentsWithViewStore(store, observe: { $0 }) with direct store.property accessIfLetStore/ForEachStore/SwitchStore with native SwiftUI + store.scopeNavigationStackStore with NavigationStack(path: $store.scope(...))WithPerceptionTracking { }migration.md<critical_rules> AI tools consistently generate outdated TCA code. Every output must use MODERN TCA (1.7+). ALWAYS:
@Reducer macro — never bare struct Feature: Reducer conformance@ObservableState on ALL State types — never generate State without it@Reducer struct body — never in extensions (macros can't see them)@Reducer enum for Destination/Path reducers (TCA 1.8+) — eliminates massive boilerplateEquatable — remove it. Why: TCA 1.4+ uses case key paths for cancellation and receive() matching, making Equatable conformance unnecessary and a maintenance burdenstore.count, store.send(.tapped) — NEVER use WithViewStore@Bindable var store when $store bindings are needed.run { send in } for effects — never EffectTask, .task { }, .fireAndForget { }@ObservableState in effect closures — extract needed values first.run effects.</critical_rules>
<fallback_strategies> When working with TCA code, you may encounter cryptic compiler errors. If you fail to fix the same error twice:
@Reducer struct. Move them inside.@Reducer struct Y inside extension of another @Reducer struct X. Extract to top level.@ObservableState State. Use @ObservationStateIgnored as workaround (changes won't trigger re-renders).reduce(into:action:) AND var body in the same reducer..navigationDestination is OUTSIDE ForEach/List, not inside.</fallback_strategies>
When migrating legacy TCA code or writing new features, these 7 tokens come up repeatedly. They are NOT optional — AI tools consistently default to the outdated form on the left. Always use the right column.
| Legacy / outdated | Modern (TCA 1.7+, 2026) | Why |
|---|---|---|
environment.mainQueue | @Dependency(\.continuousClock) | mainQueue was the pre-1.0 scheduler injection. In modern TCA, timing is a Clock dependency — override it with ImmediateClock or TestClock in tests. |
AnySchedulerOf<DispatchQueue> in State | @Dependency(\.continuousClock) | Same as above. Schedulers are no longer passed through State — they are dependencies. Use try await clock.sleep(for: .seconds(0.3)) in .run effects. |
@Bindable var store (iOS 17+ only) | iOS 16: @Perception.Bindable var store — iOS 17+: @Bindable var store | @Perception.Bindable is TCA's backport of SwiftUI's @Bindable for iOS 16 because Apple's @Bindable requires iOS 17. Without it, $store.binding fails to compile on iOS 16 targets. |
Bool showLogoutAlert in State | @Presents var alert: AlertState<Action>? | Alerts belong in AlertState, not in booleans — this gives you message text, button state, and action dispatching in one value, and it auto-dismisses through standard @Presents. |
[Product] in State | IdentifiedArrayOf<Product> | Plain arrays force O(n) updates-by-ID and confuse ForEachStore. IdentifiedArrayOf (import IdentifiedCollections) is the canonical TCA collection — constant-time lookup by id, stable animations, required by forEach(\.items, ...). |
var filtered: [T] { state.items.filter { ... } } (computed) | Stored var filtered: IdentifiedArrayOf<T> updated in the reducer | Computed vars on State hide reducer work from TCA's observation system and run on every access. Store the derived value and recompute it in the action that changes the source — it shows up in the action log and is testable. |
.run { try? await api.delete(id) } | .run { send in try await api.delete(id); await send(.deleteSucceeded) } catch: { error, send in await send(.deleteFailed(error)) } | try? inside effects is a silent-failure bug — the UI thinks the delete worked but nothing changed on the server. Use the catch: parameter on .run and emit an explicit failure action. |
When a child feature finishes (save, cancel, delete) and the parent uses @Presents, the parent MUST dismiss in the same handler that processes the delegate action:
case .destination(.presented(.editor(.delegate(.saved(let item))))):
state.items.append(item)
state.destination = nil // ← REQUIRED. Without this, the sheet stays open.
return .noneForgetting state.destination = nil is the #1 TCA navigation bug — the save succeeds but the sheet never dismisses. For an alternative, child features can use @Dependency(\.dismiss) and call await dismiss() inside their own effects.
unimplemented(placeholder:) for non-Void test dependencies@DependencyClient's testValue auto-generates unimplemented(...) for () -> Void closures, but returning non-Void requires a placeholder. Without it, the test crashes with "dependency has no implementation":
@DependencyClient
struct ProfileClient {
var fetch: @Sendable (User.ID) async throws -> Profile = {
_ in unimplemented("ProfileClient.fetch", placeholder: Profile.mock)
}
}The placeholder: value is used only when the test does NOT assert against fetch — it prevents the crash while still failing the test if the call is unexpected.
Before finalizing generated or refactored TCA code, verify ALL:
[ ] @Reducer macro — present on all feature structs
[ ] @ObservableState — present on all State types
[ ] State/Action — defined INSIDE @Reducer struct, not in extensions
[ ] No WithViewStore — direct store access everywhere
[ ] No Equatable on Action — removed (unnecessary since TCA 1.4)
[ ] Delegate actions — child-parent communication uses .delegate pattern
[ ] Effect closures — capture only needed values, not whole state
[ ] Cancel IDs — enum with cases, NOT empty enum types
[ ] Navigation — @Presents for modals, StackState for push nav, never nested NavigationStack
[ ] Dependencies — @DependencyClient struct-of-closures, not protocols
[ ] Tests — TestStore with exhaustive assertions, @MainActor annotated
[ ] Schedulers — `@Dependency(\.continuousClock)`, NOT `mainQueue` or `AnySchedulerOf`
[ ] iOS 16 bindings — `@Perception.Bindable` used instead of `@Bindable` when deployment target is iOS 16
[ ] Collections in State — `IdentifiedArrayOf` used instead of `[T]`
[ ] Derived state — stored property updated in reducer, NOT a computed var on State
[ ] `.run` effects — use `catch:` parameter, never `try?` inside effect closures
[ ] Alert state — `AlertState<Action>` in `@Presents`, NOT a `Bool showXAlert`
[ ] File size — new files <= 400 lines; oversized files have split task in refactoring/| Project need | Companion skill | Apply when |
|---|---|---|
| Swift Concurrency patterns | skills/swift-concurrency/SKILL.md | Writing async effects, actor isolation, Sendable compliance |
| GCD/OperationQueue legacy code | skills/gcd-operations/SKILL.md | Legacy async work before migrating to TCA effects |
| Comprehensive testing guidance | skills/ios-testing/SKILL.md | Advanced testing patterns beyond TCA TestStore |
| Security audit | skills/ios-security/SKILL.md | Auditing Keychain usage, network security in TCA apps |
| Reference | When to Read |
|---|---|
references/rules.md | Do's and Don'ts quick reference: modern TCA patterns and critical anti-patterns |
references/reducer-architecture.md | @Reducer macro rules, feature decomposition, parent-child scoping, state design |
references/effects.md | Effect API (.run, .send, .merge, .cancel), cancellation, long-running effects, anti-patterns |
references/dependencies.md | @DependencyClient, DependencyKey, liveValue/testValue, module boundaries, test guards |
references/navigation.md | Tree-based (@Presents/ifLet), stack-based (StackState/forEach), dismissal, deep linking |
references/testing.md | TestStore exhaustive/non-exhaustive, TestClock, case key paths, per-feature checklist |
references/migration.md | Version progression, per-feature migration checklist, syntax transformations, known issues |
references/anti-patterns.md | AI-specific mistakes, god reducer signs, performance pitfalls, enterprise concerns |
references/performance.md | Action costs, _printChanges, .signpost, scope performance, high-frequency action mitigation |
references/refactoring-workflow.md | refactoring/ directory protocol, per-feature plans, PR sizing, phase ordering |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.