OpenTodo 0.2.0

Released on

OpenTodo 0.2.0 is about trying it, using it on a phone and running it without fuss. Every visitor to the public demo now gets a private sandbox. The phone layout has been reworked so menus, pickers and dialogs fit small screens. There is a docs website and a Help view inside the app, which works offline. Archived projects can be restored with one click. Updates roll out without a Stop → Deploy. Rate limits work correctly behind trusted proxies and Cloudflare. The app meets WCAG AA contrast in light and dark, and this release includes several security fixes.

Upgrade notes

  • From 0.1.0, stop once. 0.1.0 locked the data directory exclusively, so a 0.2.0 container cannot start beside it. Plain docker compose pull && docker compose up -d works as before. With Coolify or another orchestrator that starts the new container first, click Stop, then Deploy for this one upgrade (otherwise the deploy fails with "data directory is in use by another opentodo process" and rolls back). From 0.2.0 on, updates roll without downtime. (Docker, Coolify)
  • Image tags. The release publishes ghcr.io/pixari/opentodo:0.2.0 and :0.2 for linux/amd64 and linux/arm64 (:latest follows main). The Coolify template in deploy/coolify/opentodo.yaml now follows :0.2; if you pinned :0.1, change it.
  • Migration 0025 (demo sandbox bookkeeping) is applied automatically at startup. Back up /data before upgrading: there are no down-migrations. No new mandatory environment variables; OPENTODO_TRUSTED_PROXIES, OPENTODO_DEMO_MAX_SANDBOXES and OPENTODO_DEMO_MAX_DB_MB are optional.
  • Behind a CDN or second proxy, set OPENTODO_TRUSTED_PROXIES (for example cloudflare) so rate limits and the audit log see visitors' addresses. (Reverse proxy)

Added

  • User documentation: seventeen task-based guides for people using the app (getting started, installing the app on iOS, Android and desktop, tasks and projects, views, sections and boards, labels and filters, recurring tasks and reminders, time blocking, sharing and comments, notifications, offline and sync, keyboard shortcuts, account security, import and export, trash and history, settings, troubleshooting and FAQ), with light and dark screenshots generated from the demo data (make docs-screenshots). Every page in docs/ has front matter (title, summary, audience, the capabilities it covers, the app screens it explains). (Docs)
  • Docs website at opentodo.pixari.dev/docs, generated from docs/ by make docs into website/docs/: grouped by audience, in-browser search over a prebuilt index (nothing typed leaves the browser), light and dark, readable without JavaScript, "On this page", previous and next, the version described and "Edit this page on GitHub". "Docs" is in the website's navigation.
  • Help inside the app: a Help view with the user guide of the running version, searchable and available offline (a lazily loaded chunk, precached by the service worker; screenshots stay on the website). Open it from the sidebar, Settings, the ? overlay, g then ?, or :help <topic> with Vim keybindings. Settings sections, the share dialog, the Filter view, quick add and the import screen have a Help link that opens the matching section in a side panel (a full-screen sheet on phones) without closing what you were doing.
  • Unarchive projects: Settings → Projects lists your archived projects, with their parent and open task count, and a Restore button that brings one back to the sidebar. Restoring is an ordinary change, so it works offline and syncs to every device; sub-projects stay archived until restored on their own, a project restored by hand is not archived automatically again on that device, and in shared projects only the owner or an admin can restore. Previously only the automatic-archive notice could undo an archive. (Trash and history)
  • npm run docs:check (part of make check and CI) fails on missing front matter, broken links or anchors, images without alt text, capabilities in openspec/specs/ that no page covers, help links to unknown sections, a keyboard guide that differs from the app, and stale generated files. The shortcut table moved to web/src/lib/shortcuts.ts, shared by the ? overlay and the keyboard guide. The CI layout job also runs axe-core on the Help view, the help panel and the docs website, and checks the docs website at 360, 390 and 1280 px with and without JavaScript.

Changed

  • The public demo (OPENTODO_DEMO=true) gives every visitor a private sandbox instead of one shared account: "Try the demo" creates a password-less account with its own copy of the sample data and a non-interactive collaborator, and nobody sees anybody else's changes. Sandboxes are deleted after 2 hours of inactivity, 24 hours after creation and by the nightly reset, which now also runs VACUUM. The published demo password is gone; the first start deletes the old shared demo accounts. auth/status.demo carries a creation token instead of credentials; new route POST /api/v1/demo/sandbox, new error codes demo_expired, demo_limit, demo_busy and demo_token; optional OPENTODO_DEMO_MAX_SANDBOXES and OPENTODO_DEMO_MAX_DB_MB. Migration 0025 adds the sandbox bookkeeping tables. (Public demo)
  • Links in comments and notes render with rel="nofollow ugc noopener noreferrer" on every instance.

Security

  • Demo instances bound abuse: sandbox creation needs a single-use, signed, 10-minute token from the sign-in page and a same-origin request, at most 5 new sandboxes per address per hour (addresses are stored only as salted hashes), a live cap with idle eviction and a database size guard; each sandbox may write 600 ops per hour, 5,000 ops and 2 MB of values in total, values are capped at 4 KB (32 KB for notes), and above the database size limit live sandboxes cannot write either. Every demo response carries X-Robots-Tag: noindex, nofollow and the demo page declares noindex.
  • Per-address rate limits (sign-in, passkeys, two-factor, single sign-on, CalDAV, demo sandboxes) and audit-log addresses now use the last X-Forwarded-For hop, the one the reverse proxy appended, instead of the first entry, which the client controls behind an appending proxy such as Traefik and could change to evade the limits. Run one proxy in front of the server and do not publish its port. (Reverse proxy)
  • Behind a CDN, set the optional OPENTODO_TRUSTED_PROXIES (IP addresses, CIDR prefixes and/or cloudflare) so the CDN's hop is skipped and visitors are not all counted as the CDN's address. With cloudflare, requests from a Cloudflare edge use CF-Connecting-IP. (Reverse proxy)

Fixed

  • Phones: every menu, popover, picker and dialog now fits the screen. The notifications panel was cut off on the left; it, the mention picker and the section, project, label and view menus open as bottom sheets below 640 px and next to their button (kept inside the screen) above. Dialogs (rename, share, sign-in confirmation, user administration, voice consent, label and assignee pickers, keyboard help) are full screen on phones with a pinned close button, and all overlays close with Escape or a tap outside and return focus to their button. Touch targets are at least 44 px, row menus are visible without hover on touch screens, long words and URLs wrap, the sidebar scrolls on short screens, and layouts respect the safe areas and the on-screen keyboard. A new CI job opens every screen and overlay at 360, 390 and 768 px in Chromium and WebKit (make layout-check). (mobile-layout)
  • Contrast and accessibility across the app: secondary text (sidebar section names, counts, dates, descriptions), the "All" links, primary buttons (now a darker green, white text at 5.5:1), status and priority colours and dark-mode muted text all meet WCAG AA (4.5:1) in light and dark. Secondary text uses two theme-aware tokens, text-muted and text-faint. Label and assignee pickers no longer nest buttons inside list options, the priority buttons say which one is selected (aria-pressed), and the filter and view result lists are named regions. The layout check now runs axe-core on every screen and overlay in light and dark, not only on the Help view, and fails on serious or critical issues.
  • Task and project History names people, projects, sections and parent tasks instead of showing their ids, labels fields in words ("assignee" rather than assignee_id), and no longer lists writes that kept the value, such as the plain-text copy of a note rewritten when the note is upgraded.
  • Zero-downtime updates work again: several server processes may now use one data directory, so Coolify's rolling update (new container started before the old one stops) no longer fails with "data directory is in use by another opentodo process" and rolls back. Background jobs (reminder push, webhooks, backups, email polling, Trash cleanup, audit pruning, demo reset) run in one process only and move to the new one when the old one stops; maintenance commands still require every server to be stopped. Upgrading from 0.1.0 needs one stop-then-deploy. (Updating)

Downloads and checksums for v0.2.0

Your to-do list, on your own server.

Try the demo in your browser. When you like it, install OpenTodo on your server and import your tasks.

OpenTodo is free and open source, with no paid tier. If it helps you, a star on GitHub helps other people find it, and a coffee helps keep it going.

Buy me a coffee