> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/pointfreeco/swift-composable-architecture/llms.txt
> Use this file to discover all available pages before exploring further.

# SyncUps

> A complete app demonstrating navigation, persistence, and speech recognition

The SyncUps example is a complete application that demonstrates advanced TCA patterns including navigation stacks, shared state, persistence, and device capabilities like speech recognition and timers.

## Overview

This example shows how to:

* Build navigation stacks with `NavigationStack`
* Share state across features with `@Shared`
* Persist data automatically
* Integrate with device capabilities (microphone, speech)
* Manage complex feature dependencies
* Handle real-time updates with effects

## Implementation

<CodeGroup>
  ```swift App Feature theme={null}
  import ComposableArchitecture
  import SwiftUI

  @Reducer
  struct AppFeature {
    @Reducer
    enum Path {
      case detail(SyncUpDetail)
      case meeting(Meeting, syncUp: SyncUp)
      case record(RecordMeeting)
    }

    @ObservableState
    struct State: Equatable {
      var path = StackState<Path.State>()
      var syncUpsList = SyncUpsList.State()
    }

    enum Action {
      case path(StackActionOf<Path>)
      case syncUpsList(SyncUpsList.Action)
    }

    @Dependency(\.date.now) var now
    @Dependency(\.uuid) var uuid

    var body: some ReducerOf<Self> {
      Scope(state: \.syncUpsList, action: \.syncUpsList) {
        SyncUpsList()
      }
      Reduce { state, action in
        switch action {
        case .path(.element(_, .detail(.delegate(let delegateAction)))):
          switch delegateAction {
          case .startMeeting(let sharedSyncUp):
            state.path.append(
              .record(RecordMeeting.State(syncUp: sharedSyncUp))
            )
            return .none
          }

        case .path:
          return .none

        case .syncUpsList:
          return .none
        }
      }
      .forEach(\.path, action: \.path)
    }
  }
  ```

  ```swift Models theme={null}
  import IdentifiedCollections
  import SwiftUI
  import Tagged

  struct SyncUp: Equatable, Identifiable, Codable {
    let id: Tagged<Self, UUID>
    var attendees: IdentifiedArrayOf<Attendee> = []
    var duration: Duration = .seconds(60 * 5)
    var meetings: IdentifiedArrayOf<Meeting> = []
    var theme: Theme = .bubblegum
    var title = ""

    var durationPerAttendee: Duration {
      duration / attendees.count
    }
  }

  struct Attendee: Equatable, Identifiable, Codable {
    let id: Tagged<Self, UUID>
    var name = ""
  }

  struct Meeting: Equatable, Identifiable, Codable {
    let id: Tagged<Self, UUID>
    let date: Date
    var transcript: String
  }

  enum Theme: String, CaseIterable, Equatable, Identifiable, Codable {
    case bubblegum
    case buttercup
    case indigo
    case lavender
    case magenta
    case navy
    case orange
    case oxblood
    case periwinkle
    case poppy
    case purple
    case seafoam
    case sky
    case tan
    case teal
    case yellow

    var id: Self { self }

    var accentColor: Color {
      switch self {
      case .bubblegum, .buttercup, .lavender, .orange,
           .periwinkle, .poppy, .seafoam, .sky, .tan, .teal, .yellow:
        return .black
      case .indigo, .magenta, .navy, .oxblood, .purple:
        return .white
      }
    }

    var mainColor: Color { Color(rawValue) }
  }
  ```

  ```swift App View theme={null}
  struct AppView: View {
    @Bindable var store: StoreOf<AppFeature>

    var body: some View {
      NavigationStack(
        path: $store.scope(state: \.path, action: \.path)
      ) {
        SyncUpsListView(
          store: store.scope(
            state: \.syncUpsList,
            action: \.syncUpsList
          )
        )
      } destination: { store in
        switch store.case {
        case .detail(let store):
          SyncUpDetailView(store: store)
        case .meeting(let meeting, let syncUp):
          MeetingView(meeting: meeting, syncUp: syncUp)
        case .record(let store):
          RecordMeetingView(store: store)
        }
      }
    }
  }
  ```

  ```swift Preview theme={null}
  #Preview {
    @Shared(.syncUps) var syncUps = [
      .mock,
      .productMock,
      .engineeringMock,
    ]
    AppView(
      store: Store(initialState: AppFeature.State()) {
        AppFeature()
      }
    )
  }
  ```
</CodeGroup>

## Key Concepts

### Navigation Stacks

The app uses `StackState` and `StackAction` for navigation:

```swift theme={null}
@ObservableState
struct State: Equatable {
  var path = StackState<Path.State>()
  var syncUpsList = SyncUpsList.State()
}

enum Action {
  case path(StackActionOf<Path>)
  case syncUpsList(SyncUpsList.Action)
}
```

Push a screen:

```swift theme={null}
state.path.append(.record(RecordMeeting.State(syncUp: sharedSyncUp)))
```

Pop the current screen:

```swift theme={null}
state.path.removeLast()
```

### Enum-Based Destinations

Destinations are modeled as an enum:

```swift theme={null}
@Reducer
enum Path {
  case detail(SyncUpDetail)
  case meeting(Meeting, syncUp: SyncUp)
  case record(RecordMeeting)
}
```

This provides exhaustive handling and type safety.

### Shared State

Data is shared across features using `@Shared`:

```swift theme={null}
@Shared(.syncUps) var syncUps: IdentifiedArrayOf<SyncUp>
```

Changes automatically:

* Update all observers
* Persist to disk
* Propagate through navigation

### Persistence

Define a shared persistence strategy:

```swift theme={null}
extension PersistenceKey where Self == FileStorageKey<IdentifiedArrayOf<SyncUp>> {
  static var syncUps: Self {
    fileStorage(.documentsDirectory.appending(component: "sync-ups.json"))
  }
}
```

Data automatically loads and saves:

```swift theme={null}
@Shared(.syncUps) var syncUps: IdentifiedArrayOf<SyncUp> = []
// Automatically persisted to sync-ups.json
```

### Device Integration

The app integrates with device capabilities:

#### Speech Recognition

```swift theme={null}
@Dependency(\.speechRecognizer) var speechRecognizer

return .run { send in
  let result = try await speechRecognizer.recognizeAudioFile(url)
  await send(.transcriptUpdated(result))
}
```

#### Timers

```swift theme={null}
@Dependency(\.continuousClock) var clock

return .run { send in
  for await _ in clock.timer(interval: .seconds(1)) {
    await send(.timerTicked)
  }
}
```

#### Audio Recording

```swift theme={null}
@Dependency(\.audioRecorder) var audioRecorder

return .run { send in
  try await audioRecorder.startRecording(url: url)
  await send(.recordingStarted)
}
```

### Delegate Pattern

Child features communicate with parents using delegates:

```swift theme={null}
// Child action
enum Action {
  case delegate(Delegate)

  enum Delegate {
    case startMeeting(Shared<SyncUp>)
  }
}

// Child sends delegate action
return .send(.delegate(.startMeeting(state.$syncUp)))

// Parent observes delegate action
case .path(.element(_, .detail(.delegate(.startMeeting(let sharedSyncUp))))):
  state.path.append(.record(RecordMeeting.State(syncUp: sharedSyncUp)))
  return .none
```

### Form Management

The app handles editing with draft state:

```swift theme={null}
@ObservableState
struct State {
  @Presents var editSyncUp: SyncUpForm.State?
}

// Start editing
state.editSyncUp = SyncUpForm.State(syncUp: state.syncUp)

// Save changes
case .editSyncUp(.presented(.saveButtonTapped)):
  guard let editedSyncUp = state.editSyncUp?.syncUp
  else { return .none }
  state.syncUp = editedSyncUp
  state.editSyncUp = nil
  return .none
```

## Architecture

The app is organized into focused features:

```
AppFeature (Root)
├── SyncUpsList
│   └── SyncUp rows
└── Path (Navigation)
    ├── SyncUpDetail
    │   ├── SyncUpForm (edit)
    │   └── Past meetings
    ├── RecordMeeting
    │   ├── Timer
    │   ├── Speech recognition
    │   └── Attendee tracking
    └── Meeting (past meeting view)
```

Each feature:

* Has its own state and actions
* Manages its own dependencies
* Can be developed and tested independently

## Testing

```swift theme={null}
@Test
func testRecordMeeting() async {
  let clock = TestClock()
  let syncUp = SyncUp(
    id: SyncUp.ID(),
    attendees: [
      Attendee(id: Attendee.ID(), name: "Blob"),
      Attendee(id: Attendee.ID(), name: "Blob Jr"),
    ],
    duration: .seconds(6)
  )

  let store = TestStore(
    initialState: RecordMeeting.State(syncUp: Shared(syncUp))
  ) {
    RecordMeeting()
  } withDependencies: {
    $0.continuousClock = clock
    $0.speechRecognizer.recognizeAudioFile = { _ in "Hello" }
  }

  await store.send(.task)

  await clock.advance(by: .seconds(3))
  await store.receive(\.timerTicked) {
    $0.secondsElapsed = 3
    $0.speakerIndex = 1
  }

  await clock.advance(by: .seconds(3))
  await store.receive(\.timerTicked) {
    $0.secondsElapsed = 6
  }

  await store.receive(\.delegate.saveMeeting)
}
```

## Source Code

View the complete example in the TCA repository:

* [AppFeature.swift](https://github.com/pointfreeco/swift-composable-architecture/blob/main/Examples/SyncUps/SyncUps/AppFeature.swift)
* [Models.swift](https://github.com/pointfreeco/swift-composable-architecture/blob/main/Examples/SyncUps/SyncUps/Models.swift)
* [RecordMeeting.swift](https://github.com/pointfreeco/swift-composable-architecture/blob/main/Examples/SyncUps/SyncUps/RecordMeeting.swift)

## Next Steps

* Learn about [navigation](/guides/navigation) patterns
* Explore [shared state](/guides/sharing-state) in depth
* See [dependencies](/guides/dependencies) for testing
* Read about [effects](/concepts/effects) for async operations
