How Taskr is built

By Anish Kapse · Updated 18 October 2026

Taskr is a free todo app with List, Board and Calendar views. It has been running since December 2024, works offline, and never asks for an account before you add your first task. This page covers how it works underneath: where tasks are stored, how offline changes reach the server, and the bugs that taught the most.

Open Taskr Source on GitHub

At a glance

AppReact 18 and TypeScript, built with Create React App
On the deviceIndexedDB for tasks and the offline queue, with a service worker so the app opens offline
AccountsSupabase: Postgres for tasks, Supabase Auth for sign-in and password resets
HostingVercel, with Vercel Web Analytics for anonymous page views
Drag and dropdnd-kit for the board (mouse, touch and keyboard), FullCalendar for the calendar
Dateschrono-node, loaded only when you start typing a task

Local first, account optional

Every task is saved in your browser’s IndexedDB first. Without an account, that’s the only copy: nothing is sent anywhere, and the app works the same with or without a connection.

Creating an account adds Supabase on top. The device keeps its own copy of the account’s tasks, so a signed-in user still sees their list instantly, and offline.

Signing in is optional. Guests only ever use the middle box.

Moving guest tasks into a new account

When a guest signs up, the tasks they already made come with them. Each task and subtask gets a fresh ID so it can’t clash with anything on the server. Two kinds are skipped: welcome tasks left exactly as they were (in current or older wording, so returning visitors are covered), and tasks the account already has.

Offline changes for signed-in users

A signed-in change that can’t reach Supabase goes into an outbox: a queue in IndexedDB, kept per account, that survives reloads. The device’s copy of the tasks is updated straight away, so nothing looks different to the user. The outbox is sent when the connection returns, when the app loads, on sign-in, and from the sync button.

Signing out sends what it can. If changes are still waiting, they and the account’s offline copy stay on the device until that person signs in again. Otherwise the copy is removed, so the next person on the device can’t see it.

When the database is asleep

Supabase’s free tier pauses a project after about a week without activity. Two things handle that. A GitHub Actions job makes one small read every three days to keep the project awake. And the app tells “the server is unreachable” apart from real errors (a failed fetch, a 5xx, or Supabase’s 540 “paused” response), so people see Cloud sync is asleep right now. Your tasks are still saved on this device. rather than a raw error.

Typing a task: natural language dates

Typing pay rent fri creates “pay rent” due on Friday, and dentist 3 Nov works the same way. The parser, chrono-node, is a sizeable library, so it’s split into its own file and only downloaded when you start typing. The first visit doesn’t pay for it.

Dates like 3/11 mean different things in different places. Taskr reads them month-first only when the browser’s language is US English, and day-first everywhere else, which suits Taskr’s home in Australia. A month or year on its own isn’t treated as a due date, so “call May about the lease” and “watch 1917” stay as they are. A day, a weekday or a time has to be there too.

The Ctrl+K palette

Ctrl+K (⌘K on a Mac) opens a palette for switching views, importing, exporting and jumping to any task. Matches that start with what you typed rank above matches in the middle of a word. Unless the text is already a task, the last option creates it, with a live preview of any date it found.

Small details

Colours that travel round the colour wheel

When a task changes status, its colour strip doesn’t snap from yellow to blue. It sweeps round the colour wheel, through the hues in between. The card usually moves to a different section, which creates a new element, so an ordinary CSS transition has nothing to start from. Taskr remembers each task’s last status across that move and plays a one-off animation. A registered custom property (@property) animates from 0 to 1, and color-mix(in hsl shorter hue, …) turns that number into the colour in between.

Plan trip To Do

Dark mode without a white flash

A few lines of plain script in the page’s <head> apply the saved theme, or the device’s light or dark setting, before React loads. Until someone picks a theme, Taskr follows the device, including when it switches at sunset.

Settings that follow you

For signed-in users, the theme, the last view used and the calendar’s Month or Week choice are stored in the account’s Supabase user metadata, so no extra table is needed. A new device opens the way you left the last one. A change made offline is marked on the device and wins the next time it connects.

Deleting an account

A browser can’t delete a Supabase user directly. Taskr calls a small Postgres function instead. It runs with elevated rights (SECURITY DEFINER), but can only ever delete the caller’s own tasks and account, identified by auth.uid(). Afterwards the app clears that account’s copy and outbox from the device.

Usable without a mouse

Checkboxes and task rows are real buttons, so they’re reachable with Tab, and the account menu follows the WAI-ARIA menu button pattern, with arrow keys, Home, End and Escape. Cards can be dragged with a finger as well as a mouse.

There are also single-key shortcuts. Nothing in the app points to them, to keep the screen quiet; pressing ? anywhere in Taskr lists them (press Esc first if the cursor is in a text box).

j kNext or previous task
xComplete or untick the task
eEdit it
nNew task
/ or Ctrl KSearch and commands
1 2 3List, Board or Calendar
Alt ↑ Alt ↓Move a task up or down the list
← → ↑ ↓Move a card between and within columns on the board
Ctrl-click, Shift-clickSelect several tasks to move, complete or delete together

Bugs worth telling

What people sawWhyThe fix
New visitors got one welcome task instead of five Each welcome task was saved on its own, and each save rewrote storage with just that task Save the whole set in one write
Adding tasks in quick succession lost one The save handler worked from a copy of the list captured before the previous add finished (a stale closure) Read the latest list from a ref at the moment of saving
Signed-in edits made offline disappeared The “back online” handler cleared the queue without sending it. The code even said “Here you would sync” The outbox described above, tested against a fake Supabase server that can be switched off

What’s next

Everything that has shipped so far is in the changelog.