diff --git a/CLAUDE.md b/CLAUDE.md index d49d8da8a..1c931fdf6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,1138 +1,169 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Guidance for Claude Code when working in this repo. ## Monorepo Overview -This is a pnpm workspace monorepo with a **unified web application** (`apps/mana/apps/web`) serving 27+ product modules. All modules share one SvelteKit app, one IndexedDB database, one auth session, and one deployment. Backend services use Hono/Bun compute servers. Data follows a local-first architecture (Dexie.js + mana-sync). +pnpm workspace monorepo. The main surface is the **unified web app** at `apps/mana/apps/web` — one SvelteKit build serving 27+ product modules under `mana.how`, sharing one IndexedDB, one auth session, one deployment. -**Unified App:** `apps/mana/apps/web` — the main web interface at `mana.how` -**Standalone Web Apps:** Archived to `apps/*/apps/web-archived/` (superseded by unified app) -**Active Standalone:** matrix, manavoxel, arcade (separate containers, not yet unified) +- **Package Manager:** pnpm 9.15.0 +- **Build System:** Turborepo +- **Node:** 20+ +- **Primary doc:** [`apps/mana/CLAUDE.md`](apps/mana/CLAUDE.md) — read this for module structure, data layer, encryption, and routing details. -**Package Manager:** pnpm 9.15.0 (use `pnpm` for all commands) -**Build System:** Turborepo -**Node Version:** 20+ +### Repo layout -## Detailed Guidelines +``` +apps/ # Product apps. Most are integrated as modules in apps/mana/apps/web. + # Standalone (own container, not unified): matrix, 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 +``` -For comprehensive guidelines on code patterns and conventions, see the `.claude/` directory: +### 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-matrix-bot`, `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`](.claude/GUIDELINES.md) | Main reference overview | +| [`.claude/GUIDELINES.md`](.claude/GUIDELINES.md) | Overview | | [`.claude/guidelines/code-style.md`](.claude/guidelines/code-style.md) | Formatting, naming, linting | -| [`.claude/guidelines/database.md`](.claude/guidelines/database.md) | Drizzle ORM, schema patterns | -| [`.claude/guidelines/testing.md`](.claude/guidelines/testing.md) | Jest/Vitest, mock factories | -| [`.claude/guidelines/hono-server.md`](.claude/guidelines/hono-server.md) | Hono/Bun compute servers | -| [`.claude/guidelines/error-handling.md`](.claude/guidelines/error-handling.md) | Go-style Result types, error codes | | [`.claude/guidelines/sveltekit-web.md`](.claude/guidelines/sveltekit-web.md) | Svelte 5 runes, stores | | [`.claude/guidelines/expo-mobile.md`](.claude/guidelines/expo-mobile.md) | React Native, NativeWind | +| [`.claude/guidelines/hono-server.md`](.claude/guidelines/hono-server.md) | Hono/Bun servers | +| [`.claude/guidelines/database.md`](.claude/guidelines/database.md) | Drizzle ORM, pgSchema | | [`.claude/guidelines/authentication.md`](.claude/guidelines/authentication.md) | Mana Auth integration | -| [`.claude/guidelines/design-ux.md`](.claude/guidelines/design-ux.md) | UI patterns, animations, a11y | +| [`.claude/guidelines/error-handling.md`](.claude/guidelines/error-handling.md) | Result types, error codes | +| [`.claude/guidelines/testing.md`](.claude/guidelines/testing.md) | Vitest, mock factories | +| [`.claude/guidelines/design-ux.md`](.claude/guidelines/design-ux.md) | UI patterns, a11y | -**Always consult these guidelines before making changes.** +## Development Quick Start -## Projects - -### Unified App — Mana (`apps/mana/apps/web`) - -The main web interface serving 27+ modules. All modules share one SvelteKit build, one IndexedDB, one auth session. Each module lives in `src/lib/modules/{name}/`. - -### Project Modules (integrated into unified app) - -| Module | Description | Active Apps | -|--------|-------------|-------------| -| **todo** | Task management | Server, Landing | -| **calendar** | Calendar & scheduling | Server, Landing | -| **contacts** | Contact management | Server | -| **chat** | AI chat application | Server, Mobile, Landing | -| **picture** | AI image generation | Server, Mobile, Landing | -| **memoro** | AI voice recording | Server, Audio-Server, Mobile, Landing | -| **cards** | Flashcard/deck management | Mobile | -| **storage** | Cloud file storage | Server | -| **mukke** | Music production | Server, Landing | -| **zitare** | Daily inspiration quotes | Landing | -| **presi** | Presentations | Server, Mobile, Landing | -| **questions** | Research assistant | Server | -| **context** | Document workspace | Server, Mobile | -| **photos** | Photo management | — | -| **nutriphi** | Nutrition tracking | Server, Landing | -| **planta** | Plant care | Server | -| **skilltree** | Skill tracking | — | -| **citycorners** | City guide for Konstanz | Landing | -| **inventar** | Inventory management | — | -| **traces** | City exploration | Server, Mobile | -| **times** | Time tracking & clocks | — | -| **uload** | URL shortener | Server, Landing | -| **moodlit** | Ambient lighting | Server, Landing | -| **calc** | Calculator & converter | — | -| **guides** | City guides | Server | - -Standalone web apps have been archived to `apps/*/apps/web-archived/`. - -### Standalone Web Apps (not yet unified) - -| Project | Description | Why separate | -|---------|-------------|--------------| -| **matrix** | Matrix chat client | Docker container `matrix-web`, protocol-specific | -| **manavoxel** | 3D voxel editor | Docker container `manavoxel-web`, WebGL-heavy | -| **playground** | LLM playground | Docker container, development tool | - -### Games (`games/`) - -| Game | Description | Tech | -|------|-------------|------| -| **arcade** | AI browser games platform (22+ games) | SvelteKit, Hono+Bun, Gemini/Claude/GPT | -| **voxelava** | Voxel game | SvelteKit | -| **whopixels** | Phaser.js pixel game | Phaser, JavaScript | -| **worldream** | World exploration game | SvelteKit | - -### Archived Projects (`apps-archived/`) - -Archived apps are excluded from the pnpm workspace. - -| Project | Reason | -|---------|--------| -| **clock** | Consolidated into Times | -| **wisekeep** | Inactive, not integrated into unified app | - -**Note:** Standalone web apps (`apps/*/apps/web-archived/`) are also archived but remain within their project directories. Only the unified Mana web app (`apps/mana/apps/web`) is active. - -## Development Commands - -For detailed local development setup, see **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)**. - -### Quick Start (Recommended) - -Use `dev:*:full` commands to start any app with automatic database setup: +See [`docs/LOCAL_DEVELOPMENT.md`](docs/LOCAL_DEVELOPMENT.md) for the full setup. ```bash -pnpm docker:up # Start PostgreSQL, Redis, MinIO -pnpm dev:chat:full # Start chat with auth + auto DB setup -pnpm dev:zitare:full # Start zitare with auth + auto DB setup -pnpm dev:contacts:full # Start contacts with auth + auto DB setup -pnpm dev:calendar:full # Start calendar with auth + auto DB setup -pnpm dev:times:full # Start times with auth + auto DB setup -pnpm dev:todo:full # Start todo with auth + auto DB setup -pnpm dev:picture:full # Start picture with auth + auto DB setup -pnpm dev:uload:full # Start uload with auth + auto DB 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 -These commands automatically: -1. Create the database if missing -2. Push the latest schema -3. Start auth, backend, and web with colored output - -### Database Setup - -```bash -pnpm setup:db # Setup ALL databases and schemas -pnpm setup:db:chat # Setup just chat -pnpm setup:db:auth # Setup just auth -``` - -### Individual App Commands - -```bash -# Start specific project (runs all apps in project) +# Start the unified Mana app (most common) pnpm run mana:dev -pnpm run memoro:dev -pnpm run cards:dev -pnpm run picture:dev -pnpm run chat:dev -pnpm run zitare:dev -pnpm run contacts:dev -# Start specific app within project -pnpm run dev:chat:mobile # Just mobile app -pnpm run dev:chat:server # Just Hono/Bun server -pnpm run dev:chat:local # sync + server + web (no auth needed) -pnpm run dev:chat:app # Server + web together +# 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 -# Build & quality +# Service-only +pnpm dev:auth # mana-auth (3001) +pnpm dev:sync # mana-sync Go server (3050) +``` + +Quality: + +```bash pnpm run build pnpm run type-check pnpm run format ``` -Each project has its own `CLAUDE.md` with detailed project-specific commands. +## Key Architecture Notes -## Architecture Patterns +These are the patterns that span the repo. Service-/app-specific details live in their own CLAUDE.md. -### Monorepo Structure +### Local-first data layer -``` -mana-monorepo/ -├── apps/ # Active SaaS product applications -│ ├── chat/ -│ │ ├── apps/ -│ │ │ ├── server/ # Hono/Bun compute server -│ │ │ ├── mobile/ # Expo React Native app -│ │ │ ├── web/ # SvelteKit web app -│ │ │ └── landing/ # Astro marketing page -│ │ └── packages/ # Project-specific shared code -│ ├── cards/ -│ ├── picture/ -│ └── ... -├── apps-archived/ # Archived apps (excluded from workspace) -│ ├── bauntown/ -│ ├── memoro/ -│ ├── news/ -│ ├── nutriphi/ -│ ├── reader/ -│ ├── uload/ -│ └── wisekeep/ -├── games/ # Game projects -│ ├── arcade/ # AI browser games platform (SvelteKit + Hono/Bun) -│ ├── voxelava/ # Voxel game -│ ├── whopixels/ # Phaser.js pixel game -│ └── worldream/ # World exploration game -├── services/ # Standalone microservices -│ ├── mana-auth/ # Central auth (Hono + Bun + Better Auth) -│ ├── mana-auth/ # Central auth rewrite (Hono + Bun + Better Auth) -│ ├── mana-credits/ # Credit system (Hono + Bun) -│ ├── mana-user/ # User settings & tags (Hono + Bun) -│ ├── mana-subscriptions/ # Subscription billing (Hono + Bun) -│ ├── mana-analytics/ # Feedback & analytics (Hono + Bun) -│ ├── mana-sync/ # Local-first data sync (Go, WebSocket) -│ ├── mana-search/ # Search & content extraction (Go) -│ ├── mana-crawler/ # Web crawler (Go) -│ ├── mana-api-gateway/ # API gateway + rate limiting (Go) -│ ├── mana-notify/ # Notifications: email, push, Matrix, webhook (Go) -│ ├── mana-matrix-bot/ # 21 Matrix bot plugins (Go) -│ ├── mana-media/ # Media platform: CAS, thumbnails (Hono + Bun) -│ ├── mana-llm/ # LLM abstraction (Python/FastAPI) -│ ├── mana-image-gen/ # AI image generation with FLUX (Python/FastAPI) -│ ├── mana-video-gen/ # AI video generation with LTX-Video (Python/FastAPI) -│ ├── mana-stt/ # Speech-to-text (Python/FastAPI) -│ ├── mana-tts/ # Text-to-speech (Python/FastAPI) -│ ├── mana-voice-bot/ # Voice assistant (Python/FastAPI) -│ └── mana-landing-builder/# Org landing pages (Astro → Cloudflare Pages) -├── packages/ # Monorepo-wide shared packages -└── docker/ # Docker configuration files -``` +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. -### Standard Project Structure (inside apps/) +Full architecture, sprint history, threat model: +- [`apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md`](apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md) +- [`apps/mana/CLAUDE.md`](apps/mana/CLAUDE.md) -``` -apps/{project}/ -├── apps/ -│ ├── server/ # Hono/Bun compute server (when present) -│ ├── mobile/ # Expo React Native app -│ ├── web/ # SvelteKit web app -│ └── landing/ # Astro marketing page -├── packages/ # Project-specific shared code -└── package.json -``` +### At-rest encryption -### Turborepo Configuration +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. -**CRITICAL: Avoid Recursive Turbo Calls** +When touching sensitive fields: +1. Add the table to `apps/mana/apps/web/src/lib/data/crypto/registry.ts` with the field allowlist +2. `await encryptRecord(tableName, record)` before writes +3. `await decryptRecords(tableName, visible)` after Dexie reads, before the type converter -Parent workspace packages (e.g., `apps/chat/package.json`, `apps/zitare/package.json`) must **NEVER** have scripts that call `turbo run ` for tasks that turbo orchestrates from the root. +Default new user-typed fields to **encrypt**; default new IDs/timestamps/sort-keys to **plaintext**. -```jsonc -// WRONG - Creates infinite recursion! -// apps/chat/package.json -{ - "scripts": { - "type-check": "turbo run type-check", // DON'T DO THIS - "build": "turbo run build", // DON'T DO THIS - "lint": "turbo run lint" // DON'T DO THIS - } -} +### Authentication -// CORRECT - Let root turbo handle orchestration -// apps/chat/package.json -{ - "scripts": { - "dev": "turbo run dev" // OK for dev (persistent task, scoped) - // No type-check, build, lint scripts - handled by root turbo - } -} -``` - -**Why this matters:** When root turbo runs `type-check`, it finds packages with `type-check` scripts and runs them. If that script is `turbo run type-check`, it spawns another turbo process that does the same thing → infinite loop. This causes tasks to run for 10+ minutes with thousands of duplicate task entries. - -**The `dev` script exception:** Using `turbo run dev` in parent packages is acceptable because: -1. It's typically run directly on that package (scoped) -2. Dev tasks are persistent and turbo handles them differently - -**Current turbo.json settings:** -- `concurrency: "5"` - Parallel task limit (adjust based on machine) -- `type-check` has `dependsOn: ["^type-check"]` - Dependencies are checked first - -### Technology Stack by App Type - -**Mobile Apps (Expo):** - -- React Native 0.76-0.81 + Expo SDK 52-54 -- Expo Router (file-based routing) -- NativeWind (Tailwind for React Native) -- Zustand (state management) - -**Web Apps (SvelteKit):** - -- SvelteKit 2.x + Svelte 5 -- Tailwind CSS -- Mana Auth (Better Auth + EdDSA JWT) -- Local-first data (Dexie.js + mana-sync) -- svelte-i18n (5 languages: de, en, it, fr, es) - -**Landing Pages (Astro):** - -- Astro 5.x -- Tailwind CSS -- Static site generation - -**Compute Servers (Hono + Bun):** - -- Hono 4.x + Bun runtime -- TypeScript, Drizzle ORM (where needed) -- `@mana/shared-hono` for auth middleware - -### Authentication Architecture - -All projects use **mana-auth** as the central authentication service: - -``` -┌─────────────┐ ┌─────────────┐ ┌────────────────┐ -│ Client │────>│ Server │────>│ mana-auth │ -│ (Web/Mobile)│ │ (Hono/Bun) │ │ (port 3001) │ -└─────────────┘ └─────────────┘ └────────────────┘ - │ │ │ - │ Bearer token │ POST /validate │ - │ │ {token} │ - │ │<────────────────────│ - │ │ {valid, payload} │ - │<──────────────────│ │ - │ Response │ │ -``` - -#### Key Components - -| Component | Purpose | -| ------------------------------- | -------------------------------------------------- | -| `services/mana-auth` | Central auth service (Better Auth + EdDSA JWT) | -| `@mana/shared-hono` | Shared Hono middleware for JWT validation | -| `@mana/shared-auth` | Client-side auth for web/mobile apps | - -#### Hono Server Auth Integration - -All compute servers use `@mana/shared-hono` for auth: - -```typescript -import { authMiddleware, healthRoute, errorHandler, notFoundHandler } from '@mana/shared-hono'; - -const app = new Hono(); -app.onError(errorHandler); -app.notFound(notFoundHandler); -app.route('/health', healthRoute('my-server')); -app.use('/api/*', authMiddleware()); - -// In route handlers, get user from context: -app.get('/api/v1/data', (c) => { - const userId = c.get('userId'); - // ... -}); -``` - -#### Required Environment Variables - -```env -# All servers need this -MANA_AUTH_URL=http://localhost:3001 -CORS_ORIGINS=http://localhost:5173 -``` - -#### JWT Token Structure (EdDSA) - -```json -{ - "sub": "user-id", - "email": "user@example.com", - "role": "user", - "sid": "session-id", - "exp": 1764606251, - "iss": "mana", - "aud": "mana" -} -``` - -#### Testing Auth Integration - -```bash -# 1. Start mana-auth -pnpm dev:auth - -# 2. Start an app locally -pnpm dev:contacts:local - -# 3. Get a token -TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email": "test@example.com", "password": "password"}' | jq -r '.accessToken') - -# 4. Call protected endpoint -curl http://localhost:3033/api/v1/import/vcard \ - -H "Authorization: Bearer $TOKEN" -``` - -#### Adding a New App to SSO - -When adding a new app that should participate in cross-app SSO, update **all three** locations: +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`](.claude/guidelines/authentication.md) and `services/mana-auth/`. +**Adding an app to SSO** requires updating *all three*: 1. `trustedOrigins` in `services/mana-auth/src/auth/better-auth.config.ts` 2. `CORS_ORIGINS` for mana-auth in `docker-compose.macmini.yml` -3. Run `pnpm test -- src/auth/sso-config.spec.ts` (from `services/mana-auth/`) to verify +3. Run `pnpm test -- src/auth/sso-config.spec.ts` from `services/mana-auth/` -Missing any of these will silently break SSO for that app. +### Access tiers -### Access Tier System (Phased Release) +`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`. -Apps can be gated behind access tiers for phased rollouts (e.g., founder-only alpha, then beta, then public). +### Database (PostgreSQL) -#### Tier Hierarchy +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`](.claude/guidelines/database.md). -| Tier | Level | Who | -|------|-------|-----| -| `guest` | 0 | Unauthenticated visitors (local-only) | -| `public` | 1 | Any registered user (default for new signups) | -| `beta` | 2 | Beta testers | -| `alpha` | 3 | Alpha testers / internal | -| `founder` | 4 | Founding members | +### Object storage -A user can access an app if their tier level >= the app's `requiredTier` level. +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`, …). -#### How It Works +### Turborepo: avoid recursive turbo calls -1. **`mana-apps.ts`** defines `requiredTier` per app (e.g., `requiredTier: 'founder'`) -2. **Users table** stores `accessTier` column (default: `'public'`) -3. **JWT** includes a `tier` claim, set during token creation in better-auth config -4. **AuthGate** checks the tier client-side and shows an "access restricted" state if insufficient - -#### Key Files - -| File | Purpose | -|------|---------| -| `packages/shared-branding/src/mana-apps.ts` | App registry with `requiredTier` | -| `services/mana-auth/src/db/schema/auth.ts` | `accessTier` column on users | -| `services/mana-auth/src/auth/better-auth.config.ts` | Adds `tier` to JWT claims | -| `packages/shared-auth-ui/src/components/AuthGate.svelte` | Client-side tier gating | -| `services/mana-auth/src/routes/admin.ts` | Admin API for tier management | - -#### Gating an App - -Pass `requiredTier` to AuthGate in the app's layout: - -```svelte - - - -``` - -The tier value comes from the app's entry in `mana-apps.ts`. Apps without `requiredTier` default to `'public'` (accessible to all registered users). - -#### Admin API - -```bash -# Set a user's tier -PUT /api/v1/admin/users/:id/tier -{ "tier": "beta" } - -# Get a user's tier -GET /api/v1/admin/users/:id/tier - -# List users (includes tier) -GET /api/v1/admin/users -``` - -#### Releasing an App - -To widen access, change `requiredTier` in `mana-apps.ts`: - -```typescript -// Founder-only alpha -{ id: 'myapp', requiredTier: 'founder' } - -// Open to beta testers -{ id: 'myapp', requiredTier: 'beta' } - -// Public release -{ id: 'myapp', requiredTier: 'public' } -``` - -No database migration needed -- just update the config and redeploy the app. - -### Search Architecture - -Projects requiring web search and content extraction use **mana-search** as the central search service: - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Consumer Apps │ -│ Questions │ Chat │ Project Doc Bot │ Future Apps │ -└─────────────────────────┬───────────────────────────────────┘ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ mana-search (Port 3021) │ -│ Search API │ Extract API │ Redis Cache │ -└─────────────────────────┬───────────────────────────────────┘ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ SearXNG (Port 8080, internal) │ -│ Google │ Bing │ DuckDuckGo │ Wikipedia │ arXiv │ ... │ -└─────────────────────────────────────────────────────────────┘ -``` - -#### Key Components - -| Component | Purpose | -|-----------|---------| -| `services/mana-search` | Go search service with SearXNG + Redis | -| SearXNG | Meta-search engine aggregating multiple sources | -| Redis | Caching layer (search: 1h TTL, extract: 24h TTL) | - -#### API Endpoints - -```bash -# Web search -POST /api/v1/search -{ - "query": "quantum computing", - "options": { - "categories": ["general", "science"], - "engines": ["google", "wikipedia"], - "limit": 10 - } -} - -# Extract content from URL -POST /api/v1/extract -{ - "url": "https://example.com/article", - "options": { "includeMarkdown": true } -} - -# Bulk extract (max 20 URLs) -POST /api/v1/extract/bulk - -# Health & metrics -GET /health -GET /metrics -``` - -#### Search Categories - -| Category | Engines | -|----------|---------| -| `general` | Google, Bing, DuckDuckGo, Brave, Wikipedia | -| `news` | Google News, Bing News | -| `science` | arXiv, Google Scholar, PubMed, Semantic Scholar | -| `it` | GitHub, StackOverflow, NPM, MDN | - -#### Starting the Service - -```bash -# Start SearXNG + Redis for local development -cd services/mana-search && docker-compose -f docker-compose.dev.yml up -d - -# Start Go search service -cd services/mana-search && go run ./cmd/server -``` - -#### Environment Variables - -```env -# Consumer apps need this -MANA_SEARCH_URL=http://localhost:3021 - -# mana-search service config -SEARXNG_URL=http://localhost:8080 -REDIS_HOST=localhost -REDIS_PORT=6379 -CACHE_SEARCH_TTL=3600 -CACHE_EXTRACT_TTL=86400 -``` - -#### Usage in Server - -```typescript -// Direct fetch -const response = await fetch('http://mana-search:3021/api/v1/search', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - query: 'machine learning basics', - options: { categories: ['general', 'science'], limit: 5 } - }) -}); - -const { results, meta } = await response.json(); -``` - -### Svelte 5 Runes Mode (Web Apps) - -All SvelteKit apps use Svelte 5 runes: - -```typescript -// CORRECT - Svelte 5 -let count = $state(0); -let doubled = $derived(count * 2); -$effect(() => { - console.log(count); -}); - -// WRONG - Old Svelte syntax -let count = 0; -$: doubled = count * 2; -``` - -## Local-First Architecture - -All web apps use a **local-first** data layer: reads/writes go to IndexedDB (Dexie.js) first, sync to server in the background. This enables guest mode, offline CRUD, and instant UI. - -### Unified IndexedDB Architecture - -The Mana unified app uses a **single IndexedDB** (`mana`) containing all 120+ collections from all apps. Table names that collide across apps are prefixed (e.g., `todoProjects`, `cardDecks`, `presiDecks`). - -``` -┌─────────────────────────────────────────────┐ -│ Unified IndexedDB: "mana" │ -│ │ -│ tasks, todoProjects, labels, ... (todo) │ -│ calendars, events (calendar) │ -│ contacts (contacts) │ -│ conversations, messages (chat) │ -│ ... 120+ collections across 27 apps │ -│ │ -│ _pendingChanges (tagged with appId) │ -│ _syncMeta (keyed by [appId+coll]) │ -└──────────────────┬──────────────────────────┘ - │ Dexie hooks auto-track - │ all writes as pending changes - ▼ -┌──────────────────────────────────────────────┐ -│ Unified Sync Engine (sync.ts) │ -│ One sync channel per appId │ -│ POST /sync/{appId} (push) │ -│ GET /sync/{appId}/pull (pull) │ -│ WS /ws/{appId} (real-time notifications) │ -└──────────────────┬───────────────────────────┘ - ▼ - mana-sync (Go) - PostgreSQL (sync_changes) -``` - -#### Key Files - -| File | Purpose | -|------|---------| -| `apps/mana/apps/web/src/lib/data/database.ts` | Unified Dexie DB, SYNC_APP_MAP, table name mappings, Dexie hooks | -| `apps/mana/apps/web/src/lib/data/sync.ts` | Unified sync engine (push/pull/WS per appId) | -| `apps/mana/apps/web/src/lib/data/legacy-migration.ts` | One-time migration from old per-app DBs | -| `packages/local-store/` | Standalone local-store (used by individual apps, not the unified app) | -| `services/mana-sync/` | Go sync server (WebSocket push, field-level LWW) | - -#### How Sync Works - -1. Module stores write directly to Dexie tables (`db.table('tasks').add(...)`) -2. Dexie hooks in `database.ts` automatically record each write to `_pendingChanges` with the correct `appId` -3. The unified sync engine groups pending changes by `appId` and pushes to `POST /sync/{appId}` -4. Table names are mapped between unified names (e.g., `todoProjects`) and backend names (e.g., `projects`) via `TABLE_TO_SYNC_NAME` -5. Server changes are pulled per collection and applied with a guard flag to prevent re-sync loops - -#### Adding a New App Module - -1. Add table definitions to `database.ts` schema (in `db.version(1).stores({...})`) -2. Add table-to-appId mapping in `SYNC_APP_MAP` -3. Add any renamed tables to `TABLE_TO_SYNC_NAME` -4. Create module in `src/lib/modules/{app}/` with collections, queries, stores -5. Dexie hooks automatically handle change tracking — no manual `trackChange()` needed - -### Standalone Apps (Legacy) - -Individual apps in `apps/*/apps/web/` still use `@mana/local-store` with per-app IndexedDB databases (`mana-{appId}`). When users first open the unified Mana app, `legacy-migration.ts` migrates data from these old DBs into the unified DB. - -### At-Rest Encryption (Phase 1–9) - -User-typed content in **27 tables** is encrypted with **AES-GCM-256** before it touches IndexedDB. The master key lives in mana-auth (KEK-wrapped) and is fetched on login. Two trust modes: - -- **Standard mode** (default): server holds the user's master key wrapped with `MANA_AUTH_KEK` (env-provided). Mana operators with KEK access could theoretically decrypt; protected against all realistic threats except court-ordered disclosure. -- **Zero-Knowledge mode** (opt-in via Settings → Sicherheit): user generates a 32-byte recovery code locally, the server NULLs out the KEK wrap. Mana is then **computationally incapable** of decrypting user data — losing the recovery code means losing the data, by design. - -**Production requirement:** `MANA_AUTH_KEK` must be set on the mana-auth service to a base64-encoded 32-byte value (`openssl rand -base64 32`). The dev fallback is 32 zero bytes and prints a loud warning at startup. - -**When writing module code that touches sensitive fields:** - -1. Add the table to `apps/mana/apps/web/src/lib/data/crypto/registry.ts` with the field allowlist -2. Wrap writes with `await encryptRecord(tableName, record)` before `table.add()` / `table.update()` -3. Wrap reads with `decryptRecords(tableName, visible)` after the Dexie query, before the type converter -4. Default to **encrypt** for user-typed text; default to **plaintext** for IDs / timestamps / sort keys / enum discriminators - -**Reference docs:** - -| File | Purpose | -|------|---------| -| `apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md` | Complete sprint history, rollout phases, threat model, backlog | -| `apps/docs/src/content/docs/architecture/security.mdx` | User-facing walkthrough + threat model table | -| `services/mana-auth/sql/002_encryption_vaults.sql` | Vault schema + RLS policies | -| `services/mana-auth/sql/003_recovery_wrap.sql` | Phase 9 recovery columns + CHECK constraints | - -### Dev Commands (Local-First Stack) - -```bash -pnpm dev:sync # Go sync server (port 3050) -pnpm dev:sync:build # Compile Go binary -pnpm dev:todo:server # Hono/Bun compute server (port 3019) -pnpm dev:todo:local # Web + sync + server (no auth needed) -pnpm dev:todo:full # Everything incl. auth + DB setup -``` +**CRITICAL**: Parent workspace packages (e.g. `apps/chat/package.json`) must NEVER define `type-check`, `build`, or `lint` scripts that call `turbo run `. 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/local-store` | Local-first data layer (Dexie.js + sync engine) | -| `@mana/shared-hono` | Shared Hono middleware (auth, health, errors) | -| `@mana/shared-auth` | Client-side auth service for web/mobile apps | -| `@mana/shared-storage` | S3-compatible storage (MinIO) | -| `@mana/shared-types` | Common TypeScript types | -| `@mana/shared-utils` | Utility functions | -| `@mana/shared-ui` | React Native UI components | -| `@mana/shared-theme` | Theme configuration | -| `@mana/shared-i18n` | Internationalization | - -Import shared packages: - -```typescript -import { createAuthService } from '@mana/shared-auth'; -import { formatDate, truncate } from '@mana/shared-utils'; -``` - -## Database Architecture (PostgreSQL) - -All backend data lives in **2 PostgreSQL databases**: - -| Database | Purpose | Tech | -|----------|---------|------| -| **mana_platform** | All services + app server-side tables | Drizzle ORM, pgSchema isolation | -| **mana_sync** | Sync engine (write-heavy, append-only) | Go + pgx | - -### Schema Mapping (mana_platform) - -Each service owns a PostgreSQL schema within the shared database: - -| Schema | Service | Tables | -|--------|---------|--------| -| `auth` | mana-auth | users, sessions, orgs, passkeys | -| `credits` | mana-credits | balances, transactions, packages | -| `gifts` | mana-credits | gift codes, redemptions | -| `subscriptions` | mana-subscriptions | plans, invoices | -| `feedback` | mana-analytics | user feedback, votes | -| `usr` | mana-user | settings, tags, tag groups | -| `media` | mana-media | CAS, thumbnails, references | -| `todo` | todo server | tasks, projects, reminders | -| `traces` | traces server | locations, cities, POIs, guides | -| `presi` | presi server | decks, slides, themes, shares | -| `uload` | uload server | clicks only (links via sync_changes) | -| `cards` | cards package | decks, cards, study progress | - -### Using pgSchema - -All tables must use `pgSchema('name').table()` — never plain `pgTable()`: - -```typescript -import { pgSchema } from 'drizzle-orm/pg-core'; - -const mySchema = pgSchema('myapp'); -export const items = mySchema.table('items', { ... }); -``` - -### Adding a New Schema - -1. Add schema to `docker/init-db/01-create-databases.sql` -2. Add schema to `scripts/setup-databases.sh` (PLATFORM_SCHEMAS array) -3. Create `drizzle.config.ts` with `schemaFilter: ['myschema']` -4. All DATABASE_URLs point to `mana_platform` (one DB for everything) - -## Object Storage (MinIO) - -S3-compatible object storage for file uploads, generated images, etc. - -### Architecture - -| Environment | Service | Purpose | -|-------------|---------|---------| -| **Local + Production** | MinIO (Docker) | S3-compatible storage | - -### Local Development - -```bash -# Start infrastructure (includes MinIO) -pnpm docker:up - -# MinIO Web Console: http://localhost:9001 -# Username: minioadmin -# Password: minioadmin - -# S3 API endpoint: http://localhost:9000 -``` - -### Pre-configured Buckets - -| Bucket | Project | Purpose | -|--------|---------|---------| -| `picture-storage` | Picture | AI-generated images | -| `chat-storage` | Chat | User file uploads | -| `cards-storage` | Cards | Card/deck assets | -| `nutriphi-storage` | NutriPhi | Meal photos | -| `presi-storage` | Presi | Presentation slides | -| `calendar-storage` | Calendar | Calendar attachments | -| `contacts-storage` | Contacts | Contact avatars/files | -| `storage-storage` | Storage | Cloud drive files | - -### Usage in Server - -```typescript -import { createPictureStorage, generateUserFileKey, getContentType } from '@mana/shared-storage'; - -const storage = createPictureStorage(); - -// Upload -const key = generateUserFileKey(userId, 'image.png'); -const result = await storage.upload(key, buffer, { - contentType: getContentType('image.png'), - public: true, -}); - -// Download -const data = await storage.download(key); - -// Presigned URLs -const uploadUrl = await storage.getUploadUrl(key, { expiresIn: 3600 }); -``` - -### Environment Variables - -```env -# MinIO (local + production via Docker) -S3_ENDPOINT=http://localhost:9000 -S3_REGION=us-east-1 -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -``` - -## Landing Pages (Cloudflare Pages) - -All landing pages are deployed to Cloudflare Pages using Direct Upload via Wrangler CLI. - -### Landing Pages - -| Project | Package | Cloudflare Project | URL | -|---------|---------|-------------------|-----| -| Chat | `@chat/landing` | `chat-landing` | https://chat-landing.pages.dev | -| Picture | `@picture/landing` | `picture-landing` | https://picture-landing.pages.dev | -| Mana | `@mana/landing` | `mana-landing` | https://mana-landing.pages.dev | -| Cards | `@cards/landing` | `cards-landing` | https://cards-landing.pages.dev | -| Zitare | `@zitare/landing` | `zitare-landing` | https://zitare-landing.pages.dev | - -### Local Deployment - -```bash -# First time: Login to Cloudflare -pnpm cf:login - -# Create projects (one-time setup) -pnpm cf:projects:create - -# Deploy individual landing page -pnpm deploy:landing:chat -pnpm deploy:landing:picture -pnpm deploy:landing:mana -pnpm deploy:landing:cards -pnpm deploy:landing:zitare - -# Deploy all landing pages -pnpm deploy:landing:all - -# List all projects -pnpm cf:projects:list -``` - -### Adding New Landing Pages - -1. Create the landing page in `apps/{project}/apps/landing/` -2. Add `wrangler.toml`: - ```toml - name = "{project}-landing" - compatibility_date = "2024-12-01" - pages_build_output_dir = "dist" - ``` -3. Add deploy script to root `package.json`: - ```json - "deploy:landing:{project}": "pnpm --filter @{project}/landing build && npx wrangler pages deploy apps/{project}/apps/landing/dist --project-name={project}-landing" - ``` -4. Create Cloudflare project: `npx wrangler pages project create {project}-landing --production-branch=main` - -### Custom Domains - -```bash -# Add custom domain to a project -npx wrangler pages project add-domain chat-landing chat.mana.how -``` - -### Organization Landing Pages - -Organizations can have their own landing pages at `{slug}.mana.how`, built and deployed automatically by the **mana-landing-builder** service. - -```bash -# Start the builder service -pnpm dev:landing-builder -``` - -**How it works:** -1. Org admin configures landing page at `/organizations/{id}/landing` in the Mana web dashboard -2. Config is stored in `organizations.metadata.landingPage` (mana-auth) -3. On publish, the builder service generates a static Astro site from the config -4. Site is deployed to Cloudflare Pages as `org-{slug}` → `{slug}.mana.how` - -**Available themes:** `classic` (dark, professional), `warm` (light, inviting) -**Available sections:** Hero, About/Features, Team, Contact, Footer - -See `services/mana-landing-builder/CLAUDE.md` for full documentation. - -## ManaScore (Production Readiness) - -ManaScore is the internal quality assessment system for all Mana apps. Each app is rated on a 0-100 scale across 8 categories plus extended metrics. - -**Location:** `apps/mana/apps/landing/src/content/manascore/` -**Live:** https://mana-landing.pages.dev/manascore -**Methodology:** https://mana-landing.pages.dev/manascore/about - -### Core Categories (8) - -Backend, Frontend, Database, Testing, Deployment, Documentation, Security, UX - -### Extended Metrics - -| Metric | Description | -|--------|-------------| -| **Score Trend** | Historical score changes with sparkline visualization | -| **Lighthouse** | Performance, Accessibility, Best Practices, SEO | -| **Dependency Health** | Outdated packages, vulnerabilities by severity | -| **API Conformity** | 7 checks (responses, errors, pagination, versioning, docs, health, validation) | -| **Cross-App Consistency** | Shared package usage (auth, ui, theme, branding, i18n, error-tracking) | -| **Analytics Maturity** | 5 checks (page views, custom events, auth tracking, landing, dashboard) | - -### Ecosystem Health Score - -Measures consistency and unification **across all apps** (vs. ManaScore which rates each app individually). - -**Dashboard:** `/manascore/ecosystem` -**Script:** `node scripts/ecosystem-audit.mjs` — scans the monorepo, generates `ecosystem-health.json` - -12 metrics with weighted average: Shared Packages (20%), Icon Consistency (10%), i18n (10%), Styles (10%), Local-First (8%), Error Boundaries (8%), TypeScript Strict (7%), Tests (7%), Modals (5%), Error Handling (5%), PWA (5%), Maintainability (5%). - -### Maturity Levels - -| Level | Score | Meaning | -|-------|-------|---------| -| Prototype | 0-25 | Proof of concept | -| Alpha | 26-50 | Basic functionality | -| Beta | 51-70 | Functional with gaps | -| Production | 71-85 | Stable, deployable | -| Mature | 86-100 | Fully production-ready | - -## Server Access - -### Mac Mini Production Server - -The production environment runs on a Mac Mini, accessible via Cloudflare Tunnel. - -**Domain:** mana.how -**SSH:** `ssh mana-server` (requires cloudflared and SSH config) - -```bash -# SSH config (~/.ssh/config) -Host mana-server - HostName mac-mini.mana.how - User till - ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h -``` - -#### Useful Commands - -```bash -ssh mana-server # Connect to server -cd ~/projects/mana-monorepo - -./scripts/mac-mini/status.sh # Check all services -./scripts/mac-mini/deploy.sh # Pull & restart containers -./scripts/mac-mini/build-app.sh todo-web # Build & deploy single app -./scripts/mac-mini/build-app.sh --base # Rebuild base images -./scripts/mac-mini/health-check.sh # Run health checks -docker compose -f docker-compose.macmini.yml logs -f # View logs -``` - -#### Docker Base Images - -All apps build on shared base images to reduce build time and memory usage: - -- **`sveltekit-base:local`** (`docker/Dockerfile.sveltekit-base`) — All shared packages for SvelteKit web apps -- **`hono-server`** (`docker/Dockerfile.hono-server`) — Hono/Bun compute server template - -Rebuild base images after shared package changes: `./scripts/mac-mini/build-app.sh --base` - -For detailed server documentation, see **[docs/MAC_MINI_SERVER.md](docs/MAC_MINI_SERVER.md)**. - -### Windows GPU Server - -A Windows PC with an NVIDIA RTX 3090 (24 GB VRAM) handles GPU-intensive AI workloads. - -**Hostname:** mana-server-gpu -**IP:** 192.168.178.11 (LAN only, no Cloudflare Tunnel yet) -**SSH:** `ssh mana-gpu` - -```bash -# SSH config (~/.ssh/config) -Host mana-gpu - HostName 192.168.178.11 - User tills -``` - -**Hardware:** NVIDIA GeForce RTX 3090, 24 GB VRAM, CUDA 12.9 -**Software:** Python 3.11, Git, Windows 11 -**Working directory:** `C:\mana\` (services, venvs, models) - -**Planned services:** -- Ollama (LLM inference with GPU acceleration) -- Mana STT (Speech-to-Text, Port 3020) -- Mana TTS (Text-to-Speech, Port 3022) -- Mana Image Gen (FLUX image generation, Port 3023) -- Mana Video Gen (LTX-Video generation, Port 3026) - -For setup documentation, see **[docs/WINDOWS_GPU_SERVER_SETUP.md](docs/WINDOWS_GPU_SERVER_SETUP.md)**. +| 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` | Legacy local-first store (standalone apps only — unified Mana uses its own) | ## Adding Dependencies ```bash -# Add to workspace root (dev tools only) -pnpm add -D -w - -# Add to specific project -pnpm add --filter memoro - -# Add to specific app within project -pnpm add --filter @memoro/mobile - -# Add to shared package -pnpm add --filter @mana/shared-utils +pnpm add -D -w # Workspace root (dev tools) +pnpm add --filter @mana/web # A specific app +pnpm add --filter @mana/shared-utils # A shared package ``` ## Environment Variables -### Centralized Development Environment +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`](docs/ENVIRONMENT_VARIABLES.md). -All development environment variables are managed from a single file: `.env.development` +## Server Access -```bash -# First-time setup: generates all app-specific .env files -pnpm setup:env +- **Production (Mac Mini):** `ssh mana-server` (Cloudflare Tunnel). See [`docs/MAC_MINI_SERVER.md`](docs/MAC_MINI_SERVER.md). Useful: `./scripts/mac-mini/status.sh`, `./scripts/mac-mini/deploy.sh`, `./scripts/mac-mini/build-app.sh `. +- **GPU server (Windows, RTX 3090):** `ssh mana-gpu` (192.168.178.11, LAN only). Hosts STT/TTS/image-gen/video-gen/Ollama. See [`docs/WINDOWS_GPU_SERVER_SETUP.md`](docs/WINDOWS_GPU_SERVER_SETUP.md). -# This also runs automatically after `pnpm install` -``` +## Reference Docs -The script reads `.env.development` and generates platform-specific `.env` files for each app with the correct prefixes: - -- **Expo mobile**: `EXPO_PUBLIC_*` prefix -- **SvelteKit web**: `PUBLIC_*` prefix -- **Hono/Bun server**: No prefix - -### Key Files - -- `.env.development` - Central source of truth (committed to git) -- `scripts/generate-env.mjs` - Generation script -- `apps/**/apps/**/.env` - Generated files (gitignored) - -### Adding New Variables - -1. Add the variable to `.env.development` -2. Update `scripts/generate-env.mjs` to map it to the appropriate apps -3. Run `pnpm setup:env` to regenerate - -### Platform Prefix Patterns - -**Mobile (Expo):** - -``` -EXPO_PUBLIC_SUPABASE_URL=... -EXPO_PUBLIC_SUPABASE_ANON_KEY=... -EXPO_PUBLIC_MIDDLEWARE_API_URL=... -``` - -**Web (SvelteKit):** - -``` -PUBLIC_SUPABASE_URL=... -PUBLIC_SUPABASE_ANON_KEY=... -``` - -**Server (Hono/Bun):** - -``` -PORT=... -DATABASE_URL=... -MANA_AUTH_URL=... -``` - -## Project-Specific Documentation - -- **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)** - Database setup and `dev:*:full` commands -- **[docs/ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md)** - Complete environment setup guide -- **[docs/DATABASE_MIGRATIONS.md](docs/DATABASE_MIGRATIONS.md)** - Migration best practices, CI/CD, rollback procedures - -Each project has its own `CLAUDE.md` with detailed information: - -- `apps/mana/CLAUDE.md` - Multi-app ecosystem, auth details -- `apps/cards/CLAUDE.md` - Card/deck management -- `apps/chat/CLAUDE.md` - Chat API endpoints, AI models -- `apps/picture/CLAUDE.md` - AI image generation -- `services/mana-auth/` - Central authentication service -- `services/mana-search/CLAUDE.md` - Search & content extraction service (Go) -- `services/mana-crawler/CLAUDE.md` - Web crawler service (Go) -- `services/mana-notify/CLAUDE.md` - Notification service (Go) -- `services/mana-llm/CLAUDE.md` - Central LLM abstraction service -- `services/mana-landing-builder/CLAUDE.md` - Org landing page builder service - -Navigate to the specific project directory to work on it. - -## Code Quality Infrastructure (TODO) - -A detailed plan for code quality tooling is available at `.claude/plans/proud-dancing-moon.md`. When ready to implement: - -### Planned Setup - -- **Pre-commit hooks**: Husky + lint-staged (format + lint on commit) -- **Commit messages**: Commitlint with Conventional Commits (`feat:`, `fix:`, `docs:`, etc.) -- **CI Pipeline**: GitHub Actions PR checks (lint, format, type-check, tests) -- **Formatting**: Tabs, single quotes, 100 char width (unified across all projects) -- **Test coverage**: 80% minimum for new code (once testing infrastructure is in place) - -### Key Files to Create - -``` -.husky/pre-commit # Run lint-staged -.husky/commit-msg # Run commitlint -commitlint.config.js # Conventional commit rules -.github/workflows/pr-check.yml # CI pipeline -packages/eslint-config/ # Shared ESLint configuration -``` - -### Current State - -- Testing: ~128 test files across all projects (unit + e2e + integration) -- Linting: Husky + lint-staged on pre-commit (format + lint) -- CI: PR validation (type-check, lint, format, tests) + CD pipeline with deploy tracking -- Pre-commit: Husky runs lint-staged on every commit +| Path | When you need it | +|------|------------------| +| [`apps/mana/CLAUDE.md`](apps/mana/CLAUDE.md) | **Default** — module pattern, routing, encryption usage | +| [`apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md`](apps/mana/apps/web/src/lib/data/DATA_LAYER_AUDIT.md) | Sync engine deep-dive, encryption rollout, threat model | +| [`docs/LOCAL_DEVELOPMENT.md`](docs/LOCAL_DEVELOPMENT.md) | First-time setup, `dev:*:full` commands | +| [`docs/ENVIRONMENT_VARIABLES.md`](docs/ENVIRONMENT_VARIABLES.md) | All env vars | +| [`docs/DATABASE_MIGRATIONS.md`](docs/DATABASE_MIGRATIONS.md) | Migration workflow + rollback | +| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | Production deployment | +| [`docs/PORT_SCHEMA.md`](docs/PORT_SCHEMA.md) | Which service runs on which port | +| Service-specific `CLAUDE.md` files | Service internals | diff --git a/apps/mana/CLAUDE.md b/apps/mana/CLAUDE.md index 73257f69e..60b2a38fe 100644 --- a/apps/mana/CLAUDE.md +++ b/apps/mana/CLAUDE.md @@ -1,46 +1,58 @@ -# CLAUDE.md +# CLAUDE.md — Mana Unified App -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Project-level guidance for `apps/mana/`. For monorepo-wide patterns (auth, services, dev commands, env vars), see the **[root `CLAUDE.md`](../../CLAUDE.md)**. ## Project Overview -**Mana** is the unified web application serving 27+ product modules (todo, calendar, contacts, chat, notes, dreams, memoro, cards, picture, presi, music, storage, …) under one SvelteKit app, one IndexedDB, one auth session, and one deployment at **mana.how**. +**Mana** is the unified web app at **mana.how**, serving 27+ product modules (todo, calendar, contacts, chat, notes, dreams, memoro, cards, picture, presi, music, storage, …) under one SvelteKit build, one IndexedDB, one auth session, one deployment. -- **Web App** (`apps/web`): SvelteKit 2 + Svelte 5 unified app — the main surface -- **Mobile App** (`apps/mobile`): Expo / React Native (work-in-progress, separate codebase) -- **Landing** (`apps/landing`): Astro static landing page deployed to Cloudflare Pages +``` +apps/mana/apps/ +├── web/ # SvelteKit 2 + Svelte 5 unified app — the main surface +├── mobile/ # Expo / React Native (lower priority, may lag) +└── landing/ # Astro static landing → Cloudflare Pages +``` -## Architecture +## Module System -### Unified Module System - -All modules share one SvelteKit build. Each module lives in `apps/web/src/lib/modules/{name}/` and registers itself via a `module.config.ts` file. The data layer uses a single Dexie IndexedDB (`mana`) containing all 120+ collections from every module — table names that collide across modules are prefixed (e.g. `todoProjects`, `cardDecks`, `presiDecks`). - -Module state lives in three files per module: +Each module lives in `apps/web/src/lib/modules/{name}/` and registers itself via `module.config.ts`. Module state is split into three files: | File | Role | |------|------| | `collections.ts` | Dexie table references + (sometimes) seed data | -| `queries.ts` | Read-side: Dexie liveQuery hooks, type converters, pure helpers for `$derived` | -| `stores/*.svelte.ts` | Write-side: mutation methods, no reads (those go through queries.ts) | +| `queries.ts` | **Read-side** — Dexie liveQuery hooks, type converters, pure helpers for `$derived` | +| `stores/*.svelte.ts` | **Write-side** — mutation methods. Never reads for UI rendering (queries.ts does that). Only reads when a mutation needs existing state (toggle, increment). | -### Authentication +### Module store pattern -**Mana Auth** is the central authentication service (`services/mana-auth/`, port 3001), built on Better Auth + Hono + Bun. The web app talks to it via the shared `@mana/shared-auth` client. +```typescript +// modules/todo/stores/tasks.svelte.ts +export const tasksStore = { + async createTask(input: {...}) { + const newLocal: LocalTask = { ...input, id: crypto.randomUUID() }; + const plaintextSnapshot = toTask({ ...newLocal }); + await encryptRecord('tasks', newLocal); + await taskTable.add(newLocal); + return plaintextSnapshot; + }, +}; +``` -- **Token format**: EdDSA JWT with minimal claims (`sub`, `email`, `role`, `sid`, `tier`) -- **Session storage**: Cookies (`*.mana.how` domain) + JWT in memory -- **Route protection**: `(app)` group is auth-gated via the `AuthGate` component in the layout -- **Cross-app SSO**: Same Mana Auth session works across all `*.mana.how` apps -- **Access tiers**: `guest` < `public` < `beta` < `alpha` < `founder` — apps can require a minimum tier via `mana-apps.ts` +```typescript +// modules/todo/queries.ts +export function useAllTasks() { + return useLiveQueryWithDefault(async () => { + const locals = await db.table('tasks').orderBy('order').toArray(); + const visible = locals.filter((t) => !t.deletedAt); + const decrypted = await decryptRecords('tasks', visible); + return decrypted.map(toTask); + }, [] as Task[]); +} +``` -The legacy Supabase integration was removed. Anything mentioning `@supabase/ssr`, `safeGetSession()`, or `event.locals.supabase` is a leftover from a much earlier iteration and should be deleted on sight. +## Data Layer (Local-First) -### Data Layer (Local-First) - -The app reads and writes to IndexedDB **first**, then syncs to the server in the background via the **mana-sync** Go service (port 3050). - -Architecture diagram: +The app reads and writes IndexedDB **first**, then syncs to **mana-sync** (Go, port 3050) in the background. One Dexie database (`mana`) holds 120+ collections from every module — colliding table names get a module prefix (e.g. `todoProjects`, `cardDecks`, `presiDecks`). ``` User action (e.g. tasksStore.createTask) @@ -49,16 +61,16 @@ User action (e.g. tasksStore.createTask) Module store builds the LocalRecord │ ▼ -encryptRecord(tableName, record) ← Phase 4–9 encryption layer +encryptRecord(tableName, record) │ ▼ -table.add(encryptedRecord) ← Dexie write +table.add(encryptedRecord) ← Dexie write │ ▼ Dexie hooks (database.ts): - stamp userId - stamp __fieldTimestamps per field - - record into _pendingChanges + - record into _pendingChanges (tagged with appId) - record into _activity │ ▼ @@ -76,13 +88,11 @@ Other clients pull via SSE / polling applyServerChanges → Dexie hooks (suppressed) → liveQuery → decryptRecord → UI ``` -For the deep dive — sync engine, retry/backoff, quota recovery, telemetry, RLS, encryption rollout — read **`apps/web/src/lib/data/DATA_LAYER_AUDIT.md`**. This is the single most important file for understanding how the app works under the hood. +**Deep dive**: [`apps/web/src/lib/data/DATA_LAYER_AUDIT.md`](apps/web/src/lib/data/DATA_LAYER_AUDIT.md) — sync engine, retry/backoff, quota recovery, telemetry, RLS, encryption rollout, threat model. **Single most important file for understanding how the app works under the hood.** -### At-Rest Encryption +## At-Rest Encryption -User-typed content in **27 tables** is encrypted with **AES-GCM-256** before it touches IndexedDB. The master key lives in `mana-auth` (KEK-wrapped) and is fetched on login. - -Two trust modes: +User-typed content in **27 tables** is encrypted with **AES-GCM-256** before it touches IndexedDB. Master key lives in `mana-auth` (KEK-wrapped) and is fetched on login. | Mode | Default | What Mana can decrypt | |------|---------|----------------------| @@ -92,15 +102,15 @@ Two trust modes: **When writing module code that touches sensitive fields:** 1. Add the table to `apps/web/src/lib/data/crypto/registry.ts` with the field allowlist -2. Wrap writes: `await encryptRecord(tableName, record)` before `table.add()` / `table.update()` -3. Wrap reads: `decryptRecords(tableName, visible)` after the Dexie query, before the type converter +2. `await encryptRecord(tableName, record)` before `table.add()` / `table.update()` +3. `await decryptRecords(tableName, visible)` after the Dexie query, before the type converter 4. The Dexie hook in `database.ts` does NOT auto-encrypt — every store does it explicitly. This is by design (Web Crypto is async, hooks are sync). -For new sensitive fields, default to **encrypt**. For new structural fields (IDs, timestamps, enums, sort/filter keys), default to **plaintext**. +Defaults: **encrypt** for new user-typed text fields; **plaintext** for IDs / timestamps / sort keys / enum discriminators. -User-facing docs at `apps/docs/src/content/docs/architecture/security.mdx`. +User-facing docs: `apps/docs/src/content/docs/architecture/security.mdx`. -### Routing Structure +## Routing ``` apps/web/src/routes/ @@ -111,148 +121,55 @@ apps/web/src/routes/ │ │ └── security/ # Vault status + recovery code + ZK opt-in │ ├── todo/ # …and many more module routes │ └── … -├── workbench/ # Multi-pane interface (ListView + DetailView per module) └── api/ # SvelteKit API endpoints (rare; most data is local-first) ``` -### Path Aliases +The `(app)` group is wrapped by `AuthGate`, which redirects unauthenticated users to `/login` and reads the access tier from the JWT to gate beta/alpha/founder-only modules. -Defined in `apps/web/svelte.config.js`: +**Legacy Supabase**: removed. Anything mentioning `@supabase/ssr`, `safeGetSession()`, or `event.locals.supabase` is leftover from a much earlier iteration and should be deleted on sight. -- `$lib` → `src/lib` -- `$components` → `src/lib/components` -- `$stores` → `src/lib/stores` -- `$utils` → `src/lib/utils` -- `$types` → `src/lib/types` -- `$server` → `src/lib/server` +## Path Aliases (`apps/web/svelte.config.js`) + +`$lib` → `src/lib` · `$components` → `src/lib/components` · `$stores` → `src/lib/stores` · `$utils` → `src/lib/utils` · `$types` → `src/lib/types` · `$server` → `src/lib/server` + +## Auth Access Pattern + +Auth state lives in `$lib/stores/auth.svelte.ts`. The current user id is also pushed into `$lib/data/current-user.ts` so the Dexie creating-hook can auto-stamp `userId` on every record. **Module stores never need to know who the current user is** — they just write, and the hook stamps the right userId. ## Development Commands -### Web App (apps/web) +For full local-dev (Mana Auth + mana-sync + web together), use the root-level `pnpm run mana:dev` or `pnpm dev:*:full` commands. See [root `CLAUDE.md`](../../CLAUDE.md) and [`docs/LOCAL_DEVELOPMENT.md`](../../docs/LOCAL_DEVELOPMENT.md). + +Web-app-only: ```bash -cd apps/web - -# Development -pnpm dev # Start dev server on port 5173 - -# Building -pnpm build # Build for production -pnpm preview # Preview production build - -# Code Quality -pnpm check # Type-check with svelte-check -pnpm lint # Format check + ESLint -pnpm format # Prettier write - -# Testing -pnpm test # Run Vitest unit tests -pnpm test:ui # Run Vitest with UI -pnpm test:e2e # Run Playwright E2E tests +cd apps/mana/apps/web +pnpm dev # Dev server on :5173 +pnpm build # Production build +pnpm preview # Preview production build +pnpm check # svelte-check type check +pnpm lint # Format check + ESLint +pnpm format # Prettier write +pnpm test # Vitest (unit + integration with fake-indexeddb) +pnpm test:e2e # Playwright ``` -For full local-dev setup (Mana Auth + mana-sync + web together), use the root-level `dev:*:full` commands. See `docs/LOCAL_DEVELOPMENT.md` and the root `CLAUDE.md`. +## Tech Stack -### Mobile App (apps/mobile) - -The mobile app is currently lower-priority than the web app and may lag behind in features. Standard Expo commands apply (`pnpm start`, `pnpm ios`, `pnpm android`, EAS builds for production). - -## Environment Configuration - -Generated automatically from the root `.env.development` via `pnpm setup:env`. The relevant variables for the web app: - -```env -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -PUBLIC_MANA_SYNC_URL=http://localhost:3050 -``` - -The web app does NOT read or store any database credentials directly — all server-side data goes through Mana Auth + mana-sync. See the root `CLAUDE.md` for the full env-var rundown. - -## Technology Stack - -### Web App - -- **Framework**: SvelteKit 2 + Svelte 5 (runes mode throughout) -- **Styling**: TailwindCSS +- **Web**: SvelteKit 2 + Svelte 5 (runes mode), TailwindCSS, Vite - **Auth**: Mana Auth (Better Auth + EdDSA JWT) via `@mana/shared-auth` -- **Data**: Local-first with Dexie.js + mana-sync (Go) backend -- **Encryption**: AES-GCM-256 via Web Crypto, server-wrapped MK with optional zero-knowledge mode -- **Testing**: Vitest (unit + integration with fake-indexeddb), Playwright (E2E) -- **Build**: Vite +- **Data**: Dexie.js (local-first) + mana-sync (Go) backend +- **Encryption**: AES-GCM-256 via Web Crypto, server-wrapped MK with optional zero-knowledge +- **Testing**: Vitest, Playwright +- **Mobile**: Expo, Expo Router, NativeWind, EAS Build -### Mobile App - -- **Framework**: Expo + React Native -- **Routing**: Expo Router (file-based) -- **Styling**: NativeWind -- **Build**: EAS Build - -## Important Patterns - -### Module Store Pattern - -Every module has a mutation-only store that handles writes + a queries file that handles reads. The store NEVER reads from Dexie via `await table.toArray()` for UI rendering — that's the queries file's job (via liveQuery hooks). The store only reads when it needs to mutate based on existing state (e.g. toggle, increment). - -```typescript -// modules/todo/stores/tasks.svelte.ts -import { taskTable } from '../collections'; -import { encryptRecord } from '$lib/data/crypto'; -import { toTask } from '../queries'; - -export const tasksStore = { - async createTask(input: {...}) { - const newLocal: LocalTask = { ...input, id: crypto.randomUUID() }; - const plaintextSnapshot = toTask({ ...newLocal }); - await encryptRecord('tasks', newLocal); - await taskTable.add(newLocal); - return plaintextSnapshot; - }, - // ... -}; -``` - -```typescript -// modules/todo/queries.ts -export function useAllTasks() { - return useLiveQueryWithDefault(async () => { - const locals = await db.table('tasks').orderBy('order').toArray(); - const visible = locals.filter((t) => !t.deletedAt); - const decrypted = await decryptRecords('tasks', visible); - return decrypted.map(toTask); - }, [] as Task[]); -} -``` - -### Svelte 5 Runes Mode - -All routes and components use Svelte 5 runes: - -```typescript -// CORRECT -let count = $state(0); -let doubled = $derived(count * 2); -$effect(() => { - console.log(count); -}); - -// WRONG (legacy Svelte 3/4) -let count = 0; -$: doubled = count * 2; -``` - -### Auth Access - -Auth state lives in `$lib/stores/auth.svelte.ts`. The current user id is also pushed into the data layer's `current-user.ts` so the Dexie creating-hook can auto-stamp `userId` on every record. Module stores never need to know who the current user is. - -### Route Protection - -The `(app)` group is wrapped by an `AuthGate` component that redirects unauthenticated users to `/login` and reads the access tier from the JWT to gate beta/alpha/founder-only modules. +Svelte 5 runes are mandatory — no legacy `let count = 0; $: doubled = count * 2`. Always `$state`, `$derived`, `$effect`. See [`.claude/guidelines/sveltekit-web.md`](../../.claude/guidelines/sveltekit-web.md). ## Reference Documents | Path | Purpose | |------|---------| -| `apps/web/src/lib/data/DATA_LAYER_AUDIT.md` | Complete data-layer architecture, sync engine, encryption rollout, threat model, backlog | -| `apps/docs/src/content/docs/architecture/security.mdx` | User-facing security & encryption walkthrough | +| [`apps/web/src/lib/data/DATA_LAYER_AUDIT.md`](apps/web/src/lib/data/DATA_LAYER_AUDIT.md) | Data-layer + sync deep dive, encryption rollout, threat model, backlog | +| `apps/docs/src/content/docs/architecture/security.mdx` | User-facing security walkthrough | | `apps/docs/src/content/docs/architecture/authentication.mdx` | Auth flow + JWT structure | -| Root `CLAUDE.md` | Monorepo overview, dev commands, sibling services | +| [Root `CLAUDE.md`](../../CLAUDE.md) | Monorepo overview, services, dev commands, env vars |