Skip to content

Latest commit

 

History

History
384 lines (302 loc) · 13.5 KB

File metadata and controls

384 lines (302 loc) · 13.5 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

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 & Run Commands

Building

# 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

Running Tests

# 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

Package Resolution

# Resolve Swift Package Manager dependencies
xcodebuild -resolvePackageDependencies -project Ladder.xcodeproj

# Update packages to latest versions
# (Edit Package.swift/Xcode SPM settings manually, then resolve)

Xcode

Open Ladder.xcodeproj in Xcode and use:

  • ⌘R - Build and run
  • ⌘U - Run tests
  • ⌘B - Build
  • ⇧⌘K - Clean build folder

Architecture: The Composable Architecture (TCA)

This project MUST use TCA v1.23.1 for all feature implementation. TCA is already integrated via Swift Package Manager.

Core TCA Principles

  1. Every feature is a @Reducer with three components:

    • @ObservableState struct (data)
    • Action enum (events)
    • body reducer (logic + effects)
  2. Unidirectional data flow: Action → Reducer → State → View

  3. Effects are explicit: Network, timers, side effects return Effect<Action>

  4. Dependencies are injected: Use @Dependency(\.dependencyName) for testability

  5. Navigation via state: Optional state drives sheets, alerts, navigation

TCA Pattern Example

@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
            }
        }
    }
}

Critical TCA Patterns for This Project

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.

Project Structure

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

Where to Add New Code

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/

Design System

Colors

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).

Typography

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)

Dependencies (Swift Package Manager)

The Composable Architecture: v1.23.1 (EXACT version required)

Creed-Lite: Ladder's internal package (main branch)

  • URL: https://github.com/LadderDev/creed-lite
  • Contains: featsClient dependency for API integration with complete mock data
  • Documentation: See claudedocs/CreedLite_Package_Reference.md for full API reference

Quick Reference: Using FeatsClient

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 indicators

Available Models:

  • MonthlyFeats: Title, subtitle, array of 4 Feat challenges
  • Feat: Challenge info (name, description, imageURL, videoURL, completionCount, top3Users, movement)
  • FeatLeaderboard: Challenge name + 25 FeatLeaderBoardPlacement entries
  • FeatLeaderBoardPlacement: 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
    }
}

Testing with TCA

TCA enforces exhaustive testing - you must assert all state changes and received actions.

TestStore Pattern

@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
    }
}

Testing Timers

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.

App Architecture: Tab Bar Structure

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.

Feature Implementation Context

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

Implementation Priorities

  1. Workout Timer Feature (most critical - requires accurate timing)
  2. Leaderboard Feature (social proof, pagination)
  3. Challenge Detail (video player, metadata)
  4. Challenge List (discovery, navigation)

Key Technical Requirements

  • Timer Accuracy: Use ContinuousClock for workout timing (not Task.sleep)
  • Background Handling: Track scenePhase changes 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

Figma Reference

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

Swift 6 Language Mode

This project uses Swift 6 language mode with complete data-race safety checking at compile time.

Key Swift 6 Features

  • Complete Concurrency Checking: Compiler enforces data-race safety with Sendable conformance
  • 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

Swift 6 Best Practices for TCA

  • State Conformance: All TCA State types automatically conform to Sendable (they're value types)
  • Effect Closures: TCA effects use @Sendable closures for safe concurrent execution
  • Dependencies: Use @Dependency for thread-safe dependency injection
  • Clock Dependencies: ContinuousClock is Sendable and safe for timer effects

Migration from Swift 5

TCA v1.23.1 is fully compatible with Swift 6. No code changes required for:

  • @Reducer macros
  • @ObservableState properties
  • Effect closures (already use @Sendable)
  • Dependency injection patterns

Code Style & Conventions

  • 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 ladder prefix for colors/fonts
  • Concurrency: Follow Swift 6 concurrency best practices (actor isolation, Sendable types)

Common Gotchas

  1. Timer backgrounding: Task.sleep stops in background. Use ContinuousClock + scenePhase tracking.
  2. TCA effect cancellation: Always cancel long-running effects or you'll leak memory.
  3. TestStore assertions: Every state change must be asserted or tests fail.
  4. Navigation state: Use @Presents var destination: Feature.State? for sheets/navigation, not @State var isPresented: Bool.
  5. Equatable conformance: TCA State requires Equatable - make sure all nested types conform.
  6. 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.

Assignment Evaluation Criteria

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.