AI-native task management — a Todoist replacement where an AI agent is a first-class user. Turborepo monorepo with a Next.js web app, a React Native / Expo mobile app, a custom MCP server, and shared TypeScript packages on Supabase.
Live in production at https://dodone.byebrianwong.com.
The brand is DoDone (closed compound, medial capital) in all UI, docs, and marketing. The lowercase hyphenated
do-doneis reserved for internal identifiers only (repo name, npm scope@do-done/*, Expo slug, deep-link scheme).
This README is written primarily for AI coding agents (Claude Code and friends) so they can get enough context to complete real tasks — most often adding a new feature — without re-deriving the architecture every time. It is also perfectly readable by humans, but where there's a tension, it optimizes for "an agent landing here cold can find the right file and not break an invariant."
If you are an agent, read this file, then the two companion docs below, before editing anything:
| Doc | What it gives you |
|---|---|
README.md (this file) |
The map: architecture, where features live, how a change flows through the layers, the rules you must not break. |
CLAUDE.md |
Canonical project instructions — code style, naming, commands, design system, native-build setup. These override defaults; follow them exactly. |
AGENTS.md |
Agent path ownership + coordination rules for parallel work (backend / web / mobile lanes). |
docs/HANDOFF.md |
Living execution state — what's shipped, production URLs/IDs, gotchas, open work. The source of truth for current status. |
The rest of docs/ holds feature design records (task-input redesign, pet feature, mobile roadmap).
DoDone is a layered monorepo. A feature almost never lives in one place — it flows outward from a shared core to one or more surfaces. The dependency arrows only point one way; respect them and the build stays clean.
┌─────────────────────────────────────────────┐
│ packages/shared (leaf — Zod schemas, │
│ types, constants, pure utils, display) │
└─────────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌─────────────┴──┐ ┌──────┴───────┐ ┌──┴──────────────┐
│ api-client │ │ task-engine │ │ ui │
│ Supabase + │ │ NLP parser, │ │ design tokens │
│ TasksApi etc. │ │ focus algo │ │ (colors/spacing)│
└────────┬───────┘ └──────┬───────┘ └──┬──────────────┘
│ │ │
┌────────┴─────────────────┴─────────────┴────────────┐
│ surfaces (apps) │
│ apps/web · apps/mobile · apps/mcp │
└─────────────────────────────────────────────────────┘
▲
│ all read/write the same data
┌─────────────┴───────────────────────────────────────┐
│ supabase/ — Postgres + RLS + migrations + edge fns │
└─────────────────────────────────────────────────────┘
The golden rules (also in CLAUDE.md / AGENTS.md, repeated here because breaking one breaks the build or the data model):
- Types and schemas live in
@do-done/sharedonly. Every entity is a Zod schema with its type inferred viaz.infer<>. Never redefine aTask/Projectshape in an app or another package. - All data access goes through
@do-done/api-client. Apps never call Supabase tables directly. Every API method returns{ data, error }and never throws — always check.error. - Design comes from
@do-done/ui. No hardcoded colors or spacing; pull tokens fromtheme.ts(accent is indigo-500#6366f1, 4px spacing grid, Inter). - Strict TypeScript, no
any. ES modules — local imports use the.jsextension even from.tsfiles. - Stay in your lane when coordinating (see
AGENTS.md): backend =packages/*+apps/mcp+supabase; web =apps/web+packages/ui; mobile =apps/mobile.
apps/
web/ Next.js 16 (App Router, Tailwind, Storybook + Chromatic)
mobile/ React Native / Expo 54 (expo-router, native widgets/geofencing/voice)
mcp/ MCP server — exposes tasks to Claude Code over stdio
packages/
shared/ @do-done/shared Zod schemas, types, constants, utils (leaf, no workspace deps)
api-client/ @do-done/api-client Supabase clients + TasksApi/ProjectsApi/LocationsApi/PetsApi/UserPrefsApi
task-engine/ @do-done/task-engine NLP parser, focus algorithm, scheduler, categorizer, recurrence
ui/ @do-done/ui Design tokens (theme.ts)
supabase/
migrations/ Timestamped SQL — tables, RLS policies, indexes
docs/ Design records + HANDOFF.md (current state)
.github/workflows/ chromatic.yml · eas-update.yml (mobile OTA) · mobile-e2e.yml
@do-done/shared—TaskSchema,ProjectSchema,LocationSchema,UserPreferencesSchema, … plus enums (TaskStatus,TaskPriority); constants (PRIORITY_CONFIG,STATUS_CONFIG,FOCUS_SCORES); utils (todayLocalISO(),isOverdue(),formatWhenTime(),resolveQuickSchedule()); and the sort/group/filterdisplayengine shared by web + mobile.@do-done/api-client—createAnonClient()(apps, RLS-scoped) /createServiceClient()(MCP, service role + explicit userId);TasksApi(list,getById,create,update,complete,getInbox,getToday,getUpcoming,search),ProjectsApi,LocationsApi,PetsApi,UserPrefsApi; theuseAutoSaveTask()React hook.@do-done/task-engine—parseTaskInput()(free text → structured task:p1,#size,/today,/Project, NLP dates),generateFocusList(),generateWeeklySummary(),scheduleTasks(),detectRecurrence().@do-done/ui—colors,spacing,typography,radiusdesign tokens (as const).
Prereqs: Node 20+, pnpm (repo pins pnpm@10.33.0 via packageManager).
pnpm install # install the whole workspace
cp .env.example .env.local # then fill in Supabase + Google keys (see below)
pnpm dev # all dev servers (turbo)
pnpm --filter web dev # web only → http://localhost:3000
pnpm --filter mobile start # Expo dev server (then a=android, i=ios)
pnpm --filter @do-done/mcp build # build the MCP server
pnpm build # build everything
pnpm typecheck # type-check everything ← run before you call a change done
pnpm test # run all tests
pnpm lintEnvironment variables (.env.example → .env.local): SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, POWERSYNC_URL, GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET (Calendar OAuth), and DO_DONE_USER_ID for the MCP server.
Most features touch several layers. Work inside-out so each layer compiles before the next depends on it. Skip the layers your feature doesn't need.
- New field or entity? Add/extend the Zod schema in
packages/shared/src/schemas.tsand any enums/constants inconstants.ts. The inferred type flows everywhere automatically. - Needs a column/table? Add a new timestamped migration in
supabase/migrations/(e.g.20260616000001_add_thing.sql) — never edit an applied one. All tables use UUID PKs +user_idand are protected by RLS; add matching policies. Follow the pattern in existing*_create_rls_policies.sql.
- Add the query method to the relevant API class (
tasks.ts,projects.ts, …). Return{ data, error }; never throw. Add a*.test.tsbeside it (vitest).
- Pure, side-effect-free logic (parsing, scoring, scheduling) goes here so web, mobile, and MCP all share one implementation.
Web — apps/web (Next.js App Router)
- Routes live in
src/app/(app)/<name>/page.tsx— these are server components that fetch data via the server Supabase helpers insrc/lib/supabase/and pass props down. - UI lives in
src/components/<name>-view.tsx(client components); mutations call@do-done/api-client. Add a*.stories.tsxand, where it makes sense, a*.test.tsx. - Auth: protected pages sit under the
(app)route group; login/OAuth is under(auth)+src/app/auth/.
Mobile — apps/mobile (Expo Router)
- Screens are file-based under
app/((tabs)/for the tab bar). Components incomponents/. - Data fetching uses TanStack Query hooks in
lib/task-queries.ts(not server components). Auth vialib/auth-context.tsx. - Native features: Android widgets in
widgets/, geofencing inlib/geofencing.ts, config plugins inplugins/. Native changes need a fresh EAS build (seeCLAUDE.md); JS-only changes ship over-the-air.
Agent / MCP — apps/mcp
- Expose the capability to Claude as a tool in
src/tools/index.ts(tasks) orsrc/tools/pets.ts, or a resource insrc/resources/index.ts. Tool input schemas reuse the@do-done/sharedenums so they never drift. Handlers return{ content: [{ type: "text", text }] }, serialize errors as text, log viaconsole.error(stdout is the protocol channel), and tag writes withactor: "claude". Rebuild + restart the MCP after changes.
Run pnpm typecheck and the relevant tests. For multi-phase work the maintainer prefers stacked PRs per phase over one growing branch.
The web app is auth-gated, so the live preview is a login wall — you usually can't screenshot a feature through the running app. Verify components instead via:
- Storybook (
pnpm --filter web storybook, port 6006) — components render with mocked Supabase. ~18 stories cover the main surfaces (TaskItem, TaskEditModalV2, TaskForm, WeekView, TodayView, SidebarNav, ScheduleButton, the pet panel, …). Add a story for new components. - Vitest unit/component tests across packages and apps (
pnpm test, orpnpm --filter <pkg> test). - Chromatic runs on every push/PR for visual regressions.
mainis gated by a "UI Tests" check — pending visual diffs block merge until baselines are accepted in the Chromatic UI. - Mobile — component tests in
components/__tests__/; Maestro E2E flows run in CI (mobile-e2e.yml). Native modules (widgets, geofencing, voice) only run in a custom EAS dev client, not Expo Go.
Supabase Postgres, every table UUID-keyed with user_id + RLS. Core tables: tasks, projects, locations, task_locations, calendar_sync, user_preferences, plus pet tables (pet_state, goals, events).
A few tasks concepts worth knowing before you touch scheduling:
when_datevsdue_date—when_dateis "I plan to do this on day X" (a do-date, with optionalwhen_time);due_dateis a hard deadline. They are independent. Scheduling is always a concrete date — there are no fuzzy "buckets". Human-friendly options (Today, Tomorrow, This week → this Friday, This weekend → upcoming Sunday, Next week → +7) resolve to real dates viaresolveQuickSchedule()in@do-done/shared.- Subtasks —
parent_task_id+depth; a task tree max 3 levels deep, enforced by a DB trigger. - Status —
inbox/not_started/next/in_progress/done/cancelled; prioritiesp1–p4. Dates are stored as local-timezoneYYYY-MM-DDstrings, so use the*LocalISOhelpers in@do-done/shared, not raw UTC.
- Dates are local, not UTC. "Today" depends on the user's timezone — always go through
todayLocalISO()/addDaysLocalISO()and the timezone helpers in@do-done/shared. Several past bugs were UTC drift. .jsimport suffix is required on relative imports even in TypeScript (ES modules).- MCP is stdio.
console.logcorrupts the protocol — log toconsole.error. Rebuild (pnpm --filter @do-done/mcp build) and restart the client after changing MCP source. - Mobile OTA vs native. Merging JS/asset changes auto-publishes an EAS Update to the preview channel; native changes (new modules, plugin/app.config changes, SDK bumps) require a fresh
eas build+ reinstall. - Don't edit applied migrations. Add a new one.
Private project. Not currently licensed for redistribution.