A tea-steeping timer app built as a learning exercise for pointfreeco/swift-dependencies in a vanilla MVVM (@Observable) app — no TCA.
- Xcode 26+
- iOS 17+
- Swift 6.2
- mise for tooling management
This project uses mise to manage tooling versions. After installing mise, run:
mise install
pre-commit install| Tool | Version | Purpose |
|---|---|---|
pre-commit |
latest | Git hook runner (format, lint, scan) |
swiftlint |
latest | Swift linter (also a build tool plugin) |
trufflehog |
latest | Secret / credential leak detection |
Pre-commit runs the following checks on every commit:
| Hook | Version | Purpose |
|---|---|---|
| Sourcery | 2.0.2 | Code generation (AutoKind, AutomaticSettings) |
| SwiftFormat | 0.52.8 | Opinionated Swift code formatting |
| TruffleHog | — | Secret / credential leak detection (via mise) |
Steeped/
├── mise.toml # Tooling versions (pre-commit, swiftlint, trufflehog)
├── .pre-commit-config.yaml # Pre-commit hook definitions
├── .swiftlint.yml # SwiftLint rule configuration
├── .swiftformat # SwiftFormat rule configuration
├── .sourcery.yml # Sourcery code generation config
├── .periphery.yml # Periphery unused code detection config
├── AppFeature.xctestplan # Test plan for AppFeature
├── docs/ # Design documents and specs
└── Packages/ # Swift Package (all feature code lives here)
├── Package.swift
└── Sources/
├── AppFeature/ # Root app view, tab coordination
├── HomeFeature/ # Primary feature module
├── SettingsFeature/ # App settings and acknowledgements
├── SharedModels/ # Data models shared across features
├── SharedViews/ # Reusable UI components
├── PlaybookFeature/ # Playbook (UI storybook) scenarios
└── TestExtensions/ # Shared test helpers (snapshots, diffs)
| Package | Purpose |
|---|---|
| Inject | Hot reload in DEBUG builds |
| SwiftLint | Linting via build tool plugin |
| Difference | Human-readable test failure diffs |
| AcknowList | Third-party acknowledgements screen |
| LifetimeTracker | Memory leak detection in DEBUG builds |
| Roadmap | In-app feature roadmap / voting |
| Playbook (playbook-ios) | Component storybook for UI development |
| AutomaticSettings | Settings screen code generation |
| swift-snapshot-testing | Snapshot tests for SwiftUI views |
- Modular SPM: All feature code is isolated into separate Swift Package targets under
Packages/. The Xcode app target is a thin host. - Feature modules: Each feature (
HomeFeature,SettingsFeature, etc.) owns its view and model. Shared types go inSharedModelsorSharedViews. - Code generation: Sourcery generates boilerplate (AutoKind enums, AutomaticSettings) via pre-commit. Generated files are committed and have a
// Generatedheader. - Swift 6: The package uses
.swiftLanguageModes([.v6])— all targets must be concurrency-safe.
This project follows Conventional Commits.
<type>(<scope>): <short summary>
[optional body]
[optional footer(s)]
| Type | When to use |
|---|---|
feat |
A new feature or user-visible behavior change |
fix |
A bug fix |
refactor |
Code change with no behavior change |
test |
Adding or updating tests |
chore |
Tooling, dependencies, build config |
docs |
Documentation only |
style |
Formatting, whitespace (no logic change) |
perf |
Performance improvement |
ci |
CI/CD configuration |
Use the module name as scope when the change is confined to one target: home, settings, shared-models, app, playbook.
feat(home): add undo support for the last action
fix(shared-models): correct calculation for edge case
chore: bump SwiftLint to 0.59.1
test(home): add snapshot for empty state
docs: update feature spec to v2
Append ! after the type or add BREAKING CHANGE: in the footer:
feat(shared-models)!: rename Item to DetailedItem
BREAKING CHANGE: All references to `Item` must be updated to `DetailedItem`.