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

Database tables
9

Table definitions across lib/db/src/schema. Counted in the repository on 8 August 2026.

API endpoints
53

Route handlers across 8 route modules. Counted in the repository on 8 August 2026.

Source files
273

TS/JS files across all workspace packages, excluding node_modules. Counted in the repository on 8 August 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.

The hard part

One contract, three consumers.

The API is defined as an OpenAPI spec, and both the validation schemas and the typed React Query hooks are generated from it. Those generated directories are never hand-edited.

The value shows up when something changes. Add a field, regenerate, and the compiler immediately points at every place in the web app and the native app that now needs attention. Without that, the failure mode is a mobile app that has quietly disagreed with the server for two weeks and nobody notices until a user reports something impossible.

There's a real cost: it is more setup than "just write the endpoint", and it makes quick changes slower. It pays for itself the moment there are two clients. With one client it would be over-engineering, and I'd 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