Build Any.do
YESreplaces $7.99/mosaves $95.88/yrback to the verdict
A desktop task manager whose hard problems are storing scheduled times so they still mean the same thing after a government changes the clocks, firing reminders exactly once across sleep and restarts, and letting a recurring series be edited without rewriting what already happened.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the application and the test suite.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - installfree
Why Both capture and delivery depend on working while the window is closed, which a web page cannot do dependably.
Get it Any desktop shell for your stack that exposes a global shortcut and the system notification service.
Verify
A shortcut pressed in another application opens capture, and a notification appears with the window closed - installfree
Why The whole scheduling design depends on knowing which database version produced an instant, so a library that hides the version is unusable here.
Get it Use your runtime's internationalisation support or a maintained library that exposes the version. open ↗
Verify
The application prints the database version at startup and a test can stub it - decidefree
Why Twice a year a clock change makes some wall clock times impossible and others ambiguous, and picking a rule in the debugger later means picking whatever the library happened to do.
Get it Decide and write down both rules before implementing the scheduler.
Verify
docs/time-rules.md states both and each has a test
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- tasks(id, title, notes, project_id, priority, starts_on, due_on, created_at, completed_at, deleted_at) - schedules(task_id, rule_text, local_time, zone_id, starts_on, ends_on) · local_time and zone_id together, never a stored instant for a future occurrence - occurrences(task_id, occurrence_date, state, completed_at, skipped_at, overridden_json) · a row exists only once something happened to that date - reminders(id, task_id, occurrence_date, fire_at_utc, tzdb_version, state, fired_at) · the instant is derived at scheduling time and recorded with the database version that produced it - projects(id, name, position) and tags(task_id, tag) · the two groupings that earn their keep - audit(id, entity, entity_id, action, at, payload_json) · what undo reads, and what tells you where a task went - The only UTC instants here are on reminders inside the scheduling horizon, and every one of them records which time zone database version computed it.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ~/.local/share/tasks/tasks.db | Everything. This is the file to back up. |
BACKUP_DIR | required | ~/.local/share/tasks/backups | Automatic copies. Restore is tested against these. |
BACKUP_KEEP | required | 14 | How many to retain. A task database is small, so keep plenty. |
SCHEDULING_HORIZON_HOURS | required | 48 | How far ahead reminders are converted into instants. Beyond this, only wall clock and zone exist. |
CATCHUP_GRACE_MINUTES | required | 30 | Misses newer than this fire normally; older ones collapse into one summary. |
TODAY_VIEW_LIMIT | required | 12 | The day view stops here. Overdue is a count with its own screen. |
The build, in order
Capture before you lose the thought
A task goes in with one keystroke from anywhere, and nothing typed is ever lost.
The thought arrives while you are doing something else.
Files
src/capture/window.tsGuessing silently is worse than not guessing.
Files
src/capture/parse.tstests/parse.test.tsUndo built on an audit trail also answers where a task went.
Files
src/store/audit.ts
done when · tick each as it passeswatch out- A capture box that parses 'friday' out of the title and silently rewrites what you typed means the day you write 'call about friday delivery' the title is mangled and the task is due at the wrong time. Show the parsed date as a separate chip you can reject, and never remove text from the title without the user seeing it happen.
Time stored the way a person meant it
A scheduled time is a wall clock time in a named zone, and instants are derived rather than stored.
The rules for that instant have not all been made yet.
Files
src/time/schedule.tstests/zone.test.tsIt is how you know which reminders need recomputing.
Files
src/time/derive.tsBoth happen twice a year and both need a decision, not an exception.
Files
docs/time-rules.mdtests/dst.test.ts
done when · tick each as it passeswatch out- Storing 'weekdays at 08:00' as a UTC instant bakes in the offset rules that happened to exist on the day you saved it, and those rules change: release 2026d of the time zone database, published on 11 September 2026, recorded that Canada's Northwest Territories moved to permanent minus six from 21 August 2026. Every future reminder stored in that zone as an instant was then wrong by an hour, and nobody finds out until somebody is late. Store the wall clock and the zone, derive the instant late, and recompute when the database version changes.
Reminders that fire once
Notifications survive sleep, restarts and a closed window, and never arrive in a burst.
A crash between the two should lose a notification, not repeat it forever.
Files
src/reminders/deliver.tsForty notifications is the same as none.
Files
src/reminders/catchup.tstests/catchup.test.tsOtherwise a few thousand tasks become a few thousand timers.
Files
src/reminders/scheduler.ts
done when · tick each as it passeswatch out- Computing missed reminders after a laptop has been shut for three days and firing them all produces forty notifications in one second, which the person dismisses in one gesture, losing the two that mattered along with the thirty eight that did not. Fire anything inside the grace period normally, collapse everything older into a single summary, and mark them all delivered.
A series, its occurrences, and its history
Completing, skipping or editing one occurrence never rewrites the rest of the series or the past.
Ten years of a daily task is a computation, not ten years of rows.
Files
src/recurrence/expand.tstests/expand.test.tsThe past was kept under the old rule and should stay that way.
Files
src/recurrence/split.tsA skipped day is information, not a gap.
Files
src/recurrence/state.ts
done when · tick each as it passeswatch out- Implementing 'edit this and following' by changing the rule on the existing series silently rewrites every completed occurrence's expected date, and the history of a habit you have kept for a year becomes wrong in a way you cannot reconstruct. End the old schedule on the day before the change and create a new one, so the past keeps the rule it was actually kept under.
Backups, export and a day view that endsproduct builder
The data is portable and backed up, and the daily view is bounded rather than an infinite list of guilt.
A backup nobody has restored is a hope.
Files
src/backup.tstests/restore.test.tsterminalnpm run backup npm run restore -- --from=backups/latest.db
That is what makes leaving this app possible.
Files
src/export/json.tstests/roundtrip.test.tsA list you cannot finish is a list you stop opening.
Files
src/views/today.ts
done when · tick each as it passeswatch out- A today view that lists every overdue task stops being a plan after the first busy week and becomes a wall of failure people close and stop opening, which is how task applications die. Cap it, show the overdue count as a number with its own screen, and make moving something to today an action the person takes.
That is the whole plan for Any.do. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Cloud sync between devices.
- Two-way calendar synchronisation.
- Shared lists and assignment.
- A mobile application.
- mobile capture, widgets, family features, calendar integration, and sync
- perfect mobile sync
- calendar and email integrations
- shared workspaces
- behavioral coaching polish
- A read-only view served on your own network, so a phone can see today without building a mobile app
- Import from a calendar file for fixed appointments, kept separate from tasks you actually control
- A weekly review screen built from the audit table, showing what you completed, skipped and moved
Need the files? The project pack on the verdict page hands your agent the whole brief · more tasks.