← Mikael Ishhanian

29 Jul 2026

Tomorrow

To-do apps and calendars are usually separate things, and the gap between them is where tasks go to die. A list lets you write down twenty things without ever admitting they don't fit in a day. A calendar makes you admit it, but only for things you've already decided to do.

Tomorrow is one surface for both. Unscheduled tasks live in a sidebar; the calendar sits next to them; you drag a task onto the grid to commit to it, and drag it off when you were kidding yourself. That single gesture is the whole product idea. Everything else is in service of making it feel immediate.

It's a single-user app on free-tier hosting, built keyboard-first.

Time is the hard part

Almost every real bug in this project was a date bug.

Recurring events are the worst of it. A weekly 09:00 meeting has to stay at 09:00 through a daylight-saving transition, which means the recurrence has to expand in local wall-clock time and only then convert — get the order wrong and half the year drifts by an hour. Editing one occurrence of a series, or one occurrence and everything after it, means forking the series rather than editing the rule. That logic is the one part of the app with a real test suite behind it, because it's the one part where being wrong is silent.

Everything stores UTC and renders in a timezone you choose, which is the boring answer and the correct one.

Making it feel instant

A calendar is a direct-manipulation surface, so a round-trip you can see is a bug. Nearly every mutation paints optimistically and reverts if the server disagrees, and deletes are Gmail-style: the row goes immediately, a toast offers undo, and the actual delete commits when the toast expires.

Drag is built on pointer events rather than the browser's built-in drag-and-drop, which hides the element you're dragging and won't give continuous coordinates. Pointer events give one code path for dragging out of the sidebar, within the grid, and across weeks, at one-minute resolution. Drag state runs through a subscription store so a pointer move repaints the ghost instead of the whole tree.

Overlapping events split into lanes side by side, computed per cluster, so two conflicting events at 10am don't squash the rest of the day. Events hidden inside a switched-off workspace still surface as a warning badge on whatever they collide with — a conflict you can't see is worse than one you can.

The stack

Next.js on Vercel, Supabase for Postgres, auth and row-level security, Drizzle as the typed schema mirror, Tailwind.

The calendar grid is hand-built rather than a library. Custom drag, custom lane packing, and a scrolling time axis were going to be fought for in any library that also had opinions about the rest.

Some of what's in there:

  • Natural-language input — "lunch with S tomorrow at 1" parses into a real event, with the parser loaded only when you start typing so it costs nothing at boot.
  • A command palette for everything, plus real keyboard navigation through context menus, and an undo stack on ⌘Z.
  • Day, week, month and all-day views, drag-and-drop in all of them, panning and zooming the canvas, and configurable working hours drawn on the grid.
  • Rich notes on any task, recurrence, workspaces you can toggle on and off, public holidays by country, and push reminders on a schedule that catches up if a delivery is missed.
  • Dark mode, installable as an app, and a type scale in rem so it holds up on a 4K display.

Session checks happen locally rather than by calling the auth server on every request, and the database functions got collapsed into single round-trips where a click used to cost several — most of the perceived speed came from cutting transatlantic hops, not from rendering faster.