Devlog #9
Table of Contents
Devlog — 2026-07-27#
TL;DR#
Big infra week. Stood up a full observability stack (Grafana/Tempo/Loki) and wired distributed tracing end-to-end from a watch tap through the phone to the backend. Webapp got a real accessibility pass (WCAG contrast fixes, CI-gated) and its e2e suite went from fully broken to meaningfully expanded, surfacing and fixing several production bugs along the way. Also killed the client-attestation feature outright — it was never doing anything. Two releases shipped (1.7.0, 1.7.1).
User-facing features#
- Webapp Goals tab reworked to match the mobile Goal Planner: 2×2 horizon quad, finished tasks split out from history, explicit “+ New goal” action.
- Progress ring now sweeps clockwise consistently everywhere — the watch ring was drawing counterclockwise (mirrored) and phone Flow mode wrapped instead of soft-capping at 1h; all surfaces (phone, watch, webapp) now share one fill contract.
- Fixed a cluster of timer/sync bugs: iOS reconnect storm on the sync WebSocket, a remote break broadcast that started a focus session instead of a break, mismatched timer-state fields between client and server, and recurring tasks losing their due time when advanced.
- Snackbar error/info messages are now localized instead of hardcoded English.
- iOS: removed a doubled bottom nav bar that was wasting space and crowding the FAB, fixed edge-to-edge inset spacing, fixed background sound not playing during a running timer.
- Watch: button order/colors now match the phone, complication text is centered, and the watch returns to idle correctly after a break finishes.
- Webapp accessibility: fixed color-contrast and missing-label violations across all 16 scanned routes, in both light and dark mode — dark mode specifically had two real bugs (near-invisible tinted cards, a sync badge stuck visible on plain page loads).
- Backend now keeps one machine always warm so the 24h background jobs (token cleanup, auto-archive, stats snapshots) actually complete instead of being killed mid-cycle by autoscale-to-zero. [
- Android: replaced the device-ID scheme (SSAID) with a persisted UUID.
Technical improvements#
- Stood up a full observability stack: OTel Collector → Loki + Tempo → Grafana on a VPS, behind Caddy with token-gated ingest and login-gated Grafana, real TLS via DNS-01. Wired distributed tracing through Android, iOS, and the Go backend/webapp so one timer action can be traced from watch origin through the phone to the backend request. Native watch-side export is still deferred as a follow-up.
- Continued consolidating iOS/Android-specific code into shared KMP code: timer preferences, feedback UI, test-session creation, and sound/notification/haptic settings are now unified in commonMain instead of duplicated per platform.
- Removed client attestation entirely (Play Integrity on Android; the iOS/desktop/web paths were already no-op stubs). It wasn’t blocking anything and was only producing log noise.
- The e2e test suite had been silently broken for months — an upstream CDN Playwright depended on was retired, so every run failed before a single test executed. Fixed, then extended with 31 new tests plus 7 previously-skipped multi-device/timer-flow tests, closing real gaps in cross-device sync coverage. Writing the new tests surfaced and fixed 4 production bugs: broken browser back/forward navigation, the keyboard-shortcuts overlay not opening, web push subscribe being completely broken in production, and a notification-settings toggle that silently reset the theme.
- Added automated WCAG accessibility scanning (axe-core) to the e2e suite as a report-only baseline, then fixed every flagged violation and flipped CI to fail on regressions.
No backend commits are missing this week — the API repo saw as much activity as mobile, unlike last week’s quiet backend.