Inspiration
I keep forgetting to drink water - not because I don't care, but because nothing around me ever nudges me at the right moment. My phone sits in a pocket, easy to ignore. But my Apple Watch is always on my wrist, and it already has everything it needs to know when to say something: the time of day, how far behind my goal I am, whether I've had anything to drink in hours. It just never did. WaterTracker is that watch finally speaking up.
What it does
WaterTracker is a standalone watchOS hydration app - no iPhone companion required:
- Home: an animated water-level visualization, a pace status line ("320 mL behind pace" / "On track"), and a small character whose fill level and mood (happy, worried, celebrating) track your progress
- Log: dial an amount with the Digital Crown, pick a beverage type (water, coffee, tea, sports drink, alcohol) - each applies an evidence-based net hydration multiplier, since moderate caffeine doesn't actually dehydrate you the way people assume
- History: every drink today, with swipe-to-delete and tap-to-edit - both correctly adjust your total by the entry's real, multiplier-applied contribution, not just the raw number
- Pace-aware reminders: pre-scheduled for the whole day so they survive even if you never reopen the app, with actionable Log/Snooze buttons right on the notification
- Voice: "Hey Siri, log water in WaterTracker" via native App Intents
- Sip Detection: an experimental, opt-in CoreMotion wrist-gesture classifier that recognizes the raise-dwell-return motion of an actual sip, and rejects eating, waving, and walking - always asks for confirmation, never logs automatically
- Complication: live progress on your watch face with an ambient urgency tint, simplified for Always-On Display
- Settings: shows exactly where your goal came from - a real HealthKit average, the default fallback, or your manual override
- Full accessibility support: VoiceOver, Dynamic Type, Reduce Motion
How we built it
WaterTracker was built with Kiro's CLI as the primary implementation engine, driven non-interactively and orchestrated task-by-task - never as one "generate the app" prompt. Every commit is independently buildable, tracing a real development history rather than one large opaque drop.
Spec-driven throughout, not just at the start: every feature went through .kiro/specs/watertracker-watch-app/{requirements.md, design.md, tasks.md} first - EARS-format requirements with testable acceptance criteria, a design doc explaining components and data flow, and a checkbox task plan - before any implementation code existed. This held through two mid-build product pivots and every later feature (a rigorous sip-gesture classifier, Siri/App Intents voice logging, beverage-type net hydration, Always-On Display support), each one getting its own spec update before the code. The task checkboxes and commit history are the receipt: the spec guided the build the whole way, not once at the beginning and then abandoned.
Steering and agents: .kiro/steering/{product,tech,structure}.md carried persistent product/architecture context into every session. A custom project-local agent (.kiro/agents/watertracker-engineer.json) baked in hard-won conventions - a real XcodeGen cross-target bug and its fix, testing patterns for framework side effects, Digital Crown gotchas - as steering rules so every agent invocation followed them automatically, and wired a working agentSpawn hook that loads real git log/git status output at session start. Several features were built by multiple kiro-cli agents running genuinely concurrently, each with disjoint file ownership to avoid collisions - not simulated parallelism.
Tech stack: Swift, SwiftUI, watchOS 9, HealthKit (read-only), WidgetKit, CoreMotion, App Intents, UserNotifications, XcodeGen, XCTest.
Challenges we ran into
- A real XcodeGen bug: sharing a source file across targets with mismatched reference styles silently corrupted build paths. Fixed by making every target reference shared code the same way.
- A data-loss bug: a notification action was writing to a fresh
HydrationStoreinstance instead of the one bound to the live UI - meaning a later crown-dial log could silently overwrite a notification-logged entry. Caught by an independent code review pass, not by tests. - A sip-detection false-negative bug: the gesture classifier's stillness check would have rejected nearly every real sip, because lifting your wrist to drink naturally produces the same rotation the check misread as "not still." Fixed with a grace window and a pitch-aware stillness definition.
- A reminder-chain regression: reminders were scheduled one at a time, so the chain would silently die if the app was never reopened between notifications - fixed by pre-scheduling a bounded sequence for the whole day.
What's next
Multi-day history and trends, richer per-family complication content (different info per slot size), and validating the sip classifier against a larger, more diverse set of real wrist-motion data.
Built With
- appintents
- coremotions
- healthkit
- kiro
- sirikit
- swift
- swiftui
- usernotifications
- watchos
- widgetkit
- xcodegen
- xctest
Log in or sign up for Devpost to join the conversation.