> ## Documentation Index
> Fetch the complete documentation index at: https://softmaple.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Development

> Run and verify the Softmaple v1 stack

<Info>
  Softmaple requires Node.js 24.12 or newer, pnpm 11, a Supabase project, and
  PostgreSQL credentials for that project.
</Info>

## 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.

```text theme={null}
Browser → /collab/* → apps/collab-nitro → Redis (realtime) + Supabase Postgres (history)
       ↘ apps/web → Supabase Auth / Data API / Storage
```

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

| Store | Use |
| - | - |
| Postgres | Durable EG-walker event history |
| Redis | Distributed realtime fan-out, presence TTLs, connection leases |
| Process memory | Socket objects and local peer lookup only |

## Local setup

```bash theme={null}
pnpm install
cp apps/web/.env.example apps/web/.env
cp apps/collab-nitro/.env.example apps/collab-nitro/.env.local
cp packages/db/.env.example packages/db/.env
```

Configure `apps/web/.env`:

```env theme={null}
NEXT_PUBLIC_SUPABASE_URL="https://PROJECT_REF.supabase.co"
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY="sb_publishable_..."
NEXT_PUBLIC_APP_URL="http://localhost:3000"
```

Configure the collab service:

```env theme={null}
DATABASE_URL="postgresql://postgres.PROJECT_REF:PASSWORD@POOLER:6543/postgres?pgbouncer=true"
SUPABASE_URL="https://PROJECT_REF.supabase.co"
SUPABASE_PUBLISHABLE_KEY="sb_publishable_..."
COLLAB_ALLOWED_ORIGINS="http://localhost:3000"
COLLAB_REALTIME_DRIVER="memory"
```

For multi-instance local experiments, provision Redis (for example Upstash) and
set:

```env theme={null}
COLLAB_REALTIME_DRIVER="redis"
REDIS_URL="rediss://default:TOKEN@HOST:6379"
```

Use a direct connection in `packages/db/.env` for migrations:

```env theme={null}
DATABASE_URL="postgresql://postgres.PROJECT_REF:PASSWORD@POOLER:6543/postgres?pgbouncer=true"
DIRECT_URL="postgresql://postgres:PASSWORD@DIRECT_HOST:5432/postgres"
```

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:

```html theme={null}
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/reset-password/update">Reset password</a>
```

`/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.

```bash theme={null}
pnpm --filter @softmaple/db db:generate
pnpm --filter @softmaple/db db:migrate
pnpm --filter @softmaple/db db:verify:collab-security
pnpm --filter @softmaple/db db:types
```

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

```bash theme={null}
pnpm dev
pnpm format:check
pnpm --filter @softmaple/web lint
pnpm typecheck
pnpm --filter @softmaple/web test -- --run
pnpm --filter @softmaple/collab-nitro test
pnpm turbo run build --filter=@softmaple/web --filter=@softmaple/collab-nitro
```

`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:

```env theme={null}
DATABASE_URL="isolated pooled database URL"
DIRECT_URL="isolated direct database URL"
NEXT_PUBLIC_SUPABASE_URL="https://E2E_PROJECT_REF.supabase.co"
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY="isolated publishable key"
SUPABASE_SERVICE_ROLE_KEY="isolated service-role key"
E2E_ALLOW_REMOTE_SEED="true"
E2E_SEED_SECRET="a long random bearer secret"
E2E_SUPABASE_PROJECT_REF="E2E_PROJECT_REF"
SUPABASE_PRODUCTION_PROJECT_REF="PRODUCTION_PROJECT_REF"
```

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.

```bash theme={null}
pnpm --filter @softmaple/db db:deploy
pnpm --filter @softmaple/web test:e2e:seeded
```

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:

```bash theme={null}
pnpm --filter @softmaple/web test:e2e:chromium
```

## 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:

```text theme={null}
/collab/** → apps/collab-nitro
/**        → apps/web
```

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.