Devlog #13
Table of Contents
Devlog — 2026-08-24#
TL;DR#
Mobile shipped a new Today dashboard as the app’s start destination, replacing the old Pomodoro landing screen, then spent the rest of the week stabilizing it (a squash-merge that quietly dropped its restyle, an iOS-only launch crash, a laggy long-press). Refresh-token rotation kept getting chased down — a second silent rotation per launch, a WebSocket reconnect path forcing rotations it shouldn’t, and a fix for tokens lost when the server rotates but the client’s write never lands. Sync got another round of self-inflicted-request fixes (duplicate session writes, redundant pull storms, a seconds-vs-milliseconds bug). On the backend, it turned out verification and password-reset emails had been silently failing to deliver for weeks — traced and fixed, alongside stale push-notification targets from reinstalls, an archived-tasks display bug, and a CI hardening pass. One release (1.10.4).
User-facing features#
- New Today dashboard tab: a single home screen combining today’s task load, the running session, streak, a daily prompt, “up next,” and a link to stats — now the app’s start destination in place of the Pomodoro screen. A squash-merge on the way in had quietly reverted the intended flat-card visual style back to the old shadowed look, which is now restored, and a long-press stop button that only registered on release (not on timeout) now fires promptly. Also fixed: an iOS-only crash opening the new tab.
- Resuming a paused timer now correctly registers as “resumed” instead of silently creating a second session behind the scenes — this was also quietly suppressing the resume haptic and checkpoint save, which existed in code but were unreachable.
- Verification and password-reset emails were being silently dropped for weeks by the mail relay (a non-ASCII character in the subject line, with no bounce reported) — fixed, along with sign-ups that silently created duplicate accounts for the same address in different letter casing, and emailed links that pointed at the marketing site instead of the app.
- Archived tasks were showing up on the board and dashboard while not appearing on the dedicated archived view — a filter that should have excluded them was never actually checking the archived flag.
Technical improvements#
- Refresh-token rotation, still the ongoing theme: fixed a second, unnecessary rotation firing on every app launch; a WebSocket reconnect path that forced a full token rotation when only the short-lived access token (not the refresh token) was stale; and — mainly hitting iOS background refresh — a case where a rotation completes on the server but the client’s confirmation never lands, permanently stranding the session until a manual login. That last one now replays safely instead.
- Sync: fixed the device’s own writes triggering a redundant full resync of itself, a race that could create two session records for one timer start, and a bug that wrote seconds into a milliseconds field (undercounting session duration). Sync now also tolerates a task with no status from the server instead of aborting the entire pending update batch.
- Backend push hygiene: device rows left behind by reinstalls (which rotate the local device ID) no longer stay forever as push targets — a cleanup job now retires them after 30 days idle, and a backlog of leftover simulator/emulator rows was cleared out. Push-related logging also got much quieter, moving from one line per device to one per batch.
- CI reliability: the end-to-end and accessibility test jobs kept hanging on a slow package-mirror install; that install step was removed outright rather than retried, and every job now has a runtime cap as a backstop against future hangs.
- Local dev: spinning up the backend locally now needs one command — database, migrations, and a fully seeded test account with weeks of realistic history, instead of manual setup before anything meaningful would render.