Install
$ agentstack add skill-rusel95-ios-agent-skills-tca-swiftui ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
> 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.
TCA SwiftUI Architecture (iOS 16+, TCA 1.7+)
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 — use Reduce { }` for explicit generics (helps Xcode autocomplete)
- Add child features via
Scope,ifLet, orforEachBEFORE parentReduceblock - Create dependencies with
@DependencyClientmacro (dependencies.md) - Wire view: direct
store.propertyaccess,@Bindable var storefor$bindings - Write exhaustive TestStore tests (
testing.md)
Workflow: Decompose a God Reducer
When: A single reducer handles logic for multiple screens, has 800+ lines, or testing is impossible to isolate.
- Identify feature boundaries — each screen = own reducer
- Scan for anti-patterns using detection checklist (
anti-patterns.md) - Create
refactoring/directory with per-feature plan files (refactoring-workflow.md) - Extract child reducers bottom-up: leaf features first, then mid-level, then root
- Use
Scope(state:action:)for always-present children - Use
.ifLet(\.$destination, action: \.destination)for optional children — effects auto-cancel on dismissal - Use
.forEach(\.path, action: \.path)for stack navigation - Convert child-parent communication to delegate actions — parent must NEVER observe child internal actions
- Write non-exhaustive integration tests for cross-feature flows (
testing.md)
Workflow: Migrate Legacy TCA to Modern (1.7+)
When: Modernizing pre-1.7 TCA code that uses WithViewStore, IfLetStore, Environment, etc.
- Ensure TCA 1.4+ with
@Reducermacro on all reducers (prerequisite) - Migrate feature-by-feature, bottom-up (leaf features first) (
migration.md) - Per feature: add
@ObservableStateto State AND update view simultaneously — never one without the other - Replace
@PresentationStatewith@Presents - Replace
WithViewStore(store, observe: { $0 })with directstore.propertyaccess - Replace
IfLetStore/ForEachStore/SwitchStorewith native SwiftUI + store.scope - Replace
NavigationStackStorewithNavigationStack(path: $store.scope(...)) - For iOS 16: wrap views in
WithPerceptionTracking { } - Run existing tests — all must pass before proceeding to next feature
- See full migration checklist and syntax transformations in
migration.md
Code Generation Rules
AI tools consistently generate outdated TCA code. Every output must use MODERN TCA (1.7+). ALWAYS:
- Use
@Reducermacro — never barestruct Feature: Reducerconformance - Use
@ObservableStateon ALL State types — never generate State without it - Define State AND Action INSIDE the
@Reducerstruct body — never in extensions (macros can't see them) - Use
@Reducer enumfor Destination/Path reducers (TCA 1.8+) — eliminates massive boilerplate - Action does NOT need
Equatable— remove it. Why: TCA 1.4+ uses case key paths for cancellation andreceive()matching, making Equatable conformance unnecessary and a maintenance burden - Access store properties directly:
store.count,store.send(.tapped)— NEVER useWithViewStore - Use
@Bindable var storewhen$storebindings are needed - Use
.run { send in }for effects — neverEffectTask,.task { },.fireAndForget { } - Use enum-with-cases for cancel IDs — never empty enum types (pruned in release builds)
- Never capture whole
@ObservableStatein effect closures — extract needed values first - Never do expensive work in reducers — they run on main thread. Offload to
.runeffects.
When working with TCA code, you may encounter cryptic compiler errors. If you fail to fix the same error twice:
- "compiler is unable to type-check this expression": State/Action likely defined in extension instead of inside
@Reducerstruct. Move them inside. - "Circular reference resolving attached macro 'Reducer'": Don't nest
@Reducer struct Yinside extension of another@Reducer struct X. Extract to top level. - Macro + property wrapper conflict: Avoid property wrappers inside
@ObservableStateState. Use@ObservationStateIgnoredas workaround (changes won't trigger re-renders). - "A 'reduce' method should not be defined in a reducer with a 'body'": Never define BOTH
reduce(into:action:)ANDvar bodyin the same reducer. - NavigationStack dismiss fights: Ensure
.navigationDestinationis OUTSIDEForEach/List, not inside.
Modernization Migration Cheat-Sheet
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 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? | 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 | 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 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. |
Dismiss-in-delegate-handler pattern
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 .none
Forgetting 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.
Confidence Checks
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` in `@Presents`, NOT a `Bool showXAlert`
[ ] File size — new files <= 400 lines; oversized files have split task in refactoring/
Companion Skills
| 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 |
References
| 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 |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: rusel95
- Source: rusel95/ios-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.