Devlog #15
Table of Contents
Devlog — 2026-09-07#
TL;DR#
The Log shipped: a free-form timestamped note that stands on its own, no session or task required, complete with sync, a home-screen shortcut, a widget, and the old Sessions tab renamed to reflect it now shows both. Session and timer duration got a real fix for a long-standing inaccuracy — paused time was quietly counting as focus time. Tasks picked up tags as visible chips, a dedicated tags field, and a tidier card layout; Goals got bottom-sheet editing and markdown rendering in notes. On the backend, a big batch of audit fixes landed — a login rate limiter that had been silently inert, spoofable IP headers, sync tombstone bugs that could resurrect deleted items, and a stuck notification retry system. No new release this week.
User-facing features#
- The Log, a new free-form journaling feature: timestamped notes that don’t need a session, task, or activity attached. Ships with local storage, backend sync (create/edit/delete, synced within a day’s fetch window), a merged timeline with sessions, a home-screen “add log entry” shortcut, and a read-only widget showing today’s entries. The old Sessions tab is renamed Log to reflect that it now shows both.
- Session and timer duration accuracy fix: pausing was silently counted as focus time on both the app and backend, inflating the reported length of a session. Paused time is now tracked and subtracted everywhere it’s reported — app, backend, and webapp timer.
- Tasks: tags are now visible as chips on the task card (previously stored but never shown), get their own dedicated input field instead of being scraped from the description text, and the card layout got a cleanup — one consolidated meta row for activity, tags, and stats instead of a scattered set of icons.
- Goals: editing goal text and period notes now opens in a bottom sheet instead of an inline editor that the keyboard used to cover; markdown (bold, lists, quotes) now renders properly in period and review notes instead of showing as raw text.
- Session list: the “add session” button is now a floating action button, matching the Tasks screen, instead of scrolling away with the list.
- Task edit sheet gained an explicit save button; the timer/session sheet now clears its selected task properly rather than holding onto a stale one.
- iOS widget and complication text is now localized instead of English-only.
- Webapp: session times now render in the user’s own timezone instead of UTC (a session started at 12:18 locally was showing as 10:18), and long markdown previews (goal/task descriptions) no longer get clipped.
Technical improvements#
- A batch of security and correctness fixes landed on the backend from an internal audit (roughly 30 items total across a few grouped PRs):
- The login brute-force rate limiter had been effectively disabled in normal use — a routing bug meant one ordinary API call before login silently unlocked the login and admin limits for that caller.
- IP-based rate limiting trusted client-supplied headers that could be spoofed to get a fresh limit bucket on every request; now only a header the hosting provider itself sets is trusted.
- Several sync bugs that could resurrect deleted tasks/activities or make one device replay another’s delete were fixed (missing NULL-safety and missing conditions on tombstone writes).
- The notification delivery retry system had been fully built but never actually wired up — every delivery was marked successful whether or not it went out. It now retries with backoff and gives up after 5 attempts.
- A resubmitted task form (e.g. slow connection, double click) could silently create a duplicate task; fixed by making the create idempotent.
- A race between page-load “catch up” and a just-clicked Start could cancel a timer a moment after starting it; fixed by discarding stale state fetches.
- Database connection pool was capped at 4 connections regardless of load; raised to 20, plus a missing index and a scoped stats query.
- Failed backend requests (htmx) used to render nothing, making a failed save look identical to a no-op; failures now surface as a toast.
- Auth rate limiting and the registration cooldown were only enforced on the REST API, leaving the webapp’s login/register/forgot-password forms unmetered; now consistent across both.
- Mobile: continued sync hardening around the new Log/paused-time work (stopped a pause from writing a bogus session end time, stopped a stop event from clobbering session start/focus data, fixed tag sync round-tripping).