A full-featured standup meeting manager: create and edit standups, record live meetings with a timer and speech-to-text transcript, and review past meeting records. This is the most comprehensive example in the suite, covering nearly every SwiftModel feature.
This is a refactoring of Apple's Scrumdinger sample app, which Point-Free also adapted in both plain vanilla SwiftUI and The Composable Architecture.
Navigation state is a combination of a NavigationStack path and optional sheet/alert destinations, all driven by models:
AppFeature
└── StandupsList (root list)
├── StandupDetail (pushed onto navigation stack)
│ └── RecordMeeting (pushed further)
└── StandupForm (sheet for add/edit)
Child models communicate results back to parents using node.send() / node.forEach(node.event(...)) rather than callbacks or shared references, keeping each feature independently testable.
The RecordMeeting model drives a countdown timer and captures a live speech transcript simultaneously:
func onActivate() {
node.task {
// Timer tick every second
for await _ in node.continuousClock.timer(interval: .seconds(1)) {
timerFired()
}
}
node.task {
// Stream speech recognition results
for await result in try await node.speechClient.startTask(request) {
transcript = result.bestTranscription.formattedString
}
}
}Both tasks run concurrently and are automatically cancelled when the model is deactivated.
The standups list is saved to disk whenever it changes, but writes are debounced to avoid hammering the file system on rapid edits:
node.forEach(Observed(initial: false) { standups }) { standups in
try await node.continuousClock.sleep(for: .seconds(1)) // debounce
try await node.dataManager.save(JSONEncoder().encode(standups), to: .standups)
}Data is loaded on onActivate, with a graceful error path that offers mock data if loading fails.
SpeechClient, DataManager, and ContinuousClock are all injected via the Dependencies library. Tests replace them with controlled fakes, making it possible to test timer-driven and speech-driven behaviour deterministically.
StandupForm is used for both creating new standups and editing existing ones. The same model and view handle both modes — only the initialisation differs.
| Model | Responsibility |
|---|---|
AppFeature |
Root: navigation path, delegates list |
StandupsList |
List of standups, load/save, add/edit destinations |
StandupDetail |
Standup details, past meetings, start recording |
RecordMeeting |
Active session: timer, speaker rotation, speech transcript |
StandupForm |
Reusable create/edit form with attendee management |
Standup |
Core data model (title, attendees, theme, meetings) |
Meeting |
Past meeting record with transcript |
Attendee |
Team member in a standup |