Contextboard is a canvas-first workspace for organizing markdown cards and nested whiteboards. It combines a tldraw canvas, TipTap-based rich text cards, Convex persistence, global search, and TanStack Router routes for direct links into boards and cards.
Originally, I was frustrated with the performance of Heptabase and wanted to find a better alternative. More than that, I always wanted to actually draw on a whiteboard rather than drag around pre-defined HTML elements — but there was no good candidate for it. Existing note-taking apps either focus entirely on markdown cards or go all-in on hand-written notes, with nothing in between.
In June 2026, after finishing the semester, I finally had time to build something. The first thing that came to mind was a simple yet better note-taking app. The MVP turned out well: markdown cards work (both as a standalone editor and embedded in whiteboard view), hand-drawn strokes are properly stored, and sub-whiteboards made it in too.
The only problem: I forgot to read the tldraw license. During development, the TanStack devtools panel covered the "Get a license for production" watermark. Not until I tried to deploy to Cloudflare did I realize that tldraw is not MIT-licensed the way Excalidraw is. So I decided to open source the project, even though I will probably not continue developing it. Considering how much I put into it (mostly with Claude Code and Codex), it has still been a great learning experience.
- React 19 with TanStack Start, TanStack Router, and TanStack Query
- Convex for realtime data and server functions
- tldraw for the whiteboard surface
- TipTap, markdown-it, and KaTeX for card editing and rendering
- Tailwind CSS 4 for styling
- Vitest for unit tests
- Cloudflare Workers via Wrangler for deployment
Install dependencies:
bun installCreate a local environment file:
cp .env.example .env.localSet the Convex values in .env.local:
CONVEX_DEPLOYMENT=
VITE_CONVEX_URL=Start the app and Convex together:
bun --bun run devThe web app runs on http://localhost:3000. The root route redirects to /whiteboard.
bun --bun run dev # Start Convex and Vite
bun --bun run dev:convex # Start Convex only
bun --bun run dev:web # Start Vite only
bun --bun run generate-routes # Regenerate TanStack Router route tree
bun --bun run build # Production build
bun --bun run preview # Preview the production build
bun --bun run test # Run Vitest
bun --bun run lint # Run Biome lint
bun --bun run format # Format with Biome
bun --bun run check # Run Biome checks
bun --bun run deploy # Build and deploy with Wranglersrc/routes/whiteboardcontains the primary whiteboard route. The layout route keeps one persistentWhiteboardCanvasmounted while navigating between boards.src/components/whiteboardcontains the tldraw integration, custom shapes, persistence helpers, and sizing logic.src/components/editorcontains the TipTap rich text editor, markdown paste extension, math editor, and slash command UI.src/components/searchcontains the command palette and card preview dialog.convexcontains the schema, queries, mutations, migrations, and generated Convex types.
- Route files live in
src/routes. Runbun --bun run generate-routesafter adding, removing, or renaming routes if the generated route tree is not updated automatically. - Convex generated files live in
convex/_generatedand should be regenerated by Convex tooling when the backend API changes. - The app expects Convex to be available for the whiteboard, card, and search workflows.
The project is configured for Cloudflare Workers through vite.config.ts and wrangler.jsonc.
Authenticate once:
wrangler loginDeploy:
bun --bun run deployFor production secrets, use wrangler secret put for the values from .env.example. Public non-secret values can be added under vars in wrangler.jsonc.
For Cloudflare Git builds, use bun run build:cloudflare as the build command. The wrapper script keeps main on the current production deployment and lets non-production branches target a non-prod Convex backend:
CONVEX_PREVIEW_DEPLOY_KEY: recommended for one Convex preview deployment per branch.CONVEX_DEV_DEPLOY_KEY: optional fallback if you want every non-production branch to share one Convex dev deployment instead.CONVEX_PRODUCTION_BRANCH: optional override if your production branch is notmain.
If you are using Workers Builds, set the production trigger with your production CONVEX_DEPLOY_KEY, and set the preview trigger with either CONVEX_PREVIEW_DEPLOY_KEY or CONVEX_DEV_DEPLOY_KEY.