A tray app that reminds you to drink water — and backs off when you ignore it, instead of nagging you at a fixed interval forever.
There are dozens of open-source water reminder apps already. Most of them share the same handful of problems (confirmed by reading their issue trackers, not guessing):
| Problem, seen in the wild | waterline's answer |
|---|---|
Tray icon randomly doesn't render (stay-hydrated#…, Stretchly Windows theme bug) |
Single dependency (fyne.io/systray) for the tray, no Electron, no custom icon-theme logic |
| Notifications silently stop firing, need an app restart | Every tick is stateless — nothing running that can wedge itself |
| Reminder fires again right after you just logged a drink | Logging resets the countdown immediately, in the same tick |
| Fixed-interval nagging → dismissed → uninstalled | Adaptive interval: 2 ignored reminders in a row push the interval up; a logged drink pulls it back down. See Design |
| Feature request queues full of "add Windows/Mac/Linux support" for apps that shipped one OS first | Built cross-platform from the start; CI builds and tests on Linux, macOS, and Windows on every push |
| Small hobby forks rot — years of unmerged Dependabot bumps, stuck on old Electron | One Go module, two direct dependencies, no node_modules |
| Config buried in a source file you have to edit and rebuild | Interval is a tray-menu click; config/state live in your OS's standard config dir as plain JSON |
go install github.com/vigneshakaviki/waterline/cmd/waterline@latest
Or grab a prebuilt binary from Releases
(Linux/macOS/Windows, built natively per-OS in CI — see .github/workflows/release.yml).
Or build from source:
git clone https://github.com/vigneshakaviki/waterline
cd waterline
go build ./cmd/waterline
Run the binary. A droplet icon appears in your tray/menu bar:
- I drank 💧 — logs a drink, resets the timer, nudges the interval back toward your baseline
- Snooze 10 min — defers the next reminder
- Interval — pick a baseline (30/45/60/90 min)
- Quit
No account, no cloud sync, no telemetry, no ads. State lives entirely on your machine.
The scheduling policy in internal/reminder
is grounded in the Fogg Behavior Model (B=MAP: Behavior = Motivation +
Ability + Prompt): a Prompt that fires when the user has no ability or
motivation to act just gets dismissed — and a dismissed prompt trains the
user to dismiss the next one, which is how notification fatigue actually
happens. Two decisions follow directly:
- Backoff, don't repeat. Two ignored reminders in a row push the interval up (capped at 120 min); a logged drink pulls it back down toward your configured baseline (never below it). The schedule adapts to you instead of nagging on a fixed clock.
- Waking-hours window. Reminders don't fire outside 08:00–22:00 by default — a notification with nothing to catch it is pure noise, not a Signal.
The engine is a pure state machine with no GUI/notification/storage
dependencies (internal/reminder), so the adaptive logic is unit-tested
(go test ./internal/reminder/...) without a display, a tray, or a mock
OS notification API.
cmd/waterline/ tray + notification wiring (fyne.io/systray, gen2brain/beeep)
internal/reminder/ adaptive scheduling engine — pure, unit-tested
internal/config/ JSON config/state persistence (OS-standard config dir)
assets/ embedded tray icon
tools/geniconpng/ regenerates the icon from source (go run tools/geniconpng/main.go)
Issues and PRs welcome. The reminder engine is the part most worth reading before touching anything else — it's ~160 lines and fully covered by tests.
MIT — see LICENSE.