Skip to content
On this page

Architecture

How Teak's apps, backend and Cloudflare Workers fit together, and the rules every change keeps

Teak is a Turborepo monorepo. Every app talks to one Convex backend; two Cloudflare Workers handle files, background work and email. This page is the map: where each piece lives, how a saved card moves through them, and the rules that hold everywhere.

Apps

Path What it is Built with Runs on
apps/web The web app at app.teakvault.com Next.js Vercel
apps/docs teakvault.com: the website, these docs and the changelog Blume Vercel
apps/apple The iPhone, iPad and Mac app, with its share and Safari extensions SwiftUI App Store
apps/android The Android app Kotlin, Jetpack Compose Google Play
apps/extension The Chrome and Firefox extension WXT Chrome Web Store, Firefox Add-ons
apps/raycast The Raycast extension Raycast API Raycast Store
apps/cli The teak command line, published as teak-cli Bun, TypeScript npm
apps/files-worker File delivery, uploads, card processing, imports, exports and the API edge proxy Cloudflare Workers Cloudflare
apps/email-worker Save by email Cloudflare Email Workers Cloudflare

Packages

Path What it is
packages/convex The Convex backend: schema, queries, mutations and actions, the public REST API, the MCP server, and shared/, the pure helpers other surfaces import
packages/files-protocol The contract between Convex and the Files Worker: operation shapes, signing and limits
packages/sdk teak-sdk, the public TypeScript client (SDK). The CLI, extension, docs and end-to-end tests use it from source
packages/i18n The message catalogs for the web app and extension (Translating)
packages/tests The end-to-end suite (Testing)

How a saved card moves

  1. Save. A client calls a Convex mutation (the apps) or the public API (the CLI, Raycast, MCP and your scripts). Every signed-in surface gets its Convex identity from a WorkOS AuthKit session; WorkOS is the only sign-in provider in every environment.
  2. Queue. The mutation writes the card and, in the same transaction, a row in the workflow dispatch outbox (queueCardProcessing in packages/convex/workflowDispatch.ts). A flush hands due rows to the Files Worker, which starts a Cloudflare Workflow for each. A resend is safe: an instance that already exists counts as started.
  3. Process. The card-processing Workflow (apps/files-worker/src/workflows/cardProcessing/run.ts) fetches link pages, renders thumbnails and previews, transcribes audio, reads text in images and asks Workers AI for tags and a summary. Each step reads and writes Convex through one signed endpoint, the workflow gateway.
  4. Show. Each write lands on the card, and every client’s live query updates. The card’s processingStatus records each stage, so a client can show what’s still running.

Every write a processing run makes carries the run’s processing revision. Editing a card bumps the revision and stops the older run, so a stale run can never overwrite newer work; its write is refused and the run ends as skipped.

  • Files live in a private R2 bucket. Convex mints signed URLs, and the Files Worker checks them, streams the object, and handles range requests and caching. Convex holds no R2 or Workers AI credentials: every byte and AI call goes through the Worker.
  • Media work runs in two Containers: a media renderer (ffmpeg and poppler, for video, audio and PDF) and a link fetcher that downloads remote images and media under the same SSRF rules Convex used. Link screenshots run on Browser Run.
  • Search by meaning uses Workers AI embeddings stored in Vectorize, one namespace per user, alongside Convex’s keyword search.
  • Background tasks that used to be Convex crons run as the Worker’s Cron Triggers; Convex keeps the table logic in internal functions the Worker calls.

Public API and MCP

The REST API, the MCP server, the OpenAPI spec and the OAuth discovery documents are Convex HTTP routes. In production, teakvault.com/api, /mcp and the discovery documents reach Convex only through the Files Worker’s edge proxy (apps/files-worker/src/edge/apiProxy.ts), which also rate-limits them. The docs site on Vercel has no rewrites for them. The API, MCP, SDK and CLI offer the same operations, and a test keeps them in step (Adding a Feature).

Save by email

Mail to [email protected] arrives through Cloudflare Email Routing at the Email Worker, which checks the sender’s DMARC result and posts a signed request to Convex. Convex finds the account and makes the cards; attachments go straight to the Files Worker’s signed upload URLs, so their bytes never pass through Convex.

Card types

Teak has eight card types: text, link, image, video, audio, document, palette and quote. Every surface handles all eight, and tolerates values it doesn’t know yet, because the native apps update on their own schedule.

Rules every change keeps

  • Client reads use the cached Convex query hooks; writes use Convex mutations or actions.
  • Import backend APIs from @teak/convex, generated data types from @teak/convex/_generated/dataModel, and shared constants from @teak/convex/shared/constants.
  • The web app owns its UI in apps/web/src. The browser extension keeps its own small copies of the button, wordmark and theme tokens. Pure helpers that several surfaces share (web, docs, extension, native ports) go in packages/convex/shared.
  • The Apple and Android apps port the same pure logic (filters, card edits, Markdown blocks, date search) with the same test cases, so a behavior change on one is ported to the other.
  • Card processing keeps its workflow retries, the processing-revision guard on every write, and a consistent processingStatus.
  • Workflow step names are durable history: a step is never renamed, and new steps go after the existing ones.
  • Every package moves in lockstep: one version across every manifest for each release.

Adding a Feature has the rules for fields, tables, indexes, API operations and backfills.

Last updated on

Was this page helpful?