Skip to main content
Softmaple requires Node.js 24.12 or newer, pnpm 11, a Supabase project, and PostgreSQL credentials for that project.

Architecture

The monorepo contains a Next.js 16 web app, a horizontally scalable Nitro WebSocket service, Prisma/Supabase migrations, the Lexical editor, and the EG-walker collaboration packages.
Document metadata lives in public.documents. Document bodies exist only as immutable EG-walker event batches; no Markdown or editor JSON shadow column is stored. Markdown, preview, and LaTeX are derived from the current Lexical state.

Storage roles

Local setup

Configure apps/web/.env:
Configure the collab service:
For multi-instance local experiments, provision Redis (for example Upstash) and set:
Use a direct connection in packages/db/.env for migrations:
Never expose Redis credentials or Supabase service-role keys through NEXT_PUBLIC_* variables.

Password reset emails

In the Supabase dashboard, open Authentication → Email Templates → Reset Password and point the link at the app’s confirm route:
/auth/confirm verifies the token on the server, so the link works on any device or browser. The default {{ .ConfirmationURL }} link still works, via /auth/callback, but only in the browser that requested it: its PKCE code verifier is a cookie there. {{ .SiteURL }} is the project’s Site URL (Authentication → URL Configuration), which should match NEXT_PUBLIC_APP_URL.

Database

Prisma migrations are the single schema source of truth.
Commit the regenerated packages/db/src/database.types.ts whenever the public schema or RPC surface changes. The avatars bucket accepts JPEG, PNG, and WebP up to 2 MB; the web action also rejects images above 2048 × 2048.

Run and verify

pnpm dev starts Web and Collab through Turborepo. Browser WebSockets always use the web origin at /collab/document and /collab/presence; the browser must never receive a direct backend URL or Redis credentials. Stock next dev does not forward /collab/* WebSocket upgrades. For local same-origin sockets use either:
  • Playwright’s apps/web/scripts/e2e-collab-router.mjs, or
  • vercel dev with the root vercel.json Services routing.

Real E2E

Playwright uses dynamic ports, starts both services plus the local collab router, and refuses to reuse an existing server. Core E2E never uses a mock Supabase client or a forged cookie. It requires a dedicated, non-production Supabase project:
The seed route returns 404 unless all guards pass. Cleanup deletes only the exact run-scoped test users, whose cascades remove their workspaces; it never uses broad truncation.
Seeded specs (core.spec.ts, password-reset.spec.ts, workspace-settings.spec.ts) form the chromium-seeded Playwright project. The Isolated Supabase E2E workflow runs it on demand; its GitHub environment stores these secrets and requires approval. Every other spec is in the chromium project, needs no credentials, and runs on each pull request as Chromium Smoke:

Deployment

  1. Set the Vercel project framework to Services and use the repository root (so root vercel.json is applied).
  2. Provision Upstash Redis from the Vercel Marketplace and connect it so REDIS_URL is available to the collab service.
  3. Set collab env vars: DATABASE_URL, SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, COLLAB_ALLOWED_ORIGINS, and REDIS_URL.
  4. Confirm root routing:
Before promotion, verify in a real Preview environment:
  • Origin-checked WebSocket upgrades for document and Presence paths;
  • unknown-origin rejection and reconnect/repair behavior;
  • public read-only access and immediate revocation;
  • avatar upload, public delivery, replacement, and removal;
  • two browser contexts editing the same document through /collab/document with presence on /collab/presence across Fluid instances.
Playwright’s local Upgrade-capable router covers same-origin document and Presence handshakes locally, but it is not a release-equivalent test of Vercel Services WebSocket routing, so Preview validation remains a required release gate.