Backend

Everything uses one shared product backend for account-bound data across web, Apple, Android, Windows, and future clients.

Stack

  • Convex is the source of truth for product data, sync functions, server actions, scheduled work, and future feature flags.
  • Clerk is the authentication provider. Convex validates Clerk JWTs using CLERK_JWT_ISSUER_DOMAIN.
  • Vercel continues to host the public Next.js site and web surfaces.
  • Vercel AI SDK is the backend AI layer for task drafting, task creation, and task list summaries. Convex actions call either Vercel AI Gateway or direct OpenAI from server-side environment variables.
  • Firebase remains Android-specific infrastructure for Remote Config unless a later feature requires more platform services.

Repo entry points

  • Backend schema and functions live in convex.
  • Shared TypeScript domain helpers live in packages/everything-domain.
  • Public client deployment URLs use NEXT_PUBLIC_CONVEX_URL for Next/web and EXPO_PUBLIC_CONVEX_URL for Expo-style mobile clients.

Current tables

  • users: authenticated account records keyed by Clerk/Convex token identifier.
  • areas and projects: planning containers owned by a user.
  • tasks: task records with status, priority, dates, reminders, recurrence, tags, source, and soft deletion.
  • devices: per-installation device records for platform targeting and future push/sync diagnostics.
  • aiRequests: lightweight owner-scoped AI request log for usage, model, token, and status observability. Provider error detail is kept server-side and client responses are generic.
  • featureFlags: Convex-backed flags for lightweight rollout controls.

Current functions

  • sync.initialize: provisions the authenticated user and optionally registers the current device.
  • sync.status: returns server time, the current user record, and account counts.
  • sync.snapshot: returns the user’s areas, projects, tasks, and devices for full or cursor-based client sync.
  • sync.changes: returns the owner-scoped sync event journal for incremental client reconciliation.
  • sync.importSnapshot: imports existing local areas, projects, and tasks with client IDs so platform apps can migrate local stores without duplicate records.
  • areas.list, areas.get, areas.create, areas.update, and areas.archive: area CRUD. Archiving can optionally archive child projects and detach active tasks.
  • projects.list, projects.get, projects.create, projects.update, and projects.archive: project CRUD. Moving a project updates active task area links by default, and archiving can optionally detach active tasks.
  • tasks.get, tasks.list, tasks.paginate, tasks.search, tasks.listInbox, tasks.listToday, tasks.create, tasks.update, tasks.setCompleted, tasks.remove, and tasks.restore: task CRUD with soft deletion, indexed pagination, full-text title search, search/tag/container filters, idempotent delete, and restore support.
  • ai.status: authenticated AI configuration check. It reports whether a backend provider is configured without exposing keys.
  • ai.draftTasksFromText: authenticated Vercel AI SDK action that extracts structured task drafts from natural language.
  • ai.createTasksFromText: authenticated Vercel AI SDK action that extracts task drafts and creates synced tasks through the existing task mutation path.
  • ai.summarizeTasks: authenticated Vercel AI SDK action that summarizes the signed-in user’s task list and returns recommended next actions.
  • aiRequests.listMine: returns recent AI request log rows for the signed-in user.
  • featureFlags.listForPlatform: returns enabled feature flags for a signed-in user and platform.
  • featureFlags.upsert and featureFlags.remove: admin-only flag management, gated by ADMIN_EMAILS.
  • POST /clerk-users-webhook: Clerk user lifecycle webhook. Handles user.created, user.updated, and user.deleted after Svix signature verification.

Migration contract

Clients should generate stable local clientId values for areas, projects, and tasks before importing. sync.importSnapshot processes areas first, then projects, then tasks, and resolves relationships through areaClientId and projectClientId. The default conflict policy is serverWins: retries are idempotent, and an imported record only updates an existing server record when the imported updatedAt is newer. Clients can pass conflictPolicy: "clientWins" for a deliberate one-time migration where the local device is known to be authoritative. Sync responses include schemaVersion and syncProtocolVersion. Platform clients should compare these before starting local migrations or replaying queued writes. Direct task mutations are also idempotent when a platform supplies a stable clientId on create. Use tasks.remove for soft deletion and tasks.restore to undo deletion. tasks.listToday includes overdue actionable tasks by default; pass includeOverdue: false for an exact-day view. Use sync.changes after a snapshot to reconcile incremental updates. It returns ordered sync events for area, project, task, device, and user changes. Because multiple events can share the same millisecond timestamp, clients should de-duplicate by event _id when resuming with the returned cursor. Use tasks.paginate for growing task lists. It is backed by owner/status/area/project/deleted/updated indexes so mobile and desktop clients can use cursor pagination instead of pulling every task. Use tasks.search for typeahead-style title search; it uses the Convex search_title full-text index and excludes deleted tasks. Native clients should prefer official Convex client libraries where available. Clients that cannot use a native library can call Convex public functions over the Convex HTTP API with a Clerk JWT in Authorization: Bearer <token>. Keep the token short-lived and request it from Clerk using the convex JWT template. AI clients should call Convex actions, not model providers directly. Use ai.draftTasksFromText when the user should confirm extracted tasks before saving, and ai.createTasksFromText when the client flow explicitly saves the AI-generated tasks. Keep all provider secrets in the Convex deployment environment. Update, archive, delete, complete, and restore mutations accept optional expectedUpdatedAt. Clients should pass the last server updatedAt they rendered when they need optimistic concurrency protection. The backend rejects stale writes with a conflict error instead of silently overwriting another device’s newer change. When archiving containers, clients can choose cleanup behavior:
  • areas.archive({ archived: true, archiveProjects: true }) also archives projects in that area.
  • areas.archive({ archived: true, detachTasks: true }) moves active tasks in that area back to ungrouped state.
  • projects.archive({ archived: true, detachTasks: true }) clears the project link from active tasks.
  • projects.update moves active task area links with the project by default; pass moveTasks: false to leave existing task area links untouched.

Maintenance

crons.ts registers a daily internal retention job for syncEvents. The default retention window is 90 days, which keeps mobile/offline reconciliation useful without letting the journal grow without bound. Clients that have been offline beyond the retention window should run sync.snapshot again. crons.ts also purges aiRequests after 90 days. The log is for operational visibility and user-facing request history, not durable product data.

First setup

Run npm run backend:dev to create or link the Convex project, then configure these values:
  • CONVEX_DEPLOYMENT
  • NEXT_PUBLIC_CONVEX_URL
  • EXPO_PUBLIC_CONVEX_URL
  • CLERK_JWT_ISSUER_DOMAIN
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
  • EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY
  • CLERK_SECRET_KEY
  • CLERK_WEBHOOK_SIGNING_SECRET
  • ADMIN_EMAILS
  • AI_PROVIDER (auto, vercel-gateway, gateway, or openai; defaults to auto)
  • AI_MODEL (defaults to openai/gpt-5.4-mini)
  • AI_REQUESTS_PER_HOUR (defaults to 60 per signed-in user)
  • AI_GATEWAY_API_KEY for Vercel AI Gateway
  • OPENAI_API_KEY for direct OpenAI fallback or direct OpenAI mode
After the project is linked, npm run backend:codegen regenerates Convex bindings in convex/_generated. AI_PROVIDER=auto prefers Vercel AI Gateway when AI_GATEWAY_API_KEY is present, then falls back to direct OpenAI when OPENAI_API_KEY is present. Direct OpenAI mode accepts OpenAI model IDs with or without the openai/ prefix. Use npm run ai:setup to apply AI_PROVIDER, AI_MODEL, and AI_REQUESTS_PER_HOUR to Convex and the linked Vercel project. If AI_GATEWAY_API_KEY or OPENAI_API_KEY exists in the process environment, .env, or .env.local, the script also propagates the secret to Convex and marks it sensitive in Vercel. Use npm run ai:doctor to inspect Vercel project env names without printing Convex secret values.

Clerk webhooks

Create a Clerk webhook endpoint pointing at:
https://<convex-site-url>/clerk-users-webhook
The endpoint must subscribe to user.created, user.updated, and user.deleted. Copy its signing secret into Convex as CLERK_WEBHOOK_SIGNING_SECRET. The handler verifies Svix signatures, stores Clerk email verification state, and removes user-owned product data on user.deleted.