Skip to content

Polqt/tandaan-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tandaan

Tandaan is a collaborative document workspace for work where the process matters, not just the final page.

The core product idea: documents with memory. Tandaan captures drafts, checkpoints, contributors, and replay timelines so users can review how a document evolved.

Stack

Area Technology
Framework Next.js 16 App Router
Auth Clerk
Realtime Liveblocks + Yjs
Editor BlockNote
Database Firebase Firestore
Server writes Firebase Admin SDK
Styling Tailwind CSS v4
Data fetching TanStack Query
Tests Playwright
Package manager Bun

App Structure

src/
  app/
    (public)/            Landing page now; future blog/docs stay public.
    (authentication)/    Clerk sign-in surfaces.
    (protected)/         Documents and protected replay routes.
    api/                 Authenticated app APIs and webhooks.
    layout.tsx           Root layout with ClerkProvider.
    globals.css
  components/
    layouts/             Header, sidebar, app shell.
    shared/              Shared app components and wrappers.
    ui/                  Shadcn-generated components only.
  features/
    collaboration/       Liveblocks providers, cursors, room helpers.
    documents/           Editor, document actions, replay, document hooks.
    users/               Invite/manage users and user actions.
  lib/
    clerk/               Clerk helper functions.
    firebase/            Firebase client/admin init and collection helpers.
    liveblocks.ts
    schemas.ts
    utils.ts
  types/

Routes

Route Access Purpose
/ Public Landing page
/sign-in Public Clerk sign in
/documents Protected Workspace document list
/documents/[id] Protected Collaborative editor
/replay/[shareId] Protected Replay timeline view

API Routes

Method Route Auth Purpose
POST /api/auth-endpoint Required Liveblocks room auth
POST /api/ai/batch-translate Required Proxy to Cloudflare batch translation
POST /api/ai/concept-deep-dive Required Builds a project-history mastery report for one concept
POST /api/ai/detect-language Required Proxy to Cloudflare language detection
POST /api/ai/suggest Required Proxy to Cloudflare writing suggestion
POST /api/ai/translate Required Proxy to Cloudflare translation
POST /api/clerk-webhook Svix signature Sync Clerk users to Firestore
GET /api/documents Required List documents
GET /api/documents/[id] Required Fetch one document
PATCH /api/documents/[id] Required Update document title/content
GET /api/documents/[id]/versions Required Fetch replay timeline
POST /api/documents/[id]/versions Required Save replay checkpoint
POST /api/documents/[id]/replay-share Required Create replay share ID
POST /api/liveblocks-webhook Liveblocks signature Track Yjs updates for server replay snapshots
POST /api/replay/snapshot-due CRON_SECRET Process rooms waiting for server replay snapshots
GET /api/rooms Required List rooms with document metadata
GET /api/rooms/[roomId]/users Required List collaborators
GET /api/users Required Resolve users
GET /api/users/search Required Search users for invites

Firebase Rules In Code

Firebase initialization lives only in:

  • src/lib/firebase/client.ts
  • src/lib/firebase/admin.ts

Collection helpers live in:

  • src/lib/firebase/documents.ts
  • src/lib/firebase/rooms.ts
  • src/lib/firebase/users.ts

Components and page files should not call Firestore directly.

Environment Variables

Create .env.local:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
CLERK_WEBHOOK_SECRET=

NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=

FIREBASE_ADMIN_SERVICE_KEY=

NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY=
LIVEBLOCKS_PRIVATE_KEY=
LIVEBLOCKS_WEBHOOK_SECRET=

CLOUDFLARE_AI_WORKER_URL=
CLOUDFLARE_AI_WORKER_API_KEY=

UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

NEXT_PUBLIC_SENTRY_DSN=
SENTRY_AUTH_TOKEN=
CRON_SECRET=

Cloudflare AI Worker

Set CLOUDFLARE_AI_WORKER_URL to the deployed tandaan-cloudflare Worker base URL, for example:

CLOUDFLARE_AI_WORKER_URL=https://tandaan-cloudflare.your-subdomain.workers.dev

If the Worker requires a shared secret, set:

CLOUDFLARE_AI_WORKER_API_KEY=

CLOUDFLARE_AI_WORKER_SECRET, TANDAAN_CLOUDFLARE_URL, and TANDAAN_CLOUDFLARE_SECRET are also supported as aliases, but CLOUDFLARE_AI_WORKER_URL and CLOUDFLARE_AI_WORKER_API_KEY are preferred in this project.

The Next.js app uses the Worker server-side for:

  • replay version narratives via POST /generate-summary
  • protected translation proxy via POST /api/ai/translate
  • protected batch translation proxy via POST /api/ai/batch-translate
  • protected language detection proxy via POST /api/ai/detect-language
  • protected writing suggestion proxy via POST /api/ai/suggest
  • protected Concept Deep Dive proxy via POST /api/ai/concept-deep-dive

Concept Deep Dive is intentionally user-triggered. The browser calls Tandaan's backend only; the backend validates document access, loads document/replay context, pulls Liveblocks comment threads, derives conservative struggle signals, and then calls the Worker with x-api-key.

Validate the configured Worker environment without making an AI request:

bun run validate:cloudflare-ai

Validate the deployed /concept-deep-dive response contract with a live authenticated request:

bun run validate:cloudflare-ai -- --live

Firebase Admin Key

FIREBASE_ADMIN_SERVICE_KEY must be the full Firebase service account JSON as one environment variable. Before deploying, validate it:

bun run validate:firebase-admin

The JSON must include:

{
  "project_id": "your-firebase-project",
  "client_email": "firebase-adminsdk-...",
  "private_key": "-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"
}

For platforms like Vercel, paste the compact JSON into the environment variable value and keep newline characters escaped as \n inside private_key. Do not commit service_key.json; it is local-development fallback only.

Firestore Indexes

Deploy Firestore indexes before testing collaboration and invite flows against a real Firebase project:

bun run deploy:firestore:indexes

The collaborator/member queries require a collection-group single-field index on rooms.roomId. If it is missing, Firestore will throw FAILED_PRECONDITION when opening the invite/collaborator UI.

Production Rate Limits

Rate limits use Upstash Redis REST when these server-only variables are set:

UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

Without them, Tandaan falls back to an in-memory limiter for local development. Do not rely on the fallback for production because serverless instances do not share memory.

Local Development

bun install
bun run dev

If Next dev gets stuck after route moves or proxy changes:

# stop bun run dev first
rm -rf .next
bun run dev

The Clerk proxy is src/proxy.ts. Do not add a root proxy.ts.

Verification

bun x biome check --write src e2e docs package.json
bun x tsc --noEmit
bun run build
bun x playwright test

Authenticated Playwright Flow

Create the reusable Clerk auth state once:

CLERK_E2E_EMAIL="test@example.com" \
CLERK_E2E_PASSWORD="password" \
bun x playwright test e2e/auth.setup.ts

This saves:

playwright/.auth/user.json

Then run authenticated tests:

bun x playwright test e2e/authenticated.spec.ts

Optional invite test:

PLAYWRIGHT_INVITE_EMAIL="teammate@example.com" \
bun x playwright test e2e/authenticated.spec.ts

Product Direction

Tandaan should not compete as a generic notes app. It should compete as a document memory and replay layer.

Use Tandaan when you need to know:

  • how the draft changed
  • who contributed
  • what decisions shaped the final version
  • what the work looked like before review
  • how to present proof of process

Supporting docs:

  • docs/product-direction.md
  • docs/content-architecture.md

Current Notes

  • Billing is intentionally removed for now.
  • Replay is protected and requires login.
  • Blog and docs are planned public surfaces, but the implementation choice is still open.

About

Tandaan - A modern collaborative workspace for creating and sharing documents in real-time

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors