managarten/CLAUDE.md
Till JS b76746229e chore: remove @manacore/shared-supabase package
Package was unused — no imports found across the entire codebase.
All apps have migrated to direct PostgreSQL (Drizzle ORM) for backends
and mana-core-auth API for mobile/web clients.

Removes package and all documentation references.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:07:14 +01:00

26 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Monorepo Overview

This is a pnpm workspace monorepo containing multiple product applications with shared packages. All projects use Supabase for database/auth and follow similar architectural patterns.

Package Manager: pnpm 9.15.0 (use pnpm for all commands) Build System: Turborepo Node Version: 20+

Detailed Guidelines

For comprehensive guidelines on code patterns and conventions, see the .claude/ directory:

Document Purpose
.claude/GUIDELINES.md Main reference overview
.claude/guidelines/code-style.md Formatting, naming, linting
.claude/guidelines/database.md Drizzle ORM, schema patterns
.claude/guidelines/testing.md Jest/Vitest, mock factories
.claude/guidelines/nestjs-backend.md Controllers, services, DTOs
.claude/guidelines/error-handling.md Go-style Result types, error codes
.claude/guidelines/sveltekit-web.md Svelte 5 runes, stores
.claude/guidelines/expo-mobile.md React Native, NativeWind
.claude/guidelines/authentication.md Mana Core Auth integration
.claude/guidelines/design-ux.md UI patterns, animations, a11y

Always consult these guidelines before making changes.

Projects

Project Description Apps
manacore Multi-app ecosystem platform Expo mobile, SvelteKit web
manadeck Card/deck management NestJS backend, Expo mobile, SvelteKit web
picture AI image generation Expo mobile, SvelteKit web, Astro landing
chat AI chat application NestJS backend, Expo mobile, SvelteKit web, Astro landing
zitare Daily inspiration quotes NestJS backend, Expo mobile, SvelteKit web, Astro landing
contacts Contact management NestJS backend, SvelteKit web
citycorners City guide for Konstanz Astro landing

Archived Projects (apps-archived/)

Currently empty. To archive a project, move it from apps/ to apps-archived/ (excluded from workspace).

Development Commands

For detailed local development setup, see docs/LOCAL_DEVELOPMENT.md.

Use dev:*:full commands to start any app with automatic database setup:

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:clock:full      # Start clock 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

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

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

# Start specific project (runs all apps in project)
pnpm run manacore:dev
pnpm run manadeck: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:backend    # Just NestJS backend
pnpm run dev:chat:app        # Web + backend together

# Build & quality
pnpm run build
pnpm run type-check
pnpm run format

Each project has its own CLAUDE.md with detailed project-specific commands.

Architecture Patterns

Monorepo Structure

manacore-monorepo/
├── apps/                    # Active SaaS product applications
│   ├── chat/
│   │   ├── apps/
│   │   │   ├── backend/     # NestJS API
│   │   │   ├── mobile/      # Expo React Native app
│   │   │   ├── web/         # SvelteKit web app
│   │   │   └── landing/     # Astro marketing page
│   │   └── packages/        # Project-specific shared code
│   ├── manadeck/
│   ├── picture/
│   └── ...
├── apps-archived/           # Archived apps (excluded from workspace)
│   ├── bauntown/
│   ├── memoro/
│   ├── news/
│   ├── nutriphi/
│   ├── reader/
│   ├── uload/
│   └── wisekeep/
├── games/                   # Game projects
│   └── {game-name}/         # Individual games
├── services/                # Standalone microservices
│   ├── mana-core-auth/      # Central authentication service
│   ├── mana-search/         # Central search & content extraction service
│   ├── mana-crawler/        # Web crawler service
│   └── mana-llm/            # Central LLM abstraction service
├── packages/                # Monorepo-wide shared packages
└── docker/                  # Docker configuration files

Standard Project Structure (inside apps/)

apps/{project}/
├── apps/
│   ├── backend/     # NestJS API (when present)
│   ├── mobile/      # Expo React Native app
│   ├── web/         # SvelteKit web app
│   └── landing/     # Astro marketing page
├── packages/        # Project-specific shared code
└── package.json

Turborepo Configuration

CRITICAL: Avoid Recursive Turbo Calls

Parent workspace packages (e.g., apps/chat/package.json, apps/zitare/package.json) must NEVER have scripts that call turbo run <task> for tasks that turbo orchestrates from the root.

// 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
  }
}

// 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
  • Supabase SSR auth

Landing Pages (Astro):

  • Astro 5.x
  • Tailwind CSS
  • Static site generation

Backends (NestJS):

  • NestJS 10-11
  • TypeScript
  • Supabase integration

Authentication Architecture

All projects use mana-core-auth as the central authentication service:

┌─────────────┐     ┌─────────────┐     ┌────────────────┐
│   Client    │────>│  Backend    │────>│ mana-core-auth │
│ (Web/Mobile)│     │  (NestJS)   │     │  (port 3001)   │
└─────────────┘     └─────────────┘     └────────────────┘
      │                   │                     │
      │ Bearer token      │ POST /validate      │
      │                   │ {token}             │
      │                   │<────────────────────│
      │                   │ {valid, payload}    │
      │<──────────────────│                     │
      │ Response          │                     │

Key Components

Component Purpose
services/mana-core-auth Central auth service (Better Auth + EdDSA JWT)
@manacore/shared-nestjs-auth Shared NestJS guards/decorators for JWT validation
@mana-core/nestjs-integration Extended NestJS module with auth + credits
@manacore/shared-auth Client-side auth for web/mobile apps

NestJS Backend Integration

Option 1: Simple auth only - Use @manacore/shared-nestjs-auth:

// In your controller
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';

@Controller('api')
@UseGuards(JwtAuthGuard)
export class MyController {
	@Get('profile')
	getProfile(@CurrentUser() user: CurrentUserData) {
		return { userId: user.userId, email: user.email };
	}
}

Option 2: Auth + Credits - Use @mana-core/nestjs-integration:

// app.module.ts
import { ManaCoreModule } from '@mana-core/nestjs-integration';

@Module({
	imports: [
		ManaCoreModule.forRootAsync({
			imports: [ConfigModule],
			useFactory: (config: ConfigService) => ({
				appId: config.get('APP_ID'),
				serviceKey: config.get('MANA_CORE_SERVICE_KEY'),
				debug: config.get('NODE_ENV') === 'development',
			}),
			inject: [ConfigService],
		}),
	],
})
export class AppModule {}

// In controller
import { AuthGuard } from '@mana-core/nestjs-integration/guards';
import { CurrentUser } from '@mana-core/nestjs-integration/decorators';
import { CreditClientService } from '@mana-core/nestjs-integration';

@Controller('api')
@UseGuards(AuthGuard)
export class ApiController {
	constructor(private creditClient: CreditClientService) {}

	@Post('generate')
	async generate(@CurrentUser() user: any) {
		await this.creditClient.consumeCredits(user.sub, 'generation', 10, 'AI generation');
		// ... do work
	}
}

Required Environment Variables

# All backends need this
MANA_CORE_AUTH_URL=http://localhost:3001

# For development bypass (optional)
NODE_ENV=development
DEV_BYPASS_AUTH=true
DEV_USER_ID=your-test-user-id

# For credit operations (optional)
MANA_CORE_SERVICE_KEY=your-service-key
APP_ID=your-app-id

JWT Token Structure (EdDSA)

{
	"sub": "user-id",
	"email": "user@example.com",
	"role": "user",
	"sid": "session-id",
	"exp": 1764606251,
	"iss": "manacore",
	"aud": "manacore"
}

Testing Auth Integration

# 1. Start mana-core-auth
pnpm dev:auth

# 2. Start a backend (e.g., Zitare)
pnpm dev:zitare:backend

# 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:3007/api/favorites \
  -H "Authorization: Bearer $TOKEN"

Integrated Backends

Backend Package Port
Chat @mana-core/nestjs-integration 3002
Picture @manacore/shared-nestjs-auth 3006
Zitare @manacore/shared-nestjs-auth 3007
Presi Custom (same pattern) 3008
ManaDeck @mana-core/nestjs-integration 3009

Adding a New App to SSO

When adding a new app that should participate in cross-app SSO, update all three locations:

  1. trustedOrigins in services/mana-core-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-core-auth/) to verify

Missing any of these will silently break SSO for that 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 NestJS search service with SearXNG + Redis
SearXNG Meta-search engine aggregating multiple sources
Redis Caching layer (search: 1h TTL, extract: 24h TTL)

API Endpoints

# 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

# Start SearXNG + Redis (for local NestJS development)
cd services/mana-search && docker-compose -f docker-compose.dev.yml up -d

# Start NestJS API
pnpm --filter @mana-search/service dev

# Or start everything via Docker
cd services/mana-search && docker-compose up -d

Environment Variables

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

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

// 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;

Shared Packages (packages/)

Package Purpose
@manacore/shared-nestjs-auth NestJS JWT validation guards via mana-core-auth
@mana-core/nestjs-integration NestJS module with auth guards + credit client
@manacore/shared-auth Client-side auth service for web/mobile apps
@manacore/shared-storage S3-compatible storage (MinIO)
@manacore/shared-types Common TypeScript types
@manacore/shared-utils Utility functions
@manacore/shared-ui React Native UI components
@manacore/shared-theme Theme configuration
@manacore/shared-i18n Internationalization

Import shared packages:

import { createAuthService } from '@manacore/shared-auth';
import { formatDate, truncate } from '@manacore/shared-utils';

Database (Supabase)

  • All projects use Supabase for PostgreSQL database, auth, and storage
  • Row Level Security (RLS) policies enforce access control via JWT claims
  • Each project has its own Supabase project/schema
  • Types typically generated via supabase gen types

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

# 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
manadeck-storage ManaDeck 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 Backend

import { createPictureStorage, generateUserFileKey, getContentType } from '@manacore/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

# 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
ManaCore @manacore/landing manacore-landing https://manacore-landing.pages.dev
ManaDeck @manadeck/landing manadeck-landing https://manadeck-landing.pages.dev
Zitare @zitare/landing zitare-landing https://zitare-landing.pages.dev

Local Deployment

# 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:manacore
pnpm deploy:landing:manadeck
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:
    name = "{project}-landing"
    compatibility_date = "2024-12-01"
    pages_build_output_dir = "dist"
    
  3. Add deploy script to root package.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

# Add custom domain to a project
npx wrangler pages project add-domain chat-landing chat.mana.how

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)

# 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

ssh mana-server                              # Connect to server
cd ~/projects/manacore-monorepo

./scripts/mac-mini/status.sh                 # Check all services
./scripts/mac-mini/deploy.sh                 # Pull & restart containers
./scripts/mac-mini/health-check.sh           # Run health checks
docker compose -f docker-compose.macmini.yml logs -f  # View logs

For detailed server documentation, see docs/MAC_MINI_SERVER.md.

Adding Dependencies

# Add to workspace root (dev tools only)
pnpm add -D <package> -w

# Add to specific project
pnpm add <package> --filter memoro

# Add to specific app within project
pnpm add <package> --filter @memoro/mobile

# Add to shared package
pnpm add <package> --filter @manacore/shared-utils

Environment Variables

Centralized Development Environment

All development environment variables are managed from a single file: .env.development

# First-time setup: generates all app-specific .env files
pnpm setup:env

# This also runs automatically after `pnpm install`

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
  • NestJS backend: 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=...

Backend (NestJS):

SUPABASE_URL=...
SUPABASE_SERVICE_ROLE_KEY=...
PORT=...

Project-Specific Documentation

Each project has its own CLAUDE.md with detailed information:

  • apps/manacore/CLAUDE.md - Multi-app ecosystem, auth details
  • apps/manadeck/CLAUDE.md - Card/deck management
  • apps/chat/CLAUDE.md - Chat API endpoints, AI models
  • apps/picture/CLAUDE.md - AI image generation
  • services/mana-core-auth/ - Central authentication service
  • services/mana-search/CLAUDE.md - Search & content extraction service
  • services/mana-crawler/CLAUDE.md - Web crawler service
  • services/mana-llm/CLAUDE.md - Central LLM abstraction 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