Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    entaku0818

    voilog-tca-development

    entaku0818/voilog-tca-development
    Coding
    30
    2 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Guide TCA (The Composable Architecture) development for VoiLog iOS app including @ObservableState, @ViewAction, delegate patterns, and TestStore testing.

    SKILL.md

    VoiLog TCA Development

    Quick Start

    VoiLog uses modern TCA patterns with @ObservableState and @ViewAction.

    Feature Template Location

    Start with: /ios/VoiLog/Template/FeatureTemplate.swift

    Modern Architecture Files

    Primary locations (PREFERRED for modifications):

    • Recording: /ios/VoiLog/Recording/RecordingFeature.swift
    • Playback: /ios/VoiLog/Playback/PlaybackFeature.swift
    • App Coordinator: /ios/VoiLog/DebugMode/VoiceAppFeature.swift (VoiceAppFeature)

    Legacy location (AVOID modifications):

    • /ios/VoiLog/Voice/ - Legacy production code

    Instructions

    Step 1: Choose the Right Architecture

    For new features or modifications:

    1. Use Modern Architecture (/ios/VoiLog/Recording, /Playback, /DebugMode)
    2. Follow the tab-based pattern with VoiceAppFeature
    3. Implement delegate pattern for inter-feature communication

    Never modify:

    • Files in /ios/VoiLog/Voice/ directory (Legacy)

    Step 2: Create New Feature

    @Reducer
    struct MyFeature {
      @ObservableState
      struct State: Equatable {
        var isLoading = false
        // Add your state properties
      }
    
      enum Action: ViewAction {
        case view(View)
        case delegate(Delegate)
    
        enum View {
          case onAppear
          case buttonTapped
        }
    
        enum Delegate {
          case completed(Result)
        }
      }
    
      var body: some ReducerOf<Self> {
        Reduce { state, action in
          switch action {
          case .view(.onAppear):
            // Implementation
            return .none
    
          case .view(.buttonTapped):
            // Send delegate action to parent
            return .send(.delegate(.completed(.success)))
    
          case .delegate:
            return .none
          }
        }
      }
    }
    

    Step 3: Integrate with VoiceAppFeature

    For features that need tab switching:

    // In VoiceAppFeature
    case .recording(.delegate(.recordingCompleted)):
      state.selectedTab = 1 // Switch to playback tab
      return .send(.playback(.reloadData))
    

    Step 4: Write Tests

    See "Testing Patterns" section below for comprehensive test examples.

    Testing Patterns

    Basic TestStore Structure

    import ComposableArchitecture
    import Testing
    
    @Test
    func testBasicAction() async {
      let store = TestStore(initialState: MyFeature.State()) {
        MyFeature()
      }
    
      // Send action and verify state changes
      await store.send(.view(.buttonTapped)) {
        $0.isLoading = true
      }
    
      // Verify delegate action received
      await store.receive(.delegate(.completed))
    }
    

    Pattern 1: ViewAction → State Change

    @Test
    func testStateChange() async {
      let store = TestStore(initialState: RecordingFeature.State()) {
        RecordingFeature()
      }
    
      await store.send(.view(.recordButtonTapped)) {
        $0.recordingState = .recording
        $0.isRecordButtonEnabled = false
      }
    }
    

    Pattern 2: ViewAction → Delegate Notification

    @Test
    func testDelegateNotification() async {
      let store = TestStore(
        initialState: RecordingFeature.State(recordingState: .recording)
      ) {
        RecordingFeature()
      }
    
      await store.send(.view(.stopButtonTapped)) {
        $0.recordingState = .stopped
      }
    
      await store.receive(.delegate(.recordingCompleted))
    }
    

    Pattern 3: Testing with Dependencies

    @Test
    func testWithRepository() async {
      let mockMemo = VoiceMemo(
        id: UUID(),
        title: "Test Memo",
        duration: 60.0
      )
    
      let store = TestStore(initialState: PlaybackFeature.State()) {
        PlaybackFeature()
      } withDependencies: {
        $0.voiceMemoRepository = .mock(
          fetchAll: { [mockMemo] }
        )
      }
    
      await store.send(.view(.onAppear))
      await store.receive(.memosLoaded([mockMemo])) {
        $0.memos = [mockMemo]
      }
    }
    

    Pattern 4: Async Operations

    @Test
    func testAsyncOperation() async {
      let store = TestStore(initialState: MyFeature.State()) {
        MyFeature()
      }
    
      await store.send(.view(.loadData)) {
        $0.isLoading = true
      }
    
      await store.receive(.dataLoadComplete(result: .success(data))) {
        $0.isLoading = false
        $0.data = data
      }
    }
    

    Pattern 5: Timer and Debounce

    @Test
    func testWithTimer() async {
      let clock = TestClock()
    
      let store = TestStore(initialState: MyFeature.State()) {
        MyFeature()
      } withDependencies: {
        $0.continuousClock = clock
      }
    
      await store.send(.view(.startTimer)) {
        $0.isTimerRunning = true
      }
    
      await clock.advance(by: .seconds(1))
      await store.receive(.timerTicked) {
        $0.elapsedTime = 1
      }
    }
    

    Pattern 6: Error Handling

    @Test
    func testErrorHandling() async {
      let store = TestStore(initialState: MyFeature.State()) {
        MyFeature()
      } withDependencies: {
        $0.voiceMemoRepository = .mock(
          fetchAll: { throw TestError.networkError }
        )
      }
    
      await store.send(.view(.loadData))
      await store.receive(.loadFailed(TestError.networkError)) {
        $0.errorMessage = "Network error"
      }
    }
    

    Test Execution Commands

    Run all tests

    xcodebuild test \
      -project ios/VoiLog.xcodeproj \
      -scheme VoiLogTests \
      -destination 'platform=iOS Simulator,name=iPhone 15'
    

    Run specific test class

    xcodebuild test \
      -project ios/VoiLog.xcodeproj \
      -scheme VoiLogTests \
      -destination 'platform=iOS Simulator,name=iPhone 15' \
      -only-testing:VoiLogTests/PlaylistListFeatureTests
    

    Common Test Mistakes

    Mistake 1: Missing State Verification

    // ❌ Bad: State changes but not verified
    await store.send(.view(.buttonTapped))
    
    // ✅ Good: All state changes verified
    await store.send(.view(.buttonTapped)) {
      $0.isLoading = true
      $0.buttonTitle = "Loading..."
    }
    

    Mistake 2: Missing Action Reception

    // ❌ Bad: Delegate action sent but not verified
    await store.send(.view(.complete))
    
    // ✅ Good: All received actions verified
    await store.send(.view(.complete))
    await store.receive(.delegate(.completed))
    

    Mistake 3: Wrong Order

    // ❌ Bad: Wrong reception order
    await store.receive(.second)
    await store.receive(.first)
    
    // ✅ Good: Correct order
    await store.receive(.first)
    await store.receive(.second)
    

    Common Patterns

    Pattern 1: Repository Integration

    @Dependency(\.voiceMemoRepository) var repository
    
    // In reducer
    case .view(.saveData):
      return .run { [data = state.data] send in
        try await repository.save(data)
        await send(.delegate(.saved))
      }
    

    Pattern 2: Auto Tab Switching

    See /ios/VoiLog/DebugMode/VoiceAppFeature.swift for reference:

    • Recording completion → Auto switch to playback tab
    • Use selectedTab state
    • Send reloadData action to target tab

    Troubleshooting

    Error: State not updating in View

    Cause: Forgot @ObservableState macro Solution: Add @ObservableState to State struct

    Error: Action not recognized

    Cause: Using old WithViewStore pattern Solution: Use @ViewAction pattern instead

    Test Error: "Expected state to change but it did not"

    Cause: State mutation block provided but state didn't change Solution: Either remove the mutation block or ensure state actually changes

    // ❌ Bad: Expects state change but none occurs
    await store.send(.view(.noOp)) {
      $0.value = newValue  // But reducer doesn't change this
    }
    
    // ✅ Good: No mutation block if no state change
    await store.send(.view(.noOp))
    

    Test Error: "Received unexpected action"

    Cause: Reducer sent an action that wasn't verified in test Solution: Add await store.receive() for all emitted actions

    // ❌ Bad: Missing receive
    await store.send(.view(.load))
    // Test fails: Unexpected .loadComplete action
    
    // ✅ Good: Verify all received actions
    await store.send(.view(.load))
    await store.receive(.loadComplete)
    

    Test Error: "TestStore skipped receiving actions"

    Cause: Actions received in wrong order or not at all Solution: Verify actions in the exact order they're emitted

    // ❌ Bad: Wrong order
    await store.receive(.second)
    await store.receive(.first)
    
    // ✅ Good: Correct order
    await store.receive(.first)
    await store.receive(.second)
    

    Test hangs or times out

    Cause: Waiting for an action that never comes Solution:

    1. Verify the reducer actually sends the expected action
    2. Check if async operations completed
    3. Use store.exhaustivity = .off for debugging (not recommended for final tests)

    References

    For detailed examples:

    • Recording Feature: /ios/VoiLog/Recording/RecordingFeature.swift
    • Playback Feature: /ios/VoiLog/Playback/PlaybackFeature.swift
    • App Coordinator: /ios/VoiLog/DebugMode/VoiceAppFeature.swift
    • Official TCA Docs: https://github.com/pointfreeco/swift-composable-architecture
    Recommended Servers
    Vercel Grep
    Vercel Grep
    Databutton
    Databutton
    Repository
    entaku0818/voicememo
    Files