Previous attempt (commit 55cc75e7d) tried to fix the broken JWT mint
in /api/v1/auth/login by switching the cookie name from
`mana.session_token` to `__Secure-mana.session_token` for production.
That was necessary but not sufficient: Better Auth's session cookie
value isn't just the raw session token, it's `<token>.<HMAC>` where
the HMAC is derived from the better-auth secret. Reconstructing the
cookie from auth.api.signInEmail's JSON response only gave us the raw
token, so /api/auth/token's get-session middleware still couldn't
validate it and the JWT mint kept silently failing.
Real fix: do the sign-in via auth.handler (the HTTP path) rather than
auth.api.signInEmail (the SDK path). The handler returns a real fetch
Response with a Set-Cookie header containing the fully signed cookie
envelope. We capture that header verbatim and forward it as the cookie
on the /api/auth/token request, which now passes validation and mints
the JWT correctly.
Verified end-to-end on auth.mana.how:
$ curl -X POST https://auth.mana.how/api/v1/auth/login \
-d '{"email":"...","password":"..."}'
{
"user": {...},
"token": "<session token>",
"accessToken": "eyJhbGciOiJFZERTQSI...", ← real JWT now
"refreshToken": "<session token>"
}
Side benefits:
- The email-not-verified path is now handled by checking
signInResponse.status === 403 directly, no more catching APIError
with the comment-noted async-stream footgun.
- X-Forwarded-For is forwarded explicitly so Better Auth's rate limiter
and our security log see the real client IP.
- The leftover catch block now only handles unexpected exceptions
(network errors etc); the FORBIDDEN-checking logic in it is dead but
harmless and left in for defense in depth.
9.5 KiB
CLAUDE.md
Guidance for Claude Code when working in this repo.
Monorepo Overview
pnpm workspace monorepo with two consolidated tops:
apps/mana/apps/web— unified SvelteKit frontend serving 27+ product modules undermana.how. One build, one IndexedDB, one auth session, one deployment.apps/api(@mana/api) — unified Hono/Bun backend API server. Consolidates per-module compute servers; routes registered under/api/v1/{module}/*.
Per-product directories under apps/{product}/ still exist for landing pages, mobile apps, and product-specific packages, but the active web frontend and API both live in the two consolidated apps above.
- Package Manager: pnpm 9.15.0
- Build System: Turborepo
- Node: 20+
- Primary doc:
apps/mana/CLAUDE.md— module structure, data layer, encryption, routing.
Repo layout
apps/
├── mana/ # Unified frontend (SvelteKit web + Expo mobile + Astro landing)
├── api/ # Unified backend API (Hono/Bun) — @mana/api
├── {product}/ # Per-product landing pages, mobile apps, packages
│ # Standalone (own container, not unified): manavoxel
games/ # arcade, voxelava, whopixels, worldream
services/ # Backend services (Hono/Bun, Go, Python) — see list below
packages/ # Shared workspace packages (@mana/*)
docs/ # Long-form docs (deployment, hardware, postmortems, etc.)
.claude/guidelines/ # Coding conventions — read before changing code
Active services (services/)
mana-auth (3001), mana-sync (3050), mana-credits, mana-user, mana-subscriptions, mana-analytics, mana-search (3021), mana-crawler, mana-api-gateway, mana-notify, mana-media, mana-llm, mana-image-gen, mana-video-gen, mana-stt, mana-tts, mana-voice-bot, mana-events, mana-landing-builder. Each non-trivial service has its own CLAUDE.md.
Coding Guidelines
Always consult before changing code:
| Document | Purpose |
|---|---|
.claude/GUIDELINES.md |
Overview |
.claude/guidelines/code-style.md |
Formatting, naming, linting |
.claude/guidelines/sveltekit-web.md |
Svelte 5 runes, stores |
.claude/guidelines/expo-mobile.md |
React Native, NativeWind |
.claude/guidelines/hono-server.md |
Hono/Bun servers |
.claude/guidelines/database.md |
Drizzle ORM, pgSchema |
.claude/guidelines/authentication.md |
Mana Auth integration |
.claude/guidelines/error-handling.md |
Result types, error codes |
.claude/guidelines/testing.md |
Vitest, mock factories |
.claude/guidelines/design-ux.md |
UI patterns, a11y |
Development Quick Start
See docs/LOCAL_DEVELOPMENT.md for the full setup.
pnpm docker:up # PostgreSQL, Redis, MinIO
pnpm setup:env # Generate per-app .env files from .env.development
pnpm setup:db # Create databases + push schemas
# Start the unified Mana app (most common)
pnpm run mana:dev
# Project-specific full stack (auth + backend + web with auto DB setup)
pnpm dev:chat:full
pnpm dev:todo:full
pnpm dev:picture:full
# … one per project
# Service-only
pnpm dev:auth # mana-auth (3001)
pnpm dev:sync # mana-sync Go server (3050)
Quality:
pnpm run build
pnpm run type-check
pnpm run format
Key Architecture Notes
These are the patterns that span the repo. Service-/app-specific details live in their own CLAUDE.md.
Local-first data layer
The unified Mana app uses one IndexedDB (mana) with all 120+ collections. Module stores write directly to Dexie tables; hooks in database.ts track changes into _pendingChanges tagged by appId. The unified sync engine (sync.ts) groups by appId and pushes to mana-sync (Go, port 3050), which persists field-level LWW into PostgreSQL with RLS.
Full architecture, sprint history, threat model:
At-rest encryption
Sensitive user content in 27 tables is AES-GCM-256 encrypted before hitting IndexedDB. Master key lives in mana-auth, KEK-wrapped (MANA_AUTH_KEK env, must be set in prod). Optional zero-knowledge mode via Settings → Sicherheit.
When touching sensitive fields:
- Add the table to
apps/mana/apps/web/src/lib/data/crypto/registry.tswith the field allowlist await encryptRecord(tableName, record)before writesawait decryptRecords(tableName, visible)after Dexie reads, before the type converter
Default new user-typed fields to encrypt; default new IDs/timestamps/sort-keys to plaintext.
Authentication
All servers use @mana/shared-hono with authMiddleware(). Tokens are EdDSA JWTs issued by mana-auth with claims {sub, email, role, sid, tier, exp, iss, aud}. Cross-app SSO works across *.mana.how. See .claude/guidelines/authentication.md and services/mana-auth/.
Adding an app to SSO requires updating all three:
trustedOriginsinservices/mana-auth/src/auth/better-auth.config.tsCORS_ORIGINSfor mana-auth indocker-compose.macmini.yml- Run
pnpm test -- src/auth/sso-config.spec.tsfromservices/mana-auth/
Access tiers
guest < public < beta < alpha < founder. Apps gate themselves via requiredTier in packages/shared-branding/src/mana-apps.ts; the JWT carries a tier claim; AuthGate enforces it client-side. Admin API at PUT /api/v1/admin/users/:id/tier.
Database (PostgreSQL)
Two databases: mana_platform (all services + app server-side data, schema-isolated via pgSchema) and mana_sync (sync engine, write-heavy). Always use pgSchema('name').table(...), never plain pgTable(). Adding a new schema: see .claude/guidelines/database.md.
Object storage
MinIO (Docker, S3-compatible) in both local and prod. Console: http://localhost:9001 (minioadmin/minioadmin). Use @mana/shared-storage helpers. Pre-configured per-project buckets (picture-storage, chat-storage, cards-storage, …).
Turborepo: avoid recursive turbo calls
CRITICAL: Parent workspace packages (e.g. apps/chat/package.json) must NEVER define type-check, build, or lint scripts that call turbo run <task>. Root turbo already orchestrates those — defining them in children causes infinite recursion (10+ minute hangs, thousands of duplicate tasks). Only dev is OK to delegate to turbo from a parent package, since it's persistent and typically scoped.
Shared Packages (packages/)
| Package | Purpose |
|---|---|
@mana/shared-auth |
Client-side auth for web/mobile |
@mana/shared-hono |
Hono middleware (auth, health, errors) |
@mana/shared-storage |
S3/MinIO helpers |
@mana/shared-branding |
App registry, tiers, branding |
@mana/shared-types |
Common TS types |
@mana/shared-utils |
Utility functions |
@mana/shared-ui |
React Native UI components |
@mana/shared-theme |
Theme config |
@mana/shared-i18n |
i18n |
@mana/local-store |
Local-first store primitives — used by unified Mana, manavoxel, arcade, and shared-uload/-stores/-links |
Adding Dependencies
pnpm add -D <pkg> -w # Workspace root (dev tools)
pnpm add <pkg> --filter @mana/web # A specific app
pnpm add <pkg> --filter @mana/shared-utils # A shared package
Environment Variables
Single source of truth: .env.development (committed). After editing, run pnpm setup:env to regenerate per-app .env files with the right prefixes (EXPO_PUBLIC_* for mobile, PUBLIC_* for SvelteKit, no prefix for Hono/Bun servers). Mapping logic in scripts/generate-env.mjs. Full guide: docs/ENVIRONMENT_VARIABLES.md.
Server Access
- Production (Mac Mini):
ssh mana-server(Cloudflare Tunnel). Seedocs/MAC_MINI_SERVER.md. Useful:./scripts/mac-mini/status.sh,./scripts/mac-mini/deploy.sh,./scripts/mac-mini/build-app.sh <app>. - GPU server (Windows, RTX 3090):
ssh mana-gpu(192.168.178.11, LAN only). Hosts STT/TTS/image-gen/video-gen/Ollama. Seedocs/WINDOWS_GPU_SERVER_SETUP.md.
Reference Docs
| Path | When you need it |
|---|---|
apps/mana/CLAUDE.md |
Default — module pattern, routing, encryption usage |
apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md |
Sync engine deep-dive, encryption rollout, threat model |
docs/LOCAL_DEVELOPMENT.md |
First-time setup, dev:*:full commands |
docs/ENVIRONMENT_VARIABLES.md |
All env vars |
docs/DATABASE_MIGRATIONS.md |
Migration workflow + rollback |
docs/DEPLOYMENT.md |
Production deployment |
docs/PORT_SCHEMA.md |
Which service runs on which port |
Service-specific CLAUDE.md files |
Service internals |