This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Ladder iOS Assignment - Implementation of "Feats of Strength" feature for the Ladder fitness app. This is a coding exercise demonstrating iOS development expertise with The Composable Architecture (TCA).
Target: iOS 18.0+ Language: Swift 6.0 Architecture: The Composable Architecture (TCA) v1.23.1 UI Framework: SwiftUI
# Build for simulator
xcodebuild -project Ladder.xcodeproj -scheme Ladder -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build
# Build for device (requires code signing)
xcodebuild -project Ladder.xcodeproj -scheme Ladder -destination 'generic/platform=iOS' build# Run all tests
xcodebuild test -project Ladder.xcodeproj -scheme Ladder -destination 'platform=iOS Simulator,name=iPhone 15 Pro'
# Run specific test target
xcodebuild test -project Ladder.xcodeproj -scheme Ladder -destination 'platform=iOS Simulator,name=iPhone 15 Pro' -only-testing:LadderTests
# Run single test
xcodebuild test -project Ladder.xcodeproj -scheme Ladder -destination 'platform=iOS Simulator,name=iPhone 15 Pro' -only-testing:LadderTests/ClassName/testMethodName# Resolve Swift Package Manager dependencies
xcodebuild -resolvePackageDependencies -project Ladder.xcodeproj
# Update packages to latest versions
# (Edit Package.swift/Xcode SPM settings manually, then resolve)Open Ladder.xcodeproj in Xcode and use:
- ⌘R - Build and run
- ⌘U - Run tests
- ⌘B - Build
- ⇧⌘K - Clean build folder
This project MUST use TCA v1.23.1 for all feature implementation. TCA is already integrated via Swift Package Manager.
-
Every feature is a
@Reducerwith three components:@ObservableStatestruct (data)Actionenum (events)bodyreducer (logic + effects)
-
Unidirectional data flow: Action → Reducer → State → View
-
Effects are explicit: Network, timers, side effects return
Effect<Action> -
Dependencies are injected: Use
@Dependency(\.dependencyName)for testability -
Navigation via state: Optional state drives sheets, alerts, navigation
@Reducer
struct FeatureName {
@ObservableState
struct State: Equatable {
var count: Int = 0
}
enum Action {
case incrementTapped
case decrementTapped
}
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .incrementTapped:
state.count += 1
return .none
case .decrementTapped:
state.count -= 1
return .none
}
}
}
}Timer Effects (for workout tracking):
@Dependency(\.continuousClock) var clock
case .startTimer:
return .run { send in
for await _ in self.clock.timer(interval: .milliseconds(10)) {
await send(.timerTick)
}
}
.cancellable(id: CancelID.timer, cancelInFlight: true)Sheet Presentation (for workout screen):
@ObservableState
struct State {
@Presents var workout: WorkoutFeature.State?
}
enum Action {
case showWorkoutTapped
case workout(PresentationAction<WorkoutFeature.Action>)
}
// In view
.sheet(item: $store.scope(state: \.workout, action: \.workout)) { store in
WorkoutView(store: store)
}📖 Complete TCA Reference: See claudedocs/TCA_Guide.md for comprehensive patterns, timer handling, background state management, testing strategies, and Ladder-specific examples.
Ladder/
├── LadderApp.swift # App entry point
├── ContentView.swift # Root view (starter code)
├── Theme/
│ ├── Color+Ladder.swift # Ladder design system colors
│ └── Font+Ladder.swift # Typography scale
└── (Add TCA features here)
LadderTests/ # Unit tests (TCA TestStore)
LadderUITests/ # UI/integration tests
claudedocs/ # Implementation documentation
├── FeatsOfStrength_Workflow.md # Feature spec & implementation plan
└── TCA_Guide.md # TCA patterns reference
Features: Create feature modules in Ladder/Features/ (e.g., Ladder/Features/Workout/WorkoutFeature.swift)
Shared Models: Ladder/Models/
Dependencies: Ladder/Dependencies/
Views: Co-locate with reducers or Ladder/Features/[FeatureName]/Views/
Accessed via Color.ladderColorName or .ladderColorName (ShapeStyle):
Primary: ladderVolt (#E6FF00), ladderWhite, ladderBlack
Secondary: ladderVermillion (#FF5349), ladderPurple (#B982FF)
Neutral: ladderLightGray, ladderMediumGray, ladderDarkGray, ladderShadowGray, ladderSlateGray, ladderDeepGray
Scales: Each secondary color has 50-950 variants (e.g., ladderVermillion500)
Available for both SwiftUI (Color) and UIKit (UIColor).
Accessed via Font.typeName:
Headings: .h1 (33pt heavy), .h2 (27pt bold), .h3 (21pt bold), .h4 (19pt bold), .h5 (17pt bold)
Body: .ladderBody (15pt medium), .ladderCaption (13pt regular), .ladderFootnote (11pt regular)
The Composable Architecture: v1.23.1 (EXACT version required)
- URL: https://github.com/pointfreeco/swift-composable-architecture
- Includes: ComposableArchitecture, Dependencies, IdentifiedCollections, etc.
Creed-Lite: Ladder's internal package (main branch)
- URL: https://github.com/LadderDev/creed-lite
- Contains:
featsClientdependency for API integration with complete mock data - Documentation: See
claudedocs/CreedLite_Package_Reference.mdfor full API reference
import Creed_Lite
@Reducer
struct ChallengesListFeature {
@Dependency(\.featsClient) var featsClient // Inject dependency
@ObservableState
struct State: Equatable {
var monthlyFeats: MonthlyFeats?
var isLoading: Bool = false
}
enum Action {
case loadChallenges
case challengesLoaded(MonthlyFeats)
}
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .loadChallenges:
state.isLoading = true
return .run { send in
// Fetches 4 challenges with complete mock data
let feats = try await featsClient.listMonthlyFeats()
await send(.challengesLoaded(feats))
}
case .challengesLoaded(let feats):
state.monthlyFeats = feats
state.isLoading = false
return .none
}
}
}
}
// Load leaderboard for a challenge
let featId = Feat.Id("550e8400-e29b-41d4-a716-446655440002")
let leaderboard = try await featsClient.getFeatLeaderboard(featId)
// Returns 25 placements with rankings, names, reps, movement indicatorsAvailable Models:
MonthlyFeats: Title, subtitle, array of 4FeatchallengesFeat: Challenge info (name, description, imageURL, videoURL, completionCount, top3Users, movement)FeatLeaderboard: Challenge name + 25FeatLeaderBoardPlacemententriesFeatLeaderBoardPlacement: Rank, name, reps, avatar, movement indicator (↑/↓/—)
Important: The JSON has duration_seconds but Feat model doesn't expose it. Use extension:
extension Feat {
var durationSeconds: Int {
if name.lowercased().contains("2 min") { return 120 }
if name.lowercased().contains("1 min") { return 60 }
if name.lowercased().contains("3 min") { return 180 }
if name.lowercased().contains("5 min") { return 300 }
return 180
}
}TCA enforces exhaustive testing - you must assert all state changes and received actions.
@Test
func testFeature() async {
let store = TestStore(initialState: Feature.State()) {
Feature()
}
// Send action and assert state change
await store.send(.buttonTapped) {
$0.count = 1 // MUST assert mutation
}
}let clock = TestClock()
let store = TestStore(initialState: Feature.State()) {
Feature()
} withDependencies: {
$0.continuousClock = clock
}
await store.send(.startTimer)
await clock.advance(by: .seconds(1))
await store.receive(\.timerTick) {
$0.elapsedTime = 1.0
}Tests that don't assert state changes will fail - this is intentional and catches bugs.
Tab Bar Configuration: The Ladder app has 4 tabs: Workouts, Chat, Teams, Shortcuts
Assignment Implementation Scope:
- Workouts Tab: FULLY IMPLEMENTED - Contains "Feats of Strength" feature
- Chat Tab: OUT OF SCOPE - Placeholder view only
- Teams Tab: OUT OF SCOPE - Placeholder view only
- Shortcuts Tab: OUT OF SCOPE - Placeholder view only
All work for this assignment is contained exclusively in the Workouts tab. Other tabs are placeholders for future development.
This repository implements the "Feats of Strength" feature in the Workouts tab - a community-driven fitness challenge system with:
- Challenge browsing and detail views
- Real-time workout tracking (timer + rep counter)
- Leaderboard with time filters
- Video demonstration integration
- Workout Timer Feature (most critical - requires accurate timing)
- Leaderboard Feature (social proof, pagination)
- Challenge Detail (video player, metadata)
- Challenge List (discovery, navigation)
- Timer Accuracy: Use
ContinuousClockfor workout timing (notTask.sleep) - Background Handling: Track
scenePhasechanges to preserve workout time when app backgrounds - State-Driven Navigation: Sheets, alerts, and navigation via optional state in reducers
- Offline Resilience: Workouts saved locally, sync when connected
Designs available at Figma file provided by assignment. Refer to these for visual implementation details.
📋 Complete Implementation Spec: See claudedocs/FeatsOfStrength_Workflow.md for:
- Screen-by-screen breakdowns
- Data models
- Phase-by-phase implementation plan
- Technical challenges and solutions
- Testing strategies
This project uses Swift 6 language mode with complete data-race safety checking at compile time.
- Complete Concurrency Checking: Compiler enforces data-race safety with
Sendableconformance - Actor Isolation: Actors provide data-race-free state management for concurrent code
- Async/Await: Modern concurrency model integrated with TCA's effect system
- Sendable Types: All types shared across concurrency boundaries must conform to
Sendable
- State Conformance: All TCA
Statetypes automatically conform toSendable(they're value types) - Effect Closures: TCA effects use
@Sendableclosures for safe concurrent execution - Dependencies: Use
@Dependencyfor thread-safe dependency injection - Clock Dependencies:
ContinuousClockisSendableand safe for timer effects
TCA v1.23.1 is fully compatible with Swift 6. No code changes required for:
@Reducermacros@ObservableStateproperties- Effect closures (already use
@Sendable) - Dependency injection patterns
- SwiftUI for all UI (no UIKit unless necessary)
- TCA reducers for all features (no ViewModels, ObservableObjects)
- Value types for models (structs, not classes)
- Dependency injection via
@Dependency(no singletons) - File organization: Group by feature, not layer
- Naming: Use existing
ladderprefix for colors/fonts - Concurrency: Follow Swift 6 concurrency best practices (actor isolation,
Sendabletypes)
- Timer backgrounding:
Task.sleepstops in background. UseContinuousClock+scenePhasetracking. - TCA effect cancellation: Always cancel long-running effects or you'll leak memory.
- TestStore assertions: Every state change must be asserted or tests fail.
- Navigation state: Use
@Presents var destination: Feature.State?for sheets/navigation, not@State var isPresented: Bool. - Equatable conformance: TCA State requires
Equatable- make sure all nested types conform. - Swift 6 Macro Build: First build may fail with "Unable to find module dependency: SwiftSyntax" errors in macro plugins. This is a known Xcode issue with Swift 5.9 packages + Swift 6 projects. Solution: Build again - macros compile on second attempt. Or clean build folder (
⇧⌘K) and rebuild.
When implementing, demonstrate:
- Clean TCA architecture (proper reducer composition, dependency injection)
- Production-quality code (error handling, edge cases, performance)
- Testing discipline (comprehensive TestStore coverage)
- Polish (animations, haptics, loading states)
- Thoughtful decisions (documented trade-offs, prioritization)
The goal is to showcase iOS engineering excellence and TCA expertise.