Task manager, web and native iOS · In development

FocusStack

A personal task manager where the handful of things that actually matter get lifted out of their lists into one view.

Role: Product design, flow design, and direction of the agents that wrote it.

Timeline: Built 2026

Kinds of information tracked
17

Distinct record types in the data model, from counting the table definitions in the schema file. Counted on 21 September 2026.

Things it can do
71

Separate operations the system can perform, from counting the HTTP endpoints registered in the server code. Counted on 21 September 2026.

Files of code
334

TypeScript files, excluding generated migrations and build output. Counted on 21 September 2026.

What was actually broken

Every list-based task manager has the same failure. You make sensible lists — work, house, the thing you keep meaning to do — and then the three genuinely urgent items are sitting in three different lists, and the only place they exist together is your anxiety about them.

The usual fixes are a priority field nobody maintains, or one giant list that is just the same problem with extra scrolling.

The flow I designed

Starring is a lift, not a label. A starred task doesn't get a small badge; it appears in a Priority view that spans every list. The star means "this one comes up out of its list", which is a spatial idea rather than a metadata idea, and people understand it without instruction.

Today, Snoozed and All are separate answers, not filters on one screen. Each corresponds to a distinct thing a person is actually doing: working now, deferring deliberately, or reviewing everything.

The web and native apps are the same product, not a port. They share an API contract, so a behaviour change lands in both by construction. The native app is genuinely native — real gestures, haptics, iOS blur — rather than a wrapped website.

Every interaction is optimistic. Ticking a task is instant, then reconciles. Task management is high-frequency and low-stakes; a spinner on a checkbox is an unforced error.

Interface notes

  1. Adding does not take you anywhere

    The box to add a task sits on top of the list you are already looking at. Making people navigate somewhere else to add something is how a list stops getting used.

  2. Overdue stays in the list

    Yesterday's unfinished work sits with today's, dated. Moving it to a separate overdue pile is how it gets ignored.

  3. Every row says where it came from

    One flat list, but each task still shows which list it belongs to. You never have to remember which part of your life a thing came from.

The hard part

One agreement, several things depending on it.

How the apps and the server talk to each other is written down once, in a single description. The website and the phone app are then generated from that description rather than written by hand to match it.

The value shows up when something changes. Add a field, regenerate, and anything that now disagrees breaks loudly and immediately. Without it, the usual failure is a phone app that quietly stopped matching the server a fortnight ago, and nobody notices until a user reports something impossible.

There is a real cost: it is more setup than simply writing the thing, and it makes quick changes slower. It pays for itself the moment there are two apps. With one it would be over-engineering, and I would have skipped it.

Stack

Monorepopnpm workspaces, TypeScript strict, Node 24
WebReact 18 + Vite, Wouter, TanStack Query, Tailwind, shadcn/Radix, dnd-kit
MobileExpo + React Native, expo-router, Reanimated, haptics, glass effects
BackendExpress 5 + TypeScript
DatabasePostgreSQL + Drizzle ORM
API contractOpenAPI spec → Orval codegen → Zod schemas + typed React Query hooks — The spec is the source of truth; the generated packages are never edited by hand.
AuthSessions in Postgres, with email verification and 2FA behind a flag

Outcome

A pnpm monorepo with a web app, a native iOS/Android app, and a shared generated client — lists, tasks, subtasks, sections, reminders, and the cross-list Priority view, working on both surfaces.

Runs locally; not deployed publicly.

All work · Start a project