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.