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
- 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.
- Queue. The mutation writes the card and, in the same transaction, a row in the workflow dispatch outbox (
queueCardProcessinginpackages/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. - 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. - Show. Each write lands on the card, and every client’s live query updates. The card’s
processingStatusrecords 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, AI and search
- 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 inpackages/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.