Devlog — 2026-08-17#

TL;DR#

The week’s headline is refresh-token reliability — a run of deadlocks, races, and rotation bugs across mobile and backend that were behind random logouts and stuck sessions, now fixed end to end. Due dates became timezone-correct across the whole stack: the backend now stores each user’s actual timezone instead of guessing from a midnight timestamp. Goals got period notes decoupled from outcomes (write a note without committing to a review) and the review prompt got unstuck from being silently unreachable. Also landed: calendar-week task grouping on mobile and webapp, a batch of webapp dashboard polish, URGENT priority retired, and a run of iOS stability fixes. Four releases (1.10.0 → 1.10.3).

User-facing features#

  • Goals: period notes now live independent of goals and outcomes — write a note on any period without committing to Achieved/Dropped/Carried, and edit it any time, even after review. Ended periods lock their goal from further edits, and the review prompt — previously silently unreachable on the day it should appear, e.g. Sunday for a weekly period — now actually shows up. Outcome selection is redone as inline text buttons that show which one is picked, finished tasks in the horizon detail screen render as proper task cards instead of bullet text, and the redundant activity list was dropped from planner cards. Webapp: the goal input on the planner card no longer renders blank before the page finishes loading, and a formatted goal no longer shows twice (raw markers above, rendered text below) — it now swaps between preview and edit the same way task descriptions already do.
  • “This week” task grouping now follows the actual calendar week (Monday–Sunday) instead of a rolling 7-day window that bled into next week starting Wednesday — fixed on both mobile and webapp, with mobile adding a distinct “Next week” group.
  • Due dates are timezone-correct end to end: the backend now stores each user’s timezone plus a date-only flag instead of inferring intent from a midnight timestamp, mobile stores date-only deadlines explicitly rather than by local midnight, and the backend’s calendar, stats, sessions, and dashboard views all render by the user’s zone instead of the server’s (UTC). Fixed along the way: a task due today no longer shows as overdue starting at midnight, recurring tasks keep their time-of-day when advancing to the next occurrence, and task-edit fields (deadline, activity, priority, energy, recurrence, tags) now persist immediately instead of silently reverting.
  • Webapp dashboard: the “tasks left today” count now reflects today’s workload instead of the whole backlog, long task descriptions no longer force horizontal scrolling, the Today session chart no longer hides sessions outside 8am–6pm, and settings changes (theme, timer duration) apply live instead of needing a manual reload.
  • URGENT priority retired — priority is now importance only (Low/Medium/High), with urgency expressed by the due date instead; existing URGENT tasks were migrated to High.
  • Webapp sign-up form fixed: the submit button was invisible (white text on white background from an undefined Tailwind token), password validation rejected valid non-ASCII passwords while allowing ones too long for bcrypt, and “email already registered” now shows its own message instead of a generic failure.
  • Fixed a literal backslash showing in some UI strings (e.g. “TODAY'S TOAD”) caused by an Android-only escape sequence that Compose Resources doesn’t understand.

Technical improvements#

  • Refresh-token reliability, the week’s big push: fixed a reentrant-refresh deadlock that could permanently hang every request after a single 401 on the refresh call itself, a race where a caller could pick up a token that had already been rotated away, and a burst-of-401s case where the losers of a refresh race never retried at all. On the backend, refresh failures now split into distinct error codes (rotated / revoked / device not registered) instead of one generic 401, and rotation is now atomic against request cancellation so a cancelled request can’t leave the client holding a token the server already discarded. Net effect: this week’s random-logout and stuck-session reports should be resolved.
  • iOS stability: fixed an app-launch crash on upgrade (nested DB transaction during migration, plus a separate mapper bug that silently dropped the deadline time-of-day flag before it reached the edit screen), a crash from a stale dependency-injection argument left over from a goals refactor, silent logouts caused by the device ID rotating on reinstall, device logs that were unreadable without a debugger attached, and a “Log in” snackbar action that did nothing.
  • Sync reliability: delete tombstones that replayed forever because the server only echoes a delete once are now purged correctly on both sides; a startup cleanup sweep no longer reports local-only deletes to the server as if they were real; sync flushes are now serialized instead of firing one redundant flush per queued event; a stats-snapshot mismatch was re-triggering ~40 identical re-fetch calls per app start instead of one.
  • Telemetry visibility: a gap where anonymous config requests silently dropped tracing for a client’s whole session (unnoticed for weeks on iOS) now logs a signal, and duplicate-registration attempts no longer inflate the ops error dashboard as unexpected errors.