diff --git a/.env.macmini.example b/.env.macmini.example index 69a075427..a0b2c73a6 100644 --- a/.env.macmini.example +++ b/.env.macmini.example @@ -1,5 +1,21 @@ +# ============================================================================= # Mac Mini Production Environment -# Copy to .env.macmini and fill in the values +# ============================================================================= +# +# Copy to .env.macmini (gitignored) and fill in the values. This file is +# loaded by `docker compose -f docker-compose.macmini.yml ...` on the +# Mac Mini host. The compose file references vars via ${VAR} (REQUIRED — +# missing means container fails to start) and ${VAR:-default} (OPTIONAL +# — falls back to the inline default if unset). +# +# Sections below mirror that split: +# 1. REQUIRED — production deployment cannot boot without these +# 2. OPTIONAL — defaults exist in compose; only set to override +# +# Verify the example covers every var the compose file uses: +# grep -ohE '\$\{[A-Z_][A-Z0-9_]*' docker-compose.macmini.yml | sort -u +# (audit baseline established 2026-04-08, see +# docs/REFACTORING_AUDIT_2026_04.md item #9) # ============================================ # Compose project name (pinned, do not change) @@ -69,3 +85,120 @@ GRAFANA_PASSWORD=your-grafana-admin-password # Web Analytics (Umami) # ============================================ UMAMI_APP_SECRET=your-umami-secret-here + +# ============================================================================= +# REQUIRED — production cannot boot without these +# ============================================================================= + +# ─── Azure OpenAI ─────────────────────────────────────────── +# Some compose entries reference ${AZURE_OPENAI_KEY} (no default), distinct +# from the ${AZURE_OPENAI_API_KEY:-} above. Provide both — they may be the +# same value or different keys depending on which deployment they hit. +AZURE_OPENAI_KEY= +AZURE_OPENAI_DEPLOYMENT= + +# ─── Azure Speech (mana-stt / mana-tts fallback) ──────────── +# Four rotation keys + endpoint. Get from Azure Portal → Speech resource. +AZURE_SPEECH_ENDPOINT= +AZURE_SPEECH_KEY_1= +AZURE_SPEECH_KEY_2= +AZURE_SPEECH_KEY_3= +AZURE_SPEECH_KEY_4= + +# ─── Azure Blob Storage (Memoro batch audio) ──────────────── +AZURE_STORAGE_ACCOUNT_NAME= +AZURE_STORAGE_ACCOUNT_KEY= + +# ─── Google Gemini ────────────────────────────────────────── +# Used by mana-llm + several Gemini-Vision modules (planta, nutriphi). +GEMINI_API_KEY= + +# ─── Service-to-service auth keys ─────────────────────────── +# Shared secrets backends use to call each other without going through +# user JWTs. Generate with: openssl rand -base64 32 +# MANA_SERVICE_KEY appears in compose with BOTH a default and a no-default +# reference, so it MUST be set to a real value in production. +MANA_SERVICE_KEY= +MANA_CREDITS_SERVICE_KEY= +MEMORO_SERVICE_KEY= + +# ─── Memoro Supabase (legacy) ─────────────────────────────── +# Memoro still keeps recording metadata in Supabase. Move to mana_platform +# is tracked in the Memoro CLAUDE.md. +MEMORO_SUPABASE_URL= +MEMORO_SUPABASE_SERVICE_KEY= + +# ============================================================================= +# OPTIONAL — defaults baked into docker-compose.macmini.yml +# ============================================================================= +# Only uncomment + set if you want to override the in-compose default. +# Each line shows the default that ships in the compose file so you know +# what you're overriding. + +# ─── Database / Cache (defaults are insecure!) ────────────── +# POSTGRES_PASSWORD=devpassword # CHANGE for prod +# REDIS_PASSWORD=redis123 # CHANGE for prod + +# ─── MinIO (defaults are insecure!) ───────────────────────── +# MINIO_ACCESS_KEY=minioadmin # CHANGE for prod +# MINIO_SECRET_KEY=minioadmin # CHANGE for prod + +# ─── Better Auth ──────────────────────────────────────────── +# Default falls back to ${JWT_SECRET}. Override only if you want a +# distinct session-signing key. +# BETTER_AUTH_SECRET= + +# ─── LLM models ───────────────────────────────────────────── +# MANA_LLM_API_KEY= # default empty (open llm.mana.how) +# MANA_LLM_MODEL=ollama/gemma3:12b +# OLLAMA_URL=http://host.docker.internal:13434 +# OLLAMA_MODEL=gemma3:12b + +# ─── Third-party AI APIs (optional) ───────────────────────── +# OPENROUTER_API_KEY= +# GROQ_API_KEY= +# GOOGLE_API_KEY= +# TOGETHER_API_KEY= + +# ─── STT / TTS (defaults point to GPU box on LAN) ─────────── +# STT_SERVICE_URL=http://192.168.178.11:3020 +# TTS_SERVICE_URL=http://192.168.178.11:3022 +# MANA_STT_API_KEY= + +# ─── Stripe (defaults empty — billing disabled if unset) ──── +# STRIPE_SECRET_KEY= +# STRIPE_CREDITS_WEBHOOK_SECRET= +# STRIPE_SUBSCRIPTIONS_WEBHOOK_SECRET= + +# ─── Mail (Stalwart) — defaults work for the bundled stack ── +# SMTP_HOST=stalwart +# SMTP_PORT=587 +# SMTP_USER=noreply +# SMTP_PASSWORD=ManaNoReply2026! # CHANGE for prod +# STALWART_ADMIN_PASSWORD=ChangeMe123! # CHANGE for prod + +# ─── Search (SearXNG) ─────────────────────────────────────── +# SEARXNG_SECRET=change-me-searxng-secret + +# ─── Error tracking (GlitchTip / Sentry) ──────────────────── +# GLITCHTIP_DSN_MANA_WEB= +# GLITCHTIP_SECRET_KEY=change-me-in-production + +# ─── Notifications ────────────────────────────────────────── +# NTFY_TOPIC= +# TELEGRAM_BOT_TOKEN= +# TELEGRAM_CHAT_ID= + +# ─── Cloudflare (only if deploying landings via wrangler) ─── +# CLOUDFLARE_ACCOUNT_ID= +# CLOUDFLARE_API_TOKEN= +# EXPO_ACCESS_TOKEN= + +# ─── Admin / abuse limits ─────────────────────────────────── +# ADMIN_USER_IDS= # comma-separated user IDs +# MAX_DAILY_SIGNUPS=0 # 0 = unlimited + +# ─── Misc ─────────────────────────────────────────────────── +# AZURE_OPENAI_API_VERSION= +# AZURE_STORAGE_CONTAINER=memoro-batch-audio +# AZURE_SPEECH_REGION=germanywestcentral diff --git a/apps/api/src/modules/chat/routes.ts b/apps/api/src/modules/chat/routes.ts index df6aa607f..fe248010f 100644 --- a/apps/api/src/modules/chat/routes.ts +++ b/apps/api/src/modules/chat/routes.ts @@ -9,10 +9,11 @@ import { Hono } from 'hono'; import { streamSSE } from 'hono/streaming'; import { consumeCredits, validateCredits } from '@mana/shared-hono/credits'; +import type { AuthVariables } from '@mana/shared-hono'; const LLM_URL = process.env.MANA_LLM_URL || 'http://localhost:3025'; -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── Chat Completion (sync) ────────────────────────────────── diff --git a/apps/api/src/modules/contacts/routes.ts b/apps/api/src/modules/contacts/routes.ts index 8a6a0051a..e2e4fd4cc 100644 --- a/apps/api/src/modules/contacts/routes.ts +++ b/apps/api/src/modules/contacts/routes.ts @@ -4,6 +4,7 @@ */ import { Hono } from 'hono'; +import type { AuthVariables } from '@mana/shared-hono'; const ALLOWED_AVATAR_TYPES = new Set([ 'image/jpeg', @@ -13,7 +14,7 @@ const ALLOWED_AVATAR_TYPES = new Set([ 'image/svg+xml', ]); -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── Avatar Upload (S3) ───────────────────────────────────── @@ -33,9 +34,7 @@ routes.post('/:id/avatar', async (c) => { if (file.type === 'image/svg+xml') { // SVGs stay on shared-storage (Sharp can't process SVG) - const { createContactsStorage, generateUserFileKey } = await import( - '@mana/shared-storage' - ); + const { createContactsStorage, generateUserFileKey } = await import('@mana/shared-storage'); const storage = createContactsStorage(); const key = generateUserFileKey(userId, `avatar-${c.req.param('id')}.svg`); const result = await storage.upload(key, Buffer.from(buffer), { diff --git a/apps/api/src/modules/context/routes.ts b/apps/api/src/modules/context/routes.ts index 15305ffa7..8f3bdd2fd 100644 --- a/apps/api/src/modules/context/routes.ts +++ b/apps/api/src/modules/context/routes.ts @@ -7,10 +7,11 @@ import { Hono } from 'hono'; import { consumeCredits, validateCredits } from '@mana/shared-hono/credits'; +import type { AuthVariables } from '@mana/shared-hono'; const LLM_URL = process.env.MANA_LLM_URL || 'http://localhost:3025'; -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── AI Generation (server-only: mana-llm) ────────────────── diff --git a/apps/api/src/modules/guides/routes.ts b/apps/api/src/modules/guides/routes.ts index 365c0f635..238e19ac3 100644 --- a/apps/api/src/modules/guides/routes.ts +++ b/apps/api/src/modules/guides/routes.ts @@ -6,7 +6,8 @@ * This module handles web import via mana-search + mana-llm, and share links. */ -import { Hono } from 'hono'; +import { Hono, type Context } from 'hono'; +import { logger } from '@mana/shared-hono'; const MANA_SEARCH_URL = process.env.MANA_SEARCH_URL ?? 'http://localhost:3021'; const MANA_LLM_URL = process.env.MANA_LLM_URL ?? 'http://localhost:3030'; @@ -35,7 +36,7 @@ routes.post('/import/url', async (c) => { extracted = await res.json(); } } catch (e) { - console.error('mana-search extract failed:', e); + logger.error('guides.extract_failed', { error: e instanceof Error ? e.message : String(e) }); } const content = extracted.markdown ?? extracted.content ?? ''; @@ -128,7 +129,7 @@ routes.get('/share/:token', (c) => { // ─── Shared: LLM guide generation ────────────────────────── async function generateGuideFromText( - c: Parameters[1]>[0], + c: Context, opts: { text: string; title?: string; sourceUrl?: string; isAiPrompt?: boolean } ) { const systemPrompt = `Du bist ein Experte für das Erstellen strukturierter Schritt-für-Schritt-Anleitungen. @@ -189,7 +190,7 @@ Regeln: throw new Error(`LLM error: ${llmRes.status}`); } - const llmData = await llmRes.json<{ content: string }>(); + const llmData = (await llmRes.json()) as { content: string }; const rawJson = llmData.content.trim(); // Extract JSON from potential markdown code fences @@ -209,7 +210,7 @@ Regeln: sections: parsed.sections ?? [], }); } catch (e) { - console.error('Guide generation failed:', e); + logger.error('guides.generate_failed', { error: e instanceof Error ? e.message : String(e) }); return c.json({ error: 'Guide-Generierung fehlgeschlagen', details: String(e) }, 500); } } diff --git a/apps/api/src/modules/music/routes.ts b/apps/api/src/modules/music/routes.ts index 41a2782e7..475dcd1cd 100644 --- a/apps/api/src/modules/music/routes.ts +++ b/apps/api/src/modules/music/routes.ts @@ -4,8 +4,9 @@ */ import { Hono } from 'hono'; +import type { AuthVariables } from '@mana/shared-hono'; -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── Song Upload (presigned URL) ──────────────────────────── diff --git a/apps/api/src/modules/nutriphi/routes.ts b/apps/api/src/modules/nutriphi/routes.ts index 2ae67b675..358fe4987 100644 --- a/apps/api/src/modules/nutriphi/routes.ts +++ b/apps/api/src/modules/nutriphi/routes.ts @@ -7,6 +7,7 @@ */ import { Hono } from 'hono'; +import { logger, type AuthVariables } from '@mana/shared-hono'; const LLM_URL = process.env.MANA_LLM_URL || 'http://localhost:3025'; @@ -20,7 +21,7 @@ const ANALYSIS_PROMPT = `Du bist ein Ernährungsexperte. Analysiere die Mahlzeit "suggestions": [] }`; -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── Photo Analysis (server-only: Gemini Vision) ──────────── @@ -81,7 +82,9 @@ routes.post('/analysis/photo', async (c) => { return c.json(analysis); } catch (err) { - console.error('Photo analysis failed:', err); + logger.error('nutriphi.photo_analysis_failed', { + error: err instanceof Error ? err.message : String(err), + }); return c.json({ error: 'Analysis failed' }, 500); } }); @@ -115,7 +118,9 @@ routes.post('/analysis/text', async (c) => { return c.json(analysis); } catch (err) { - console.error('Text analysis failed:', err); + logger.error('nutriphi.text_analysis_failed', { + error: err instanceof Error ? err.message : String(err), + }); return c.json({ error: 'Analysis failed' }, 500); } }); diff --git a/apps/api/src/modules/picture/routes.ts b/apps/api/src/modules/picture/routes.ts index a1b380237..c7e8648e3 100644 --- a/apps/api/src/modules/picture/routes.ts +++ b/apps/api/src/modules/picture/routes.ts @@ -8,11 +8,12 @@ import { Hono } from 'hono'; import { consumeCredits, validateCredits } from '@mana/shared-hono/credits'; +import type { AuthVariables } from '@mana/shared-hono'; const REPLICATE_TOKEN = process.env.REPLICATE_API_TOKEN || ''; const IMAGE_GEN_URL = process.env.MANA_IMAGE_GEN_URL || ''; -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── AI Image Generation (server-only: Replicate/local) ───── diff --git a/apps/api/src/modules/planta/routes.ts b/apps/api/src/modules/planta/routes.ts index 5ce574403..a34d79e20 100644 --- a/apps/api/src/modules/planta/routes.ts +++ b/apps/api/src/modules/planta/routes.ts @@ -7,10 +7,11 @@ */ import { Hono } from 'hono'; +import { logger, type AuthVariables } from '@mana/shared-hono'; const LLM_URL = process.env.MANA_LLM_URL || 'http://localhost:3025'; -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── Photo Upload (server-only: S3 storage) ───────────────── @@ -38,7 +39,9 @@ routes.post('/photos/upload', async (c) => { 201 ); } catch (err) { - console.error('Upload failed:', err); + logger.error('planta.upload_failed', { + error: err instanceof Error ? err.message : String(err), + }); return c.json({ error: 'Upload failed' }, 500); } }); @@ -81,7 +84,9 @@ routes.post('/analysis/identify', async (c) => { return c.json(analysis); } catch (err) { - console.error('Analysis failed:', err); + logger.error('planta.analysis_failed', { + error: err instanceof Error ? err.message : String(err), + }); return c.json({ error: 'Analysis failed' }, 500); } }); diff --git a/apps/api/src/modules/presi/routes.ts b/apps/api/src/modules/presi/routes.ts index 3adb31d73..641e18164 100644 --- a/apps/api/src/modules/presi/routes.ts +++ b/apps/api/src/modules/presi/routes.ts @@ -10,6 +10,7 @@ import { Hono } from 'hono'; import { eq, and, gt, or, isNull, asc } from 'drizzle-orm'; import { HTTPException } from 'hono/http-exception'; import { authMiddleware } from '@mana/shared-hono/auth'; +import type { AuthVariables } from '@mana/shared-hono'; import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import { @@ -112,7 +113,7 @@ function generateShareCode(): string { // ─── Routes ───────────────────────────────────────────────── -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── Public endpoint (no auth) ────────────────────────────── @@ -187,7 +188,9 @@ routes.post('/share/deck/:deckId', async (c) => { } // Parse optional expiry - const body = await c.req.json<{ expiresAt?: string }>().catch(() => ({})); + const body = (await c.req.json<{ expiresAt?: string }>().catch(() => ({}))) as { + expiresAt?: string; + }; const [share] = await db .insert(sharedDecks) @@ -223,6 +226,7 @@ routes.get('/share/deck/:deckId/links', async (c) => { routes.delete('/share/:shareId', authMiddleware(), async (c) => { const userId = c.get('userId'); const shareId = c.req.param('shareId'); + if (!shareId) throw new HTTPException(400, { message: 'shareId required' }); const share = await db.query.sharedDecks.findFirst({ where: eq(sharedDecks.id, shareId), diff --git a/apps/api/src/modules/storage/routes.ts b/apps/api/src/modules/storage/routes.ts index b87bd08b1..a3ee7c34e 100644 --- a/apps/api/src/modules/storage/routes.ts +++ b/apps/api/src/modules/storage/routes.ts @@ -7,8 +7,9 @@ */ import { Hono } from 'hono'; +import type { AuthVariables } from '@mana/shared-hono'; -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── File Upload (server-only: S3) ────────────────────────── @@ -46,9 +47,8 @@ routes.post('/files/upload', async (c) => { } // Non-images -> shared-storage as before - const { createStorageStorage, generateUserFileKey, getContentType } = await import( - '@mana/shared-storage' - ); + const { createStorageStorage, generateUserFileKey, getContentType } = + await import('@mana/shared-storage'); const storage = createStorageStorage(); const key = generateUserFileKey(userId, file.name); @@ -91,10 +91,13 @@ routes.get('/files/:id/download', async (c) => { return c.json({ url }); } - const data = await storage.download(storagePath); - return new Response(data.body, { + const [buffer, metadata] = await Promise.all([ + storage.download(storagePath), + storage.getMetadata(storagePath).catch(() => null), + ]); + return new Response(new Uint8Array(buffer), { headers: { - 'Content-Type': data.contentType || 'application/octet-stream', + 'Content-Type': metadata?.contentType || 'application/octet-stream', 'Content-Disposition': `attachment; filename="${storagePath.split('/').pop()}"`, }, }); diff --git a/apps/api/src/modules/todo/routes.ts b/apps/api/src/modules/todo/routes.ts index 32e954959..111045607 100644 --- a/apps/api/src/modules/todo/routes.ts +++ b/apps/api/src/modules/todo/routes.ts @@ -18,7 +18,7 @@ import { z } from 'zod'; import { eq, and, asc, sql } from 'drizzle-orm'; import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; -import { serviceAuthMiddleware } from '@mana/shared-hono'; +import { serviceAuthMiddleware, type AuthVariables } from '@mana/shared-hono'; import { pgSchema, uuid, @@ -93,7 +93,7 @@ const db = drizzle(connection, { schema: { tasks, projects, reminders } }); // ─── Routes ──────────────────────────────────────────────── -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── RRULE Compute ───────────────────────────────────────── diff --git a/apps/api/src/modules/traces/routes.ts b/apps/api/src/modules/traces/routes.ts index f924f5b6c..6b8c697f8 100644 --- a/apps/api/src/modules/traces/routes.ts +++ b/apps/api/src/modules/traces/routes.ts @@ -7,6 +7,7 @@ */ import { Hono } from 'hono'; +import { logger, type AuthVariables } from '@mana/shared-hono'; import { eq, and } from 'drizzle-orm'; import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; @@ -120,7 +121,7 @@ const db = drizzle(connection, { schema: { locations, cities, pois, guides, guid // ─── Routes ───────────────────────────────────────────────── -const routes = new Hono(); +const routes = new Hono<{ Variables: AuthVariables }>(); // ─── Guide Generation (server-only: AI + search) ──────────── @@ -152,7 +153,11 @@ routes.post('/guides/generate', async (c) => { // Fire-and-forget async pipeline runGuidePipeline(guide.id, userId, city, params.language || 'de', params.maxPois || 10).catch( (err) => { - console.error('Guide generation failed:', err); + logger.error('traces.guide_generation_failed', { + guideId: guide.id, + cityId: city.id, + error: err instanceof Error ? err.message : String(err), + }); db.update(guides) .set({ status: 'error' }) .where(eq(guides.id, guide.id)) diff --git a/apps/calendar/CLAUDE.md b/apps/calendar/CLAUDE.md index 3773b7c16..b397f2861 100644 --- a/apps/calendar/CLAUDE.md +++ b/apps/calendar/CLAUDE.md @@ -1,660 +1,18 @@ -# Calendar Project Guide +# Calendar — consolidated into the unified Mana app -## Übersicht +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/calendar/apps/server/` and `apps/calendar/apps/web/` +directories have been removed. Active code now lives in: -**Kalender** ist eine vollständige Kalender-Anwendung für persönliches und geteiltes Zeitmanagement. Die App unterstützt mehrere Kalender, wiederkehrende Termine, CalDAV/iCal-Synchronisation und Erinnerungen. +- **Backend compute routes**: [`apps/api/src/modules/calendar/routes.ts`](../api/src/modules/calendar/routes.ts) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/calendar/`](../mana/apps/web/src/lib/modules/calendar/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/calendar/`](../mana/apps/web/src/routes/(app)/calendar/) +- **Landing page** (still standalone): [`apps/calendar/apps/landing/`](apps/landing/) -| App | Port | URL | -|-----|------|-----| -| Server | 3014 | http://localhost:3014 | -| Web App | 5179 | http://localhost:5179 | -| Landing Page | 4322 | http://localhost:4322 | -| Mobile | 8081 | Expo Go | +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -## Project Structure - -``` -apps/calendar/ -├── apps/ -│ ├── server/ # Hono/Bun compute server (@calendar/server) -│ │ └── src/ -│ │ ├── main.ts -│ │ ├── app.module.ts -│ │ ├── db/ # Drizzle schemas + migrations -│ │ │ ├── schema/ -│ │ │ │ ├── calendars.schema.ts -│ │ │ │ ├── events.schema.ts -│ │ │ │ ├── reminders.schema.ts -│ │ │ │ ├── calendar-shares.schema.ts -│ │ │ │ └── external-calendars.schema.ts -│ │ │ └── db.ts -│ │ ├── calendar/ # Calendar CRUD -│ │ ├── event/ # Event CRUD + queries -│ │ ├── reminder/ # Reminders + notifications -│ │ ├── sync/ # CalDAV/iCal sync -│ │ ├── share/ # Calendar sharing -│ │ └── health/ -│ │ -│ ├── web/ # SvelteKit web application (@calendar/web) -│ │ └── src/ -│ │ ├── lib/ -│ │ │ ├── api/ # API clients -│ │ │ │ ├── client.ts -│ │ │ │ ├── calendars.ts -│ │ │ │ ├── events.ts -│ │ │ │ ├── reminders.ts -│ │ │ │ └── shares.ts -│ │ │ ├── stores/ # Svelte 5 runes stores -│ │ │ │ ├── auth.svelte.ts -│ │ │ │ ├── view.svelte.ts -│ │ │ │ ├── calendars.svelte.ts -│ │ │ │ ├── events.svelte.ts -│ │ │ │ ├── theme.ts -│ │ │ │ ├── navigation.ts -│ │ │ │ └── toast.ts -│ │ │ ├── components/ -│ │ │ │ ├── calendar/ -│ │ │ │ │ ├── CalendarHeader.svelte -│ │ │ │ │ ├── WeekView.svelte -│ │ │ │ │ ├── DayView.svelte -│ │ │ │ │ ├── MonthView.svelte -│ │ │ │ │ ├── MiniCalendar.svelte -│ │ │ │ │ └── CalendarSidebar.svelte -│ │ │ │ └── event/ -│ │ │ │ └── EventForm.svelte -│ │ │ └── i18n/ # Internationalization (5 Sprachen) -│ │ └── routes/ -│ │ ├── +layout.svelte -│ │ ├── +page.svelte # Hauptkalender (Wochenansicht) -│ │ ├── agenda/+page.svelte # Agenda-Ansicht -│ │ ├── event/ -│ │ │ ├── new/+page.svelte # Neuer Termin -│ │ │ └── [id]/+page.svelte # Termin bearbeiten -│ │ ├── calendars/+page.svelte -│ │ ├── settings/+page.svelte -│ │ ├── feedback/+page.svelte -│ │ └── (auth)/ -│ │ ├── login/+page.svelte -│ │ ├── register/+page.svelte -│ │ └── forgot-password/+page.svelte -│ │ -│ ├── landing/ # Astro marketing landing page (@calendar/landing) -│ │ └── src/ -│ │ ├── pages/index.astro -│ │ ├── layouts/Layout.astro -│ │ └── components/ -│ │ ├── Hero.astro -│ │ ├── Features.astro -│ │ ├── CTA.astro -│ │ └── Footer.astro -│ │ -│ └── mobile/ # Expo/React Native mobile app (@calendar/mobile) [TODO] -│ -├── packages/ -│ ├── shared/ # Shared types, utils, constants (@calendar/shared) -│ │ └── src/ -│ │ ├── types/ -│ │ │ ├── calendar.ts -│ │ │ ├── event.ts -│ │ │ ├── reminder.ts -│ │ │ └── share.ts -│ │ └── index.ts -│ └── web-ui/ # Shared Svelte components (@calendar/web-ui) [TODO] -│ -├── package.json -└── CLAUDE.md -``` - -## Commands - -### Root Level (from monorepo root) - -```bash -# Alle Apps starten -pnpm calendar:dev # Run all calendar apps - -# Einzelne Apps starten -pnpm dev:calendar:server # Start server (port 3014) -pnpm dev:calendar:web # Start web app (port 5179) -pnpm dev:calendar:landing # Start landing page (port 4322) -pnpm dev:calendar:mobile # Start mobile app [TODO] -pnpm dev:calendar:app # Start web + server together -pnpm dev:calendar:local # Start web + sync (no auth needed) - -# Datenbank -pnpm calendar:db:push # Push schema to database -pnpm calendar:db:studio # Open Drizzle Studio -pnpm calendar:db:seed # Seed initial data -``` - -### Server (apps/calendar/apps/server) - -```bash -pnpm dev # Start with hot reload -pnpm build # Build for production -pnpm start:prod # Start production server -pnpm db:push # Push schema to database -pnpm db:studio # Open Drizzle Studio -pnpm db:seed # Seed initial data -``` - -### Web App (apps/calendar/apps/web) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -pnpm preview # Preview production build -``` - -### Landing Page (apps/calendar/apps/landing) - -```bash -pnpm dev # Start dev server (port 4322) -pnpm build # Build for production -pnpm preview # Preview build -``` - -## Technology Stack - -| Layer | Technology | -|-------|------------| -| **Server** | Hono + Bun, Drizzle ORM, PostgreSQL | -| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 | -| **Landing** | Astro 5.x, Tailwind CSS | -| **Mobile** | React Native 0.81 + Expo SDK 54, NativeWind [TODO] | -| **Auth** | Mana Auth (JWT) | -| **i18n** | svelte-i18n (DE, EN, FR, ES, IT) | -| **Dates** | date-fns | -| **Sync** | ical.js, tsdav (CalDAV) | - -## Architecture - -### Core Features - -1. **Persönliche Kalender** - Erstelle und verwalte mehrere farbcodierte Kalender -2. **Termine** - Vollständiges CRUD mit Wiederholungsunterstützung (RFC 5545 RRULE) -3. **Geteilte Kalender** - Teile Kalender mit Lese-/Schreib-/Admin-Berechtigungen -4. **CalDAV/iCal Sync** - Bi-direktionale Synchronisation mit Google, Apple, etc. -5. **Erinnerungen** - Push-Benachrichtigungen und E-Mail-Erinnerungen - -### Kalender-Ansichten - -| Ansicht | Route | Beschreibung | -|---------|-------|--------------| -| **Woche** | `/` (default) | 7-Tage-Raster mit Stunden | -| **Tag** | Click auf Tag | 24-Stunden-Timeline | -| **Monat** | Header-Switch | Traditionelles Kalenderraster | -| **Agenda** | `/agenda` | Chronologische Terminliste | -| **Jahr** | [TODO] | Kompakte 12-Monats-Übersicht | - -### Web App Stores (Svelte 5 Runes) - -```typescript -// auth.svelte.ts - Authentifizierung -authStore.isAuthenticated // boolean -authStore.user // User | null -authStore.signIn(email, password) -authStore.signOut() -authStore.getAccessToken() - -// view.svelte.ts - Kalender-Ansicht -viewStore.currentDate // Date -viewStore.viewType // 'day' | 'week' | 'month' | 'year' | 'agenda' -viewStore.setDate(date) -viewStore.setViewType(type) -viewStore.goToToday() -viewStore.navigate(direction) // 'prev' | 'next' - -// calendars.svelte.ts - Kalender-Verwaltung -calendarsStore.calendars // Calendar[] -calendarsStore.loading // boolean -calendarsStore.fetchCalendars() -calendarsStore.createCalendar(data) -calendarsStore.updateCalendar(id, data) -calendarsStore.deleteCalendar(id) -calendarsStore.getColor(calendarId) - -// events.svelte.ts - Termine -eventsStore.events // Event[] -eventsStore.loading // boolean -eventsStore.fetchEvents(start, end) -eventsStore.getEventsForDay(date) -eventsStore.getEventsForWeek(date) -eventsStore.createEvent(data) -eventsStore.updateEvent(id, data) -eventsStore.deleteEvent(id) -``` - -### Server API Endpoints - -#### Health - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/health` | GET | Health check | - -#### Calendars - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/calendars` | GET | List user's calendars | -| `/api/v1/calendars` | POST | Create calendar | -| `/api/v1/calendars/:id` | GET | Get calendar details | -| `/api/v1/calendars/:id` | PUT | Update calendar | -| `/api/v1/calendars/:id` | DELETE | Delete calendar | - -#### Events - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/events` | GET | Query events (date range) | -| `/api/v1/events` | POST | Create event | -| `/api/v1/events/:id` | GET | Get event details | -| `/api/v1/events/:id` | PUT | Update event | -| `/api/v1/events/:id` | DELETE | Delete event | -| `/api/v1/events/calendar/:calendarId` | GET | Get events by calendar | - -#### Reminders - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/events/:eventId/reminders` | GET | List event reminders | -| `/api/v1/events/:eventId/reminders` | POST | Add reminder | -| `/api/v1/reminders/:id` | DELETE | Remove reminder | - -#### Sharing - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/calendars/:id/shares` | GET | List calendar shares | -| `/api/v1/calendars/:id/shares` | POST | Share calendar | -| `/api/v1/shares/:shareId/accept` | POST | Accept invitation | -| `/api/v1/shares/:shareId/decline` | POST | Decline invitation | - -#### Sync - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/sync/external` | GET | List external calendars | -| `/api/v1/sync/external` | POST | Connect external calendar | -| `/api/v1/sync/external/:id` | DELETE | Disconnect external | -| `/api/v1/sync/external/:id/sync` | POST | Trigger manual sync | -| `/api/v1/sync/caldav/discover` | POST | Discover CalDAV calendars | -| `/api/v1/calendars/:id/export.ics` | GET | Export calendar as iCal | - -### Database Schema - -#### calendars - -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `user_id` | UUID | Owner | -| `name` | VARCHAR(255) | Calendar name | -| `description` | TEXT | Optional description | -| `color` | VARCHAR(7) | Hex color code (#3B82F6) | -| `is_default` | BOOLEAN | Default calendar flag | -| `is_visible` | BOOLEAN | Visibility in UI | -| `timezone` | VARCHAR(100) | Default timezone | -| `settings` | JSONB | CalendarSettings object | -| `created_at` | TIMESTAMP | Creation time | -| `updated_at` | TIMESTAMP | Last update | - -#### events - -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `calendar_id` | UUID | FK to calendars | -| `user_id` | UUID | Owner | -| `title` | VARCHAR(500) | Event title | -| `description` | TEXT | Event description | -| `location` | VARCHAR(500) | Location | -| `start_time` | TIMESTAMP | Start datetime | -| `end_time` | TIMESTAMP | End datetime | -| `is_all_day` | BOOLEAN | All-day flag | -| `timezone` | VARCHAR(100) | Event timezone | -| `recurrence_rule` | VARCHAR(500) | RFC 5545 RRULE | -| `recurrence_end_date` | TIMESTAMP | End of recurrence | -| `recurrence_exceptions` | JSONB | Exception dates | -| `parent_event_id` | UUID | Parent for instances | -| `color` | VARCHAR(7) | Override color | -| `status` | VARCHAR(20) | confirmed/tentative/cancelled | -| `external_id` | VARCHAR(255) | External calendar ID | -| `metadata` | JSONB | Attendees, URL, etc. | -| `created_at` | TIMESTAMP | Creation time | -| `updated_at` | TIMESTAMP | Last update | - -#### calendar_shares - -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `calendar_id` | UUID | FK to calendars | -| `shared_with_user_id` | UUID | Target user (optional) | -| `shared_with_email` | VARCHAR(255) | Email for invite | -| `permission` | VARCHAR(20) | read/write/admin | -| `share_token` | VARCHAR(64) | For link sharing | -| `share_url` | VARCHAR(500) | Public share URL | -| `status` | VARCHAR(20) | pending/accepted/declined | -| `invited_by` | UUID | Inviter user ID | -| `accepted_at` | TIMESTAMP | Accept timestamp | -| `expires_at` | TIMESTAMP | Expiration date | -| `created_at` | TIMESTAMP | Creation time | -| `updated_at` | TIMESTAMP | Last update | - -#### reminders - -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `event_id` | UUID | FK to events | -| `user_id` | UUID | Owner | -| `minutes_before` | INTEGER | Reminder offset | -| `reminder_time` | TIMESTAMP | Calculated time | -| `notify_push` | BOOLEAN | Push notification | -| `notify_email` | BOOLEAN | Email notification | -| `status` | VARCHAR(20) | pending/sent/failed | -| `sent_at` | TIMESTAMP | Send timestamp | -| `event_instance_date` | TIMESTAMP | For recurring events | -| `created_at` | TIMESTAMP | Creation time | - -#### external_calendars - -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `user_id` | UUID | Owner | -| `name` | VARCHAR(255) | Display name | -| `provider` | VARCHAR(50) | google/apple/caldav/ical_url | -| `calendar_url` | TEXT | CalDAV or iCal URL | -| `username` | VARCHAR(255) | CalDAV username | -| `encrypted_password` | TEXT | Encrypted password | -| `access_token` | TEXT | OAuth token | -| `refresh_token` | TEXT | OAuth refresh token | -| `token_expires_at` | TIMESTAMP | Token expiration | -| `sync_enabled` | BOOLEAN | Sync toggle | -| `sync_direction` | VARCHAR(20) | both/import/export | -| `sync_interval` | INTEGER | Minutes between syncs | -| `last_sync_at` | TIMESTAMP | Last sync time | -| `last_sync_error` | TEXT | Error message | -| `color` | VARCHAR(7) | Display color | -| `is_visible` | BOOLEAN | Visibility in UI | -| `provider_data` | JSONB | Provider-specific data | -| `created_at` | TIMESTAMP | Creation time | -| `updated_at` | TIMESTAMP | Last update | - -### Recurrence (RFC 5545 RRULE) - -Beispiele für wiederkehrende Termine: - -``` -FREQ=DAILY # Täglich -FREQ=WEEKLY;BYDAY=MO,WE,FR # Mo, Mi, Fr -FREQ=WEEKLY;INTERVAL=2;BYDAY=TU # Jeden 2. Dienstag -FREQ=MONTHLY;BYMONTHDAY=15 # Am 15. jeden Monats -FREQ=MONTHLY;BYDAY=2MO # Am 2. Montag jeden Monats -FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=25 # Jährlich am 25.12. -FREQ=DAILY;COUNT=10 # Täglich, 10 mal -FREQ=WEEKLY;UNTIL=20241231T235959Z # Wöchentlich bis Ende 2024 -``` - -## Environment Variables - -### Server (.env) - -```env -NODE_ENV=development -PORT=3014 -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/calendar -MANA_AUTH_URL=http://localhost:3001 -CORS_ORIGINS=http://localhost:5173,http://localhost:5179,http://localhost:8081 - -# Notifications (optional) -EXPO_ACCESS_TOKEN=your-expo-access-token -RESEND_API_KEY=your-resend-api-key -EMAIL_FROM=calendar@mana.how -``` - -### Web (.env) - -```env -PUBLIC_BACKEND_URL=http://localhost:3014 -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -### Mobile (.env) - -```env -EXPO_PUBLIC_BACKEND_URL=http://localhost:3014 -EXPO_PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -## Shared Packages - -### @calendar/shared - -**Types:** -- `Calendar` - Kalender-Entity -- `CalendarSettings` - Kalender-Einstellungen (JSONB) -- `CalendarViewType` - 'day' | 'week' | 'month' | 'year' | 'agenda' -- `Event` - Termin-Entity -- `EventStatus` - 'confirmed' | 'tentative' | 'cancelled' -- `Reminder` - Erinnerung-Entity -- `ReminderStatus` - 'pending' | 'sent' | 'failed' -- `CalendarShare` - Freigabe-Entity -- `SharePermission` - 'read' | 'write' | 'admin' -- `ExternalCalendar` - Externe Kalender-Entity - -**Constants:** -- `DEFAULT_CALENDAR_COLORS` - 8 vordefinierte Farben -- `DEFAULT_TIMEZONES` - Häufige Zeitzonen - -## Code Style Guidelines - -- **TypeScript**: Strict typing mit Interfaces -- **Web**: Svelte 5 runes mode (`$state`, `$derived`, `$effect`) -- **Styling**: Tailwind CSS mit CSS-Variablen -- **Formatting**: Prettier mit Projekt-Config -- **i18n**: Alle UI-Texte in Locale-Dateien - -### Svelte 5 Runes Beispiel - -```svelte - -``` - -## Quick Add Syntax - -Natural language event creation via `event-parser.ts`: - -``` -"Meeting morgen 14 Uhr 1h @Arbeit #wichtig" -``` - -Recognized patterns: -- **Date**: heute, morgen, nächsten Montag, 15.12. -- **Time**: um 14 Uhr, 14:00 -- **Time Range**: 14-16 Uhr, 10:00-11:30 -- **Duration**: 30min, 2h, 1.5 Stunden, 2h30m -- **All-Day**: ganztägig, ganzer Tag -- **Calendar**: @Kalender (first @ref matches calendar) -- **Attendees**: @Name (subsequent @refs become attendees) -- **Tags**: #tag1 #tag2 -- **Location**: in Berlin, im Büro, bei Dr. Müller -- **Recurrence**: jeden Tag, wöchentlich, monatlich - -### Multi-Event Input - -Split multiple events with keywords (`danach`, `dann`, `und dann`, `anschließend`) or semicolons: - -``` -"Meeting 14 Uhr 1h danach Review 30min" -→ Event 1: Meeting (14:00-15:00) -→ Event 2: Review (15:00-15:30, auto-offset) - -"Standup 9 Uhr 30min @Arbeit; Sprint Planning 1h; Code Review 30min" -→ 3 events chained: 9:00-9:30, 9:30-10:30, 10:30-11:00 -``` - -Context inheritance: subsequent events inherit date, time, and calendar from the first event. If the first event has a duration, the next event starts where it ends. - -### Smart Duration (Auto-Estimation) - -Duration is **automatically applied** to new events when no explicit duration is typed. Uses `estimateEventDuration()` from `event-estimator.ts` with weighted similarity (calendar, title overlap, tags). Falls back to `defaultEventDuration` from settings. Controllable via Settings > Termin-Einstellungen: - -- **Smarte Dauer** toggle (`smartDurationEnabled`, default: on) -- **Standard-Dauer** fallback (`defaultEventDuration`, default: 60min) - -Priority: explicit duration in text > history estimate > default fallback > 1h (if disabled). Runs fully offline against IndexedDB. - -### Conflict Detection - -`detectConflicts()` in `event-estimator.ts` checks for overlapping events. Ignores all-day events, supports exclude-by-ID for edit mode. Returns list of conflicting events with title and time. - -## Quick Start - -### 1. Datenbank erstellen - -```bash -# PostgreSQL Container muss laufen -docker compose -f docker-compose.dev.yml up -d postgres - -# Datenbank erstellen -PGPASSWORD=devpassword psql -h localhost -U mana -d postgres -c "CREATE DATABASE calendar;" - -# Schema pushen -pnpm calendar:db:push -``` - -### 2. Apps starten - -```bash -# Server + Web zusammen -pnpm dev:calendar:app - -# Oder einzeln: -pnpm dev:calendar:server # Terminal 1 -pnpm dev:calendar:web # Terminal 2 -pnpm dev:calendar:landing # Terminal 3 (optional) -``` - -### 3. URLs öffnen - -- Web App: http://localhost:5179 -- Landing: http://localhost:4322 -- API Health: http://localhost:3014/api/v1/health - -## Testing API (mit curl) - -```bash -# Health Check -curl http://localhost:3014/api/v1/health - -# Login (get 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') - -# Kalender abrufen -curl http://localhost:3014/api/v1/calendars \ - -H "Authorization: Bearer $TOKEN" - -# Neuen Kalender erstellen -curl -X POST http://localhost:3014/api/v1/calendars \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name": "Arbeit", "color": "#3B82F6"}' - -# Termine abrufen (Datumsbereich) -curl "http://localhost:3014/api/v1/events?start=2024-12-01&end=2024-12-31" \ - -H "Authorization: Bearer $TOKEN" - -# Neuen Termin erstellen -curl -X POST http://localhost:3014/api/v1/events \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "calendarId": "calendar-uuid", - "title": "Meeting", - "startTime": "2024-12-15T10:00:00Z", - "endTime": "2024-12-15T11:00:00Z" - }' -``` - -## Production Readiness - -**Status: Production-Ready (2026-03-24)** - -### Checklist - -| Category | Status | Details | -|----------|--------|---------| -| **Error Handling** | ✅ | Global `+error.svelte` with i18n (5 languages), error tracking via GlitchTip | -| **Offline Support** | ✅ | Offline page with shared `OfflinePage` component | -| **PWA** | ✅ | Service worker, manifest, icons, apple-touch-icon, shortcuts | -| **Security Headers** | ✅ | CSP, X-Frame-Options, HSTS via `setSecurityHeaders()` | -| **Loading States** | ✅ | Skeleton loaders: CalendarView, EventDetail, Agenda, AppLoading | -| **i18n** | ✅ | 5 languages (DE/EN/FR/ES/IT), all pages including settings fully localized | -| **Meta/SEO** | ✅ | OG tags, meta description in root layout | -| **Accessibility** | ✅ | Focus trapping in all modals, ARIA roles, keyboard navigation | -| **Rate Limiting** | ✅ | ThrottlerGuard global (100 req/min) | -| **API Validation** | ✅ | DTOs with class-validator, whitelist + forbidNonWhitelisted | -| **Auth** | ✅ | JWT via mana-auth, guards on all controllers | -| **Toast System** | ✅ | All toast messages localized via svelte-i18n | -| **Docker** | ✅ | Multi-stage build, health checks, entrypoint script | -| **Tests** | ✅ | 13 unit tests, 7 E2E test suites (Playwright) | -| **Error Tracking** | ✅ | GlitchTip integration (client + server) | -| **Metrics** | ✅ | Prometheus via MetricsModule | -| **Context Menu** | ✅ | Shared ContextMenu on WeekView + AgendaView events | - -### E2E Test Suites - -```bash -pnpm --filter @calendar/web test:e2e -``` - -| Suite | Coverage | -|-------|----------| -| `auth.spec.ts` | Login, redirect, invalid credentials | -| `calendar-views.spec.ts` | Week/month/agenda views, navigation | -| `events.spec.ts` | Event CRUD | -| `calendars.spec.ts` | Calendar management | -| `settings.spec.ts` | Settings page | -| `week-view-interactions.spec.ts` | Drag-to-create, time indicator | -| `error-page.spec.ts` | 404 error page | - -## Roadmap / TODO - -- [ ] Mobile App (Expo) -- [ ] Year View -- [ ] CalDAV Sync Implementation -- [ ] Push Notifications -- [ ] E-Mail Reminders -- [ ] Event Attendees -- [ ] Calendar Import/Export -- [ ] Dark/Light Theme in Landing - -## Important Notes - -1. **Authentication**: Nutzt Mana Auth (JWT im Authorization Header) -2. **Database**: PostgreSQL mit Drizzle ORM (Port 5432) -3. **Port**: Server läuft auf Port 3014 -4. **Recurrence**: Verwendet RFC 5545 RRULE Format -5. **i18n**: 5 Sprachen unterstützt (DE, EN, FR, ES, IT) -6. **Theme**: Ocean-Theme (Blautöne) als Standard +The previous 660-line "Calendar Project Guide" that described a per-product +Hono backend with its own Drizzle schemas, RRULE service, CalDAV sync, +etc. has been deleted — it had been inaccurate since the consolidation +of early 2026. Pre-consolidation reference is in git history. diff --git a/apps/chat/CLAUDE.md b/apps/chat/CLAUDE.md index 04bc87a02..6dd4e7325 100644 --- a/apps/chat/CLAUDE.md +++ b/apps/chat/CLAUDE.md @@ -1,195 +1,19 @@ -# Chat Project Guide +# Chat — consolidated into the unified Mana app -## Project Structure +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/chat/apps/server/` and `apps/chat/apps/web/` directories +have been removed. Active code now lives in: -``` -apps/chat/ -├── apps/ -│ ├── server/ # Hono/Bun compute server (@chat/server) -│ ├── landing/ # Astro marketing landing page (@chat/landing) -│ ├── web/ # SvelteKit web application (@chat/web) -│ └── mobile/ # Expo/React Native mobile app (@chat/mobile) -├── packages/ -│ └── chat-types/ # Shared TypeScript types (@chat/types) -└── package.json -``` +- **Backend compute routes**: [`apps/api/src/modules/chat/routes.ts`](../api/src/modules/chat/routes.ts) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/chat/`](../mana/apps/web/src/lib/modules/chat/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/chat/`](../mana/apps/web/src/routes/(app)/chat/) +- **Landing page** (still standalone): [`apps/chat/apps/landing/`](apps/landing/) +- **Mobile app**: [`apps/chat/apps/mobile/`](apps/mobile/) -## Commands +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -### Root Level - -```bash -pnpm chat:dev # Run all chat apps -pnpm dev:chat:mobile # Start mobile app -pnpm dev:chat:web # Start web app -pnpm dev:chat:landing # Start landing page -pnpm dev:chat:server # Start server -pnpm dev:chat:local # Start web + sync (no auth needed) -pnpm dev:chat:full # Start server + web + auth together -``` - -### Mobile App (chat/apps/mobile) - -```bash -pnpm dev # Start Expo dev server -pnpm ios # Run on iOS simulator -pnpm android # Run on Android emulator -pnpm build:dev # Build development version -pnpm build:preview # Build preview version -pnpm build:prod # Build production version -``` - -### Server (apps/chat/apps/server) - -```bash -pnpm start:dev # Start with hot reload -pnpm build # Build for production -pnpm start:prod # Start production server -pnpm db:push # Push schema to database -pnpm db:seed # Seed AI models -pnpm db:studio # Open Drizzle Studio -``` - -### Web App (chat/apps/web) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -pnpm preview # Preview production build -``` - -### Landing Page (chat/apps/landing) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -pnpm preview # Preview production build -``` - -## Technology Stack - -- **Mobile**: React Native 0.76.7 + Expo SDK 52, NativeWind, Expo Router -- **Web**: SvelteKit 2.x, Svelte 5, Tailwind CSS 4 -- **Landing**: Astro 5.16, Tailwind CSS -- **Server**: Hono + Bun, OpenRouter AI + mana-llm (local), Drizzle ORM, PostgreSQL -- **Auth**: Mana Auth (JWT) -- **Types**: TypeScript 5.x - -## Architecture - -### Server API Endpoints - -| Endpoint | Method | Description | -| --------------------------------- | ------ | --------------------------- | -| `/api/v1/health` | GET | Health check | -| `/api/v1/chat/models` | GET | List available AI models | -| `/api/v1/chat/completions` | POST | Create chat completion | -| `/api/v1/conversations` | GET | List user conversations | -| `/api/v1/conversations/:id` | GET | Get conversation details | -| `/api/v1/conversations/:id/messages` | GET | Get conversation messages | -| `/api/v1/conversations` | POST | Create new conversation | -| `/api/v1/conversations/:id/messages` | POST | Add message to conversation | - -### Environment Variables - -#### Server (.env) - -```env -# Cloud AI models via OpenRouter (optional if using only local models) -OPENROUTER_API_KEY=sk-or-v1-xxx # Get at https://openrouter.ai/keys - -# Local AI via mana-llm service -MANA_LLM_URL=http://localhost:3025 # mana-llm service URL -LLM_TIMEOUT=120000 # Timeout in ms (default: 120s) - -# Database (uses shared Docker PostgreSQL) -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/chat - -# Auth -MANA_AUTH_URL=http://localhost:3001 - -# Server -PORT=3002 -``` - -#### Mobile (.env) - -```env -EXPO_PUBLIC_MANA_AUTH_URL=http://localhost:3001 -EXPO_PUBLIC_BACKEND_URL=http://localhost:3002 -``` - -#### Web (.env) - -```env -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -PUBLIC_BACKEND_URL=http://localhost:3002 -``` - -## Code Style Guidelines - -- **TypeScript**: Strict typing with interfaces -- **Mobile**: Functional components with hooks -- **Web**: Svelte 5 runes mode -- **Styling**: Tailwind CSS everywhere -- **Formatting**: 100 char line limit, 2 space tabs, single quotes - -## AI Models Available - -### Local Models (Ollama - Free, runs on Mac Mini) - -| Model ID | Name | Best For | -| -------- | ---- | -------- | -| ...440101 | Gemma 3 4B (Lokal) | Everyday tasks (default) | -| ...440102 | Qwen2.5 Coder 7B (Lokal) | Code generation (92.7% HumanEval) | -| ...440103 | LLaVA 7B Vision (Lokal) | Image/screenshot analysis | -| ...440104 | Qwen3 VL 4B (Lokal) | Fast image analysis | -| ...440105 | DeepSeek OCR (Lokal) | Text recognition in images | -| ...440106 | Phi 3.5 (Lokal) | Compact, efficient | -| ...440107 | Ministral 3B (Lokal) | Very fast, simple tasks | - -### Cloud Models (OpenRouter - Paid) - -| Model ID | Name | Price | Best For | -| -------- | ---- | ----- | -------- | -| ...440201 | Llama 3.1 8B | $0.05/M | Fast cloud alternative | -| ...440202 | Llama 3.1 70B | $0.35/M | Complex reasoning | -| ...440203 | DeepSeek V3 | $0.14/M | Reasoning at low cost | -| ...440204 | Mistral Small | $0.10/M | General tasks | -| ...440205 | Claude 3.5 Sonnet | $3/M | Best quality | -| ...440206 | GPT-4o Mini | $0.15/M | Balanced performance | - -### Adding New Local Models - -```bash -# Add new models to existing database -pnpm --filter @chat/server db:add-local-models -``` - -## Quick Start - -1. **Get OpenRouter API key** at https://openrouter.ai/keys -2. **Create `.env`** in `apps/chat/apps/server/`: - ```env - OPENROUTER_API_KEY=sk-or-v1-xxx - DATABASE_URL=postgresql://mana:devpassword@localhost:5432/chat - MANA_AUTH_URL=http://localhost:3001 - PORT=3002 - ``` -3. **Start services**: - ```bash - pnpm docker:up # Start PostgreSQL - pnpm dev:chat:full # Start auth + backend + web - ``` -4. **Seed database** (first time only): - ```bash - pnpm --filter @chat/server db:push - pnpm --filter @chat/server db:seed - ``` - -## Important Notes - -1. **Security**: API keys are stored in the server only - never in client apps -2. **Authentication**: Uses Mana Auth (JWT tokens) -3. **Database**: PostgreSQL with Drizzle ORM (uses shared Docker container) -4. **Deployment**: Server runs on port 3002 +The previous standalone "Chat Project Guide" describing a per-product +backend was deleted in the audit cleanup of 2026-04-09 — it had been +inaccurate since the consolidation. Pre-consolidation reference is in +git history. diff --git a/apps/contacts/CLAUDE.md b/apps/contacts/CLAUDE.md index 2409c266e..69396e0c0 100644 --- a/apps/contacts/CLAUDE.md +++ b/apps/contacts/CLAUDE.md @@ -1,260 +1,17 @@ -# Contacts Project Guide +# Contacts — consolidated into the unified Mana app -## Project Structure +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/contacts/apps/server/` and `apps/contacts/apps/web/` +directories have been removed. Active code now lives in: -``` -apps/contacts/ -├── apps/ -│ ├── server/ # Hono/Bun compute server (@contacts/server) - Port 3015 -│ ├── landing/ # Astro marketing landing page (@contacts/landing) -│ ├── web/ # SvelteKit web application (@contacts/web) - Port 5184 -│ └── mobile/ # Expo/React Native mobile app (@contacts/mobile) -├── packages/ -│ └── shared/ # Shared types, utils, configs (@contacts/shared) -└── package.json -``` +- **Backend compute routes**: [`apps/api/src/modules/contacts/routes.ts`](../api/src/modules/contacts/routes.ts) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/contacts/`](../mana/apps/web/src/lib/modules/contacts/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/contacts/`](../mana/apps/web/src/routes/(app)/contacts/) +- **Landing page** (still standalone): [`apps/contacts/apps/landing/`](apps/landing/) -## Commands +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -### Root Level (from monorepo root) - -```bash -pnpm contacts:dev # Run all contacts apps -pnpm dev:contacts:mobile # Start mobile app -pnpm dev:contacts:web # Start web app -pnpm dev:contacts:landing # Start landing page -pnpm dev:contacts:server # Start server -pnpm dev:contacts:app # Start web + server together -pnpm dev:contacts:local # Start web + sync (no auth needed) -``` - -### Mobile App (apps/contacts/apps/mobile) - -```bash -pnpm dev # Start Expo dev server -pnpm ios # Run on iOS simulator -pnpm android # Run on Android emulator -``` - -### Backend (apps/contacts/apps/backend) - -```bash -pnpm dev # Start with hot reload -pnpm build # Build for production -pnpm start:prod # Start production server -pnpm db:push # Push schema to database -pnpm db:studio # Open Drizzle Studio -``` - -### Web App (apps/contacts/apps/web) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -pnpm preview # Preview production build -``` - -### Landing Page (apps/contacts/apps/landing) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -``` - -## Technology Stack - -- **Mobile**: React Native 0.81 + Expo SDK 54, NativeWind, Expo Router, Zustand -- **Web**: SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS -- **Landing**: Astro 5.x, Tailwind CSS -- **Backend**: NestJS 10, Drizzle ORM, PostgreSQL -- **Types**: TypeScript 5.x - -## Architecture - -### Backend API Endpoints - -| Endpoint | Method | Description | -| ------------------------------------- | ------ | -------------------------- | -| `/api/v1/health` | GET | Health check | -| `/api/v1/contacts` | GET | Get user's contacts | -| `/api/v1/contacts` | POST | Create new contact | -| `/api/v1/contacts/:id` | GET | Get contact details | -| `/api/v1/contacts/:id` | PATCH | Update contact | -| `/api/v1/contacts/:id` | DELETE | Delete contact | -| `/api/v1/contacts/:id/favorite` | POST | Toggle favorite | -| `/api/v1/contacts/:id/archive` | POST | Toggle archive | -| `/api/v1/contacts/:id/photo` | POST | Upload contact photo | -| `/api/v1/tags` | GET | Get user's tags | -| `/api/v1/tags` | POST | Create new tag | -| `/api/v1/tags/:id` | DELETE | Delete tag | -| `/api/v1/contacts/:id/notes` | GET | Get contact notes | -| `/api/v1/contacts/:id/notes` | POST | Add note to contact | -| `/api/v1/notes/:id` | PATCH | Update note | -| `/api/v1/notes/:id` | DELETE | Delete note | -| `/api/v1/contacts/:id/activities` | GET | Get contact activities | -| `/api/v1/contacts/:id/activities` | POST | Log activity | -| `/api/v1/import/preview` | POST | Preview file import (vCard/CSV) | -| `/api/v1/import/execute` | POST | Execute contact import | -| `/api/v1/import/template/csv` | GET | Download CSV template | -| `/api/v1/google/auth-url` | GET | Get Google OAuth URL | -| `/api/v1/google/callback` | POST | Exchange OAuth code | -| `/api/v1/google/status` | GET | Get Google connection status | -| `/api/v1/google/disconnect` | DELETE | Disconnect Google account | -| `/api/v1/google/contacts` | GET | Fetch Google contacts | -| `/api/v1/google/import` | POST | Import from Google | -| `/api/v1/export` | GET | Quick export all contacts | -| `/api/v1/export` | POST | Export with options | -| `/api/v1/organizations/:orgId/contacts` | GET | Get organization contacts | -| `/api/v1/teams/:teamId/contacts` | GET | Get team contacts | -| `/api/v1/contacts/:id/share` | POST | Share contact | - -### Database Schema - -**contacts** - Contact information - -- `id` (UUID) - Primary key -- `user_id` (VARCHAR) - User reference -- `first_name`, `last_name`, `display_name`, `nickname` (VARCHAR) -- `email`, `phone`, `mobile` (VARCHAR) -- `street`, `city`, `postal_code`, `country` (VARCHAR) -- `company`, `job_title`, `department` (VARCHAR) -- `website`, `birthday`, `notes`, `photo_url` (VARCHAR/TEXT/DATE) -- `is_favorite`, `is_archived` (BOOLEAN) -- `organization_id`, `team_id` (UUID) - Mana integration -- `visibility` (VARCHAR) - private/team/organization/public -- `shared_with` (JSONB) - Array of user IDs -- `created_at`, `updated_at` (TIMESTAMP) - -**contact_tags** - Tags for contacts - -- `id` (UUID) - Primary key -- `user_id` (VARCHAR) - User reference -- `name` (VARCHAR) - Tag name -- `color` (VARCHAR) - Tag color - -**contact_to_tags** - Many-to-many relation - -- `contact_id` (UUID) - Contact reference -- `tag_id` (UUID) - Tag reference - -**contact_notes** - Notes for contacts - -- `id` (UUID) - Primary key -- `contact_id` (UUID) - Contact reference -- `user_id` (VARCHAR) - User reference -- `content` (TEXT) - Note content -- `is_pinned` (BOOLEAN) -- `created_at`, `updated_at` (TIMESTAMP) - -**contact_activities** - Activity log - -- `id` (UUID) - Primary key -- `contact_id` (UUID) - Contact reference -- `user_id` (VARCHAR) - User reference -- `activity_type` (VARCHAR) - created/updated/called/emailed/met/note_added -- `description` (TEXT) -- `metadata` (JSONB) -- `created_at` (TIMESTAMP) - -**connected_accounts** - OAuth provider connections (Google, etc.) - -- `id` (UUID) - Primary key -- `user_id` (VARCHAR) - User reference -- `provider` (VARCHAR) - Provider name (e.g., 'google') -- `provider_account_id` (VARCHAR) - Provider's user ID -- `provider_email` (VARCHAR) - Provider account email -- `access_token` (TEXT) - OAuth access token (encrypted) -- `refresh_token` (TEXT) - OAuth refresh token (encrypted) -- `token_expires_at` (TIMESTAMP) - Token expiration time -- `scope` (TEXT) - Granted OAuth scopes -- `provider_data` (JSONB) - Additional provider-specific data -- `created_at`, `updated_at` (TIMESTAMP) - -### Environment Variables - -#### Backend (.env) - -``` -NODE_ENV=development -PORT=3015 -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/contacts -MANA_AUTH_URL=http://localhost:3001 -CORS_ORIGINS=http://localhost:5173,http://localhost:5184,http://localhost:8081 -S3_ENDPOINT=http://localhost:9000 -S3_REGION=us-east-1 -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -S3_BUCKET=contacts-photos - -# Google OAuth (for contacts import) -# Get credentials from https://console.cloud.google.com/apis/credentials -GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com -GOOGLE_CLIENT_SECRET=your-client-secret -GOOGLE_REDIRECT_URI=http://localhost:5184/data?tab=import&source=google -``` - -#### Mobile (.env) - -``` -EXPO_PUBLIC_BACKEND_URL=http://localhost:3015 -EXPO_PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -#### Web (.env) - -``` -PUBLIC_BACKEND_URL=http://localhost:3015 -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -## Shared Packages - -### @contacts/shared - -- Types: `Contact`, `ContactGroup`, `ContactTag`, `ContactNote`, `ContactActivity` -- Utils: Search, filter, import/export functions -- Configs: App configuration - -## Quick Input Syntax - -The NewContactModal includes a NL quick-input bar that parses contact info and pre-fills form fields: - -``` -"Max Mustermann @ACME Corp max@example.com +49 170 1234567 #kunde" -→ firstName: Max, lastName: Mustermann, company: ACME Corp, - email: max@example.com, phone: +49 170..., tags: [kunde] -``` - -Recognized patterns: -- **Name**: First and last name (remaining text after extraction) -- **Company**: `@CompanyName` or `bei CompanyName` or `von CompanyName` -- **Email**: Standard email format -- **Phone**: International (+49...), German (0123...), or 6+ digit numbers -- **Tags**: `#tag1 #tag2` - -Type the full text and press **Enter** to apply. Fields are pre-filled and can be edited before saving. - -### Live Duplicate Detection - -While typing in the name or email fields, the system checks for duplicates against IndexedDB in real-time: -- **Email**: Exact match (case-insensitive) -- **Name**: Fuzzy match (Levenshtein distance, tolerates typos) -- Shows warning with matched contact name, company, and match type - -Implementation: `duplicate-detector.ts` — runs fully offline, no server calls. - -## Code Style Guidelines - -- **TypeScript**: Strict typing with interfaces -- **Mobile**: Functional components with hooks, Zustand for state -- **Web**: Svelte 5 runes mode (`$state`, `$derived`, `$effect`) -- **Styling**: Tailwind CSS / NativeWind -- **Formatting**: Prettier with project config - -## Important Notes - -1. **Authentication**: Uses Mana Auth (JWT in Authorization header) -2. **Database**: PostgreSQL with Drizzle ORM -3. **Port**: Backend runs on port 3015, Web on port 5184 by default -4. **Storage**: Uses MinIO/S3 for contact photos via @mana/shared-storage -5. **Mana Integration**: Contacts can be linked to Organizations and Teams +The previous standalone "Contacts Project Guide" was deleted in the +audit cleanup of 2026-04-09 — it had been inaccurate since the +consolidation. Pre-consolidation reference is in git history. diff --git a/apps/context/CLAUDE.md b/apps/context/CLAUDE.md index 8335a1269..e81cbb1e6 100644 --- a/apps/context/CLAUDE.md +++ b/apps/context/CLAUDE.md @@ -1,210 +1,24 @@ -# Context App +# Context — consolidated into the unified Mana app -AI-powered document management and context system for knowledge organization. +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/context/apps/backend/` (NestJS) and +`apps/context/apps/web/` directories have been removed. Active code now +lives in: -| App | Port | URL | -|-----|------|-----| -| Backend | 3020 | http://localhost:3020 | -| Web App | 5192 | http://localhost:5192 | -| Mobile | 8081 | Expo Go | +- **Backend compute routes**: [`apps/api/src/modules/context/routes.ts`](../api/src/modules/context/routes.ts) (AI text generation via mana-llm, server-side credit deduction) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/context/`](../mana/apps/web/src/lib/modules/context/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/context/`](../mana/apps/web/src/routes/(app)/context/) +- **Mobile app**: [`apps/context/apps/mobile/`](apps/mobile/) -## Structure +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -``` -apps/context/ -├── apps/ -│ ├── backend/ # Hono/Bun compute server (@context/server) -│ │ └── src/ -│ │ ├── main.ts -│ │ ├── app.module.ts -│ │ ├── db/ # Drizzle schemas + migrations -│ │ │ ├── schema/ -│ │ │ │ ├── spaces.schema.ts -│ │ │ │ ├── documents.schema.ts -│ │ │ │ ├── token-transactions.schema.ts -│ │ │ │ ├── model-prices.schema.ts -│ │ │ │ └── user-tokens.schema.ts -│ │ │ ├── connection.ts -│ │ │ ├── database.module.ts -│ │ │ ├── migrate.ts -│ │ │ └── seed.ts -│ │ ├── space/ # Space CRUD -│ │ ├── document/ # Document CRUD + versions + tags -│ │ ├── ai/ # AI generation (Azure + Google) -│ │ ├── token/ # Token balance + stats -│ │ └── common/ -│ ├── web/ # SvelteKit web application (@context/web) -│ ├── mobile/ # Expo React Native app (@context/mobile) -│ └── landing/ # (Planned) Astro Landing Page -├── packages/ # Project-specific shared code -└── package.json # Workspace root -``` +The previous "Context App" guide describing a NestJS backend with its +own spaces/documents/token tables was deleted in the audit cleanup of +2026-04-09 — it had been inaccurate since the consolidation. The +token-economy logic now lives in `mana-credits`. Pre-consolidation +reference is in git history. -## Development Commands - -```bash -# From monorepo root -pnpm dev:context:full # Start auth + backend + web (with DB setup) -pnpm dev:context:server # Start backend only (port 3020) -pnpm dev:context:web # Start web only (port 5192) -pnpm dev:context:app # Start web + backend together -pnpm dev:context:mobile # Start mobile app - -# Database -pnpm context:db:push # Push schema to database -pnpm context:db:studio # Open Drizzle Studio -pnpm context:db:seed # Seed model prices -pnpm setup:db:context # Create DB + push schema -``` - -## Tech Stack - -| Layer | Technology | -|-------|------------| -| **Backend** | Hono + Bun, Drizzle ORM, PostgreSQL | -| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 | -| **Mobile** | React Native 0.76 + Expo SDK 52, NativeWind | -| **Auth** | Mana Auth (JWT) | -| **AI** | Azure OpenAI (GPT-4.1), Google Gemini (Pro, Flash) | -| **i18n** | svelte-i18n (DE, EN) | - -## Core Features - -- **Spaces**: Organize documents into collections with prefix-based short IDs -- **Documents**: Text, context references, and AI prompts with versioning -- **AI Generation**: Multi-model support (Azure OpenAI, Google Gemini) -- **Token Economy**: Track and manage AI usage credits per user -- **Document Versioning**: AI-generated summaries, continuations, rewrites - -## Backend API Endpoints - -### Health - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/health` | GET | Health check | - -### Spaces - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/spaces` | GET | List user's spaces | -| `/api/v1/spaces` | POST | Create space | -| `/api/v1/spaces/:id` | GET | Get space details | -| `/api/v1/spaces/:id` | PUT | Update space | -| `/api/v1/spaces/:id` | DELETE | Delete space (cascades documents) | - -### Documents - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/documents` | GET | List documents (?spaceId=&preview=true&limit=) | -| `/api/v1/documents/recent` | GET | Recent documents (?limit=) | -| `/api/v1/documents` | POST | Create document | -| `/api/v1/documents/:id` | GET | Get document | -| `/api/v1/documents/:id` | PUT | Update document | -| `/api/v1/documents/:id` | DELETE | Delete document | -| `/api/v1/documents/:id/tags` | PUT | Update document tags | -| `/api/v1/documents/:id/pinned` | PUT | Toggle pinned | -| `/api/v1/documents/:id/versions` | GET | Get document versions | -| `/api/v1/documents/:id/versions` | POST | Create AI version | - -### AI - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/ai/generate` | POST | Generate text (server-side AI) | -| `/api/v1/ai/estimate` | POST | Estimate token cost | - -### Tokens - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/tokens/balance` | GET | Get user token balance | -| `/api/v1/tokens/stats` | GET | Usage stats (?timeframe=day\|week\|month\|year) | -| `/api/v1/tokens/transactions` | GET | Transaction history (?limit=&offset=) | -| `/api/v1/tokens/models` | GET | Available model prices | - -## Database Schema - -### spaces -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `user_id` | TEXT | Owner | -| `name` | VARCHAR(255) | Space name | -| `description` | TEXT | Optional description | -| `settings` | JSONB | Space settings | -| `pinned` | BOOLEAN | Pinned in sidebar | -| `prefix` | VARCHAR(10) | Short ID prefix (e.g. "A") | -| `text_doc_counter` | INTEGER | Counter for text docs | -| `context_doc_counter` | INTEGER | Counter for context docs | -| `prompt_doc_counter` | INTEGER | Counter for prompt docs | - -### documents -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `user_id` | TEXT | Owner | -| `space_id` | UUID | FK to spaces (cascade delete) | -| `title` | VARCHAR(500) | Document title | -| `content` | TEXT | Document content | -| `type` | VARCHAR(20) | text / context / prompt | -| `short_id` | VARCHAR(20) | Short ID (e.g. "AD1") | -| `pinned` | BOOLEAN | Pinned flag | -| `metadata` | JSONB | Tags, word count, version info | - -### token_transactions -| Column | Type | Description | -|--------|------|-------------| -| `id` | UUID | Primary key | -| `user_id` | TEXT | User | -| `amount` | INTEGER | Tokens used (negative for usage) | -| `transaction_type` | VARCHAR(50) | usage / bonus / purchase | -| `model_used` | VARCHAR(100) | AI model name | -| `prompt_tokens` | INTEGER | Input tokens | -| `completion_tokens` | INTEGER | Output tokens | -| `cost_usd` | NUMERIC(10,6) | Actual USD cost | - -### model_prices -| Column | Type | Description | -|--------|------|-------------| -| `model_name` | VARCHAR(100) | Unique model name | -| `input_price_per_1k_tokens` | NUMERIC(10,6) | Input price | -| `output_price_per_1k_tokens` | NUMERIC(10,6) | Output price | -| `tokens_per_dollar` | INTEGER | App tokens per USD | - -### user_tokens -| Column | Type | Description | -|--------|------|-------------| -| `user_id` | TEXT | Primary key | -| `token_balance` | INTEGER | Current balance | -| `monthly_free_tokens` | INTEGER | Free monthly allocation | - -## Environment Variables - -### Backend (.env) -```env -NODE_ENV=development -PORT=3020 -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/context -MANA_AUTH_URL=http://localhost:3001 -AZURE_OPENAI_API_KEY=your-key -AZURE_OPENAI_ENDPOINT=https://your-endpoint.openai.azure.com/ -GOOGLE_API_KEY=your-key -``` - -### Web (.env) -```env -PUBLIC_BACKEND_URL=http://localhost:3020 -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -## Important Patterns - -1. **API Client pattern** - All web services use `@mana/shared-api-client` (Go-style `{ data, error }`) -2. **Svelte 5 runes** - `$state`, `$derived`, `$effect` throughout -3. **Server-side AI keys** - API keys only on backend, never in frontend -4. **Auto word/token count** - Backend calculates on create/update -5. **Optimistic updates** - Immediate UI feedback in stores -6. **Document versioning** - AI generations linked via `parent_document` in metadata +> **Note:** The legacy `apps/context/pnpm-lock.yaml` (242 KB, separate +> workspace setup) and the broken `dev:web` / `dev:server` filter scripts +> in `apps/context/package.json` are tracked in audit items #2 and #26. diff --git a/apps/context/package.json b/apps/context/package.json index 162f63ad2..a1f05c94f 100644 --- a/apps/context/package.json +++ b/apps/context/package.json @@ -3,9 +3,6 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "turbo run dev", - "dev:web": "pnpm --filter @context/web dev", - "dev:server": "pnpm --filter @context/server dev", "dev:mobile": "pnpm --filter @context/mobile dev" } } diff --git a/apps/guides/CLAUDE.md b/apps/guides/CLAUDE.md index 5373a37c5..e83959c9a 100644 --- a/apps/guides/CLAUDE.md +++ b/apps/guides/CLAUDE.md @@ -1,108 +1,21 @@ -# Guides — CLAUDE.md +# Guides — consolidated into the unified Mana app -Mana Guides is a local-first step-by-step guide app (SOPs, recipes, tutorials, learning paths). -Port: **5200** (web), **3027** (server) -Theme: Teal `#0d9488` -Tier: `beta` +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/guides/apps/server/` and `apps/guides/apps/web/` +directories have been removed. Active code now lives in: -## Apps +- **Backend compute routes**: [`apps/api/src/modules/guides/routes.ts`](../api/src/modules/guides/routes.ts) (URL/text/AI import, structured guide generation via mana-search + mana-llm) +- **Frontend module** (mostly static content): [`apps/mana/apps/web/src/lib/modules/guides/`](../mana/apps/web/src/lib/modules/guides/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/guides/`](../mana/apps/web/src/routes/(app)/guides/) -| App | Package | Port | Description | -|-----|---------|------|-------------| -| `apps/web` | `@guides/web` | 5200 | SvelteKit 5 local-first UI | -| `apps/server` | `@guides/server` | 3025 | Hono/Bun compute server (import, share) | +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -## Dev Commands +The previous standalone "Guides" guide was deleted in the audit cleanup +of 2026-04-09 — it had been inaccurate since the consolidation. +Pre-consolidation reference is in git history. -```bash -pnpm dev:guides:web # Web only -pnpm dev:guides:server # Server only -pnpm dev:guides:app # Server + web -pnpm dev:guides:local # Sync + server + web (no auth) -pnpm dev:guides:full # Auth + sync + server + web -``` - -## Architecture - -**Local-first**: All CRUD goes through `guidesStore` (Dexie.js IndexedDB), synced via mana-sync. -**Server (port 3025)**: Compute-only — web import via mana-search + mana-llm, shareable links. - -### Data Model - -``` -LocalGuide → has many LocalSection, LocalStep -LocalSection → has many LocalStep (order field) -LocalStep → belongs to LocalGuide, optional LocalSection -LocalCollection → has many LocalGuide (ordered list) -LocalRun → belongs to LocalGuide, stepStates: Record -``` - -### Collections - -| Collection | Index | Description | -|------------|-------|-------------| -| `guides` | category, difficulty, collectionId | Guide library | -| `sections` | guideId, order | Optional sections within a guide | -| `steps` | guideId, sectionId, order | Steps (instruction/warning/tip/checkpoint/code) | -| `collections` | type | Path or Library groupings | -| `runs` | guideId, startedAt | Execution history | - -## Routes - -``` -/ Library (guide grid, search, filters) -/guide/[id] Guide detail + edit mode + run history -/guide/[id]/run Run mode (?mode=scroll|focus) -/collections Collections grid -/collections/[id] Collection detail with progress -/history All run history -/shared/[token] Public shared guide (no auth needed) — Phase 4 -/(auth)/login Login page -``` - -## Server Routes (port 3025) - -``` -POST /api/v1/import/url → fetch URL → mana-search extract → mana-llm → { guide, sections } -POST /api/v1/import/text → raw text/markdown → mana-llm → { guide, sections } -POST /api/v1/import/ai → AI prompt → mana-llm → { guide, sections } -POST /api/v1/share → create shareable link (7-day TTL) → { token, url, expiresAt } -GET /api/v1/share/:token → retrieve shared guide snapshot -GET /health → service health check -``` - -## Environment Variables - -```env -# Web -PUBLIC_SYNC_SERVER_URL=ws://localhost:3050 -PUBLIC_GUIDES_SERVER_URL=http://localhost:3027 - -# Server -PORT=3027 -CORS_ORIGINS=http://localhost:5200 -MANA_SEARCH_URL=http://localhost:3021 -MANA_LLM_URL=http://localhost:3030 -PUBLIC_BASE_URL=http://localhost:5200 -``` - -## Key Files - -| File | Purpose | -|------|---------| -| `apps/web/src/lib/data/local-store.ts` | 5 Dexie collections with TypeScript types | -| `apps/web/src/lib/data/guest-seed.ts` | 3 demo guides, 1 collection for onboarding | -| `apps/web/src/lib/stores/guides.svelte.ts` | Guide/section/step/collection mutations | -| `apps/web/src/lib/stores/runs.svelte.ts` | Run start/step state/complete mutations | -| `apps/web/src/routes/(app)/guide/[id]/run/+page.svelte` | Scroll + focus run modes | -| `apps/server/src/routes/import.ts` | URL/text/AI import via mana-llm | -| `apps/server/src/routes/share.ts` | Shareable guide links (in-memory MVP) | - -## Phase Status - -| Phase | Status | Description | -|-------|--------|-------------| -| 1 | Done | Core CRUD, local-first, guest seed, library/detail/run views | -| 2 | Done | Collections, StepEditorModal, CollectionEditModal, inline step add | -| 3 | In progress | Hono server (import + share done), ImportModal frontend, share button | -| 4 | Planned | DB persistence for shares, /shared/[token] public route, XP/gamification | +> **Note:** Guides is one of the few modules that doesn't own a Dexie +> collection (the catalogue is hardcoded in `index.ts`); only `tags` are +> stored. See `apps/mana/CLAUDE.md` for the standard module pattern this +> intentionally diverges from. diff --git a/apps/memoro/CLAUDE.md b/apps/memoro/CLAUDE.md index 71bc15a55..96ce76c66 100644 --- a/apps/memoro/CLAUDE.md +++ b/apps/memoro/CLAUDE.md @@ -1,459 +1,63 @@ -# CLAUDE.md +# Memoro -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +AI-powered voice recording + memo management. Memoro is a **hybrid**: its +frontend was consolidated into the unified Mana web app, but its backend +was kept as standalone services (Hono + Supabase) because of the +audio-processing pipeline and the legacy Supabase Storage bucket layout. -## Repository Overview +## Where things live -Memoro is a monorepo containing an AI-powered voice recording and memo management application with two apps: +| Surface | Path | Notes | +|---------|------|-------| +| **Web frontend** (local-first, in unified Mana app) | [`apps/mana/apps/web/src/lib/modules/memoro/`](../mana/apps/web/src/lib/modules/memoro/) | Same module pattern as every other module — Dexie collections, Svelte 5 stores, runes UI. Reachable via `/memoro` route in mana.how. | +| **Native mobile app** | [`apps/memoro/apps/mobile/`](apps/mobile/) | React Native + Expo SDK 55. Talks directly to `memoro-server` (NOT to mana.how). Build via EAS, see `apps/mobile/eas.json`. | +| **Backend compute** | [`apps/memoro/apps/server/`](apps/server/) (`@memoro/server`) | Hono + Bun. Handles memo CRUD, transcription callbacks, spaces, invites, credits, settings, cleanup, meetings. **Still uses Supabase** for some legacy state. Deployed as `memoro-server` container. | +| **Audio processing** | [`apps/memoro/apps/audio-server/`](apps/audio-server/) | Separate Hono+Bun service for audio uploads + transcoding. Deployed as `memoro-audio-server` container. | +| **Landing page** | [`apps/memoro/apps/landing/`](apps/landing/) | Astro static landing → Cloudflare Pages | -- **Mobile App** (`apps/mobile/`): React Native + Expo cross-platform app (iOS, Android, Web) -- **Web App** (`apps/web/`): SvelteKit companion web application +## Why memoro is not (yet) in `apps/api` -Both apps share the same Supabase backend. +Most consolidated products migrated their compute routes into +`apps/api/src/modules/{name}/`. Memoro stayed standalone because: -## Development Commands +1. **Audio pipeline.** The audio-server runs background transcoding/ + upload jobs that don't fit the request-response shape of `apps/api`. +2. **Legacy Supabase coupling.** Memo and storage records still live + in Supabase tables (`storage.objects`, RLS policies on `memos`). + Migrating to mana_platform was descoped in the consolidation sprint. +3. **Three deploy targets.** `memoro-server`, `memoro-audio-server`, + and the mobile app all need to coordinate. Easier to evolve as one + unit while migration is in flight. -### Mobile App (`apps/mobile/`) +A future cleanup item is to either fold the routes into `apps/api` +(once Supabase is gone) or document this exception explicitly in the +root architecture overview. -```bash -# Development -npm start # Start Expo dev server -npm run start:dev # Start with dev environment -npm run start:prod # Start with prod environment -npm run ios # Run on iOS simulator -npm run android # Run on Android emulator -npm run web # Run web version -npm run web:dev # Run web with dev environment +## Production deployment -# Code Quality -npm run lint # Run ESLint and Prettier check -npm run lint:fix # Auto-fix linting issues -npm run lint:unused # Find unused imports/vars -npm run format # Format code with ESLint + Prettier - -# Build & Deploy -npm run prebuild # Generate native projects -npm run rebuild # Clean rebuild (removes node_modules, ios/, android/) -npm run web:build # Build for web deployment -eas build --profile development # Development build -eas build --profile preview # Preview build -eas build --profile production # Production build -``` - -### Web App (`apps/web/`) - -```bash -npm run dev # Start development server -npm run build # Build for production -npm run preview # Preview production build -npm run check # Run svelte-check -npm run check:watch # Watch mode for svelte-check -``` - -## Architecture - -### Mobile App Architecture - -**Framework Stack:** -- React Native 0.83.2 + Expo SDK 55 -- Expo Router (file-based routing) -- TypeScript -- NativeWind (Tailwind CSS for React Native) -- Zustand (state management) - -**Key Design Patterns:** - -1. **Feature-Based Architecture** (`features/`): - - Each feature is self-contained with its own services, hooks, components, and stores - - Features: auth, audioRecordingV2, memos, spaces, credits, subscription, i18n, theme, etc. - - 33 feature modules in total - -2. **Atomic Design System** (`components/`): - - `atoms/`: Basic UI components (Button, Input, Text, Icon, etc.) - - `molecules/`: Composite components (MemoPreview, RecordingBar, TagSelector, etc.) - - `organisms/`: Complex components (AudioRecorder, Memory, TranscriptDisplay, etc.) - - `statistics/`: Specialized analytics components - -3. **Route Structure** (`app/`): - - `(public)/`: Unauthenticated routes (login, register) - - `(protected)/`: Authenticated routes with auth guard - - `(tabs)/`: Main tab navigation (home, memos, spaces) - - `(memo)/[id]`: Dynamic memo detail pages - - `(space)/[id]`: Dynamic space detail pages - - Uses Expo Router's file-based routing with typed routes enabled - -### Authentication System - -Uses a **middleware-based authentication bridge** between the app and Supabase: +Both backends are part of `docker-compose.macmini.yml`: ``` -Mobile App → Middleware Auth Service → Supabase +memoro-server (apps/memoro/apps/server) — main backend +memoro-audio-server (apps/memoro/apps/audio-server) — audio worker ``` -**Key Points:** -- Middleware issues three tokens: `manaToken`, `appToken` (Supabase-compatible JWT), `refreshToken` -- Tokens stored securely via platform-specific `safeStorage` utility -- Auth state managed via `AuthContext` provider -- Supabase client configured to use JWT from middleware -- Row Level Security (RLS) policies use JWT claims (`sub`, `role`, `app_id`) -- Supports email/password, Google Sign-In, and Apple Sign-In -- Automatic token refresh mechanism +The mobile app builds via EAS — not part of the monorepo CI. -See `apps/mobile/features/auth/README.md` for detailed authentication flow. +## Known issues / cleanup items -### Audio Recording System +- **`@mana/notify-client` is imported by `apps/memoro/apps/server/src/lib/notify.ts:6` but NOT declared as a dependency** in `apps/memoro/apps/server/package.json`. Currently works via hoisted node_modules but should either be added as a workspace dep or replaced with a direct call to `mana-notify`. Tracked in `docs/REFACTORING_AUDIT_2026_04.md` items #29. +- **`apps/memoro/apps/server` still pulls `@supabase/supabase-js`** — not a bug, but flagged as a dependency to remove once Supabase migration completes. +- **No `apps/memoro/apps/web`** — was removed during the consolidation. The old SvelteKit "companion web app" lives now under `apps/mana/apps/web/src/lib/modules/memoro/`. -**AudioRecordingV2** is the current audio recording implementation: +## For monorepo-wide patterns -- Uses `expo-audio` (migrated from deprecated `expo-av`) -- Platform-specific services: `IOSRecordingService`, `AndroidRecordingService` -- Zustand store for state management (`recordingStore`) -- Comprehensive error handling with retry strategies -- Android: Foreground service with wake locks -- iOS: Background audio capability with `mixWithOthers` mode -- Real-time status updates via polling -- Prevents zero-byte recordings with validation -- **Background recording works correctly** - continues when app is backgrounded or locked +See [root `CLAUDE.md`](../../CLAUDE.md) for the overall architecture and +[`apps/mana/CLAUDE.md`](../mana/CLAUDE.md) for the unified web app's +module pattern (which the memoro frontend follows). -**iOS Background Recording:** -- Uses `interruptionMode: 'mixWithOthers'` for background recording support -- Recording continues when pressing home button, switching apps, or locking device -- Audio session automatically restored when returning to foreground -- JavaScript timers suspended in background, but native recording continues -- Handles real interruptions (phone calls, Siri) automatically - -**Recording Options:** -- High quality: M4A format with AAC encoding (MONO for compatibility) -- Presets: HIGH_QUALITY, MEDIUM_QUALITY, LOW_QUALITY, VOICE_MEMO -- Max duration and size limits -- Pause/resume support -- Audio level metering for waveform visualization -- Optimized for voice (MONO, 96 quality) to prevent FFmpeg 'chnl' box errors - -**Key Technical Details:** -- MONO recording prevents iOS spatial audio metadata issues -- Audio session verification on cold start prevents first-recording failures -- Status polling restarts when app returns from background -- Full duration captured (foreground + background time) - -See `apps/mobile/features/audioRecordingV2/README.md` for full details. -See `apps/mobile/features/audioRecordingV2/TROUBLESHOOTING.md` for bug fixes and solutions. - -### AI Processing System - -**Blueprints:** -- Reusable AI analysis patterns for different use cases -- Examples: Text Analysis, Creative Writing, Meeting Notes -- Each blueprint has localized advice tips (32 languages) -- Stored in Supabase with public/private visibility - -**Prompts:** -- Specific AI tasks for content transformation -- Examples: Summary, To-Do extraction, Translation, Q&A -- Associated with blueprints via `blueprint_prompts` join table -- Multi-language support (German/English minimum) - -**Content Organization:** -- 8 categories: Coaching, Crafts, Healthcare, Journal, Journalism, Office, Sales, University -- Categories provide contextual grouping for blueprints/prompts - -See `apps/mobile/docs/blueprints_and_prompts.md` for full documentation. - -### Theme System - -**Multi-Theme Support:** -- 4 theme variants: Lume (gold), Nature (green), Stone (slate), Ocean (blue) -- Each theme has light and dark mode variants -- 13 semantic color tokens per theme (primary, secondary, borders, backgrounds, text) -- Theme state managed via `ThemeProvider` context -- Dark mode detection + manual override -- All colors defined in `tailwind.config.js` - -**Markdown Rendering:** -- Full Markdown support in memo display -- Theme-aware styles adapt to light/dark mode -- Centralized styles in `features/theme/markdownStyles.ts` -- Hybrid rendering with auto-detection - -### Spaces (Collaboration) - -**Team Workspaces:** -- Create unlimited collaborative spaces -- Role-based permissions (owner, member) -- Memo sharing within spaces -- Email-based invitation system -- Credit pools shared among team members -- Real-time sync via Supabase Realtime - -**Backend Integration:** -- RESTful API for space management -- RLS policies for access control -- Space-specific memo filtering - -See `apps/mobile/docs/SPACES.md` for implementation details. - -### Subscription & Credits - -**Mana Credit System:** -- Backend-driven transparent pricing -- Real-time credit validation before operations -- Usage tracking and analytics -- Credit sharing in team spaces -- Free tier: 150 Mana + 5 daily Mana - -**RevenueCat Integration:** -- Cross-platform (iOS, Android, Web) -- Subscription lifecycle management -- User identification tied to auth -- Purchase restoration across devices -- 4 individual plans: Stream (€5.99), River (€14.99), Lake (€29.99), Ocean (€49.99) -- Team and Enterprise plans available - -### Internationalization - -**32 Languages Supported:** -- Arabic, Bengali, Bulgarian, Chinese, Czech, Danish, Dutch, English, Estonian, Finnish, French, Gaelic, German, Greek, Hindi, Croatian, Hungarian, Indonesian, Italian, Japanese, Korean, Lithuanian, Latvian, Maltese, Norwegian, Persian, Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovenian, Spanish, Swedish, Turkish, Ukrainian, Urdu, Vietnamese - -**Implementation:** -- `react-i18next` for translations -- Automatic device language detection -- Persistent user preference storage -- RTL support for Arabic/Hebrew -- Translation files in `features/i18n/translations/` - -### Real-Time Features - -**Supabase Realtime:** -- Live memo updates (INSERT, UPDATE, DELETE) -- Real-time collaboration in spaces -- `MemoRealtimeProvider` context for subscriptions -- Automatic reconnection handling -- RLS-aware subscriptions - -### Platform-Specific Notes - -**Web Platform:** -- Uses `.web.ts` file extensions for web-specific implementations -- `safeStorage.web.ts` uses localStorage (vs AsyncStorage on native) -- Web Audio API for recording (vs expo-audio) -- Some features unavailable: push notifications, haptics, native gestures - -**iOS:** -- Background audio capability required -- Audio session management -- Apple Sign-In integration -- RevenueCat StoreKit 2 - -**Android:** -- Foreground service for recording -- Wake lock to prevent sleep -- Android 16+ requires foreground to start recording -- Google Sign-In integration - -## Environment Configuration - -The mobile app uses environment-specific `.env` files: - -- `.env.dev`: Development environment (copy from `.env.dev.example`) -- `.env.prod`: Production environment (copy from `.env.prod.example`) -- `.env.local`: Active environment (auto-generated by npm scripts) - -**Key Environment Variables:** -- `EXPO_PUBLIC_SUPABASE_URL`: Supabase project URL -- `EXPO_PUBLIC_SUPABASE_ANON_KEY`: Supabase anon key -- `EXPO_PUBLIC_MIDDLEWARE_API_URL`: Middleware auth service URL -- `EXPO_PUBLIC_APPID`: Application ID for middleware -- RevenueCat keys for iOS/Android - -## Code Quality - -**Linting:** -- ESLint with TypeScript plugin -- React/React Native rules -- Unused imports auto-removal -- Configuration in `eslint.config.js` - -**Formatting:** -- Prettier with Tailwind plugin -- Auto-format on save recommended - -**TypeScript:** -- Strict mode enabled -- Typed routes from Expo Router -- Type definitions in `types/` and feature-specific types - -## Migration Notes - -**Expo SDK 55 (Current):** -- React Native 0.83.2, React 19.2 -- Native `allowsBackgroundRecording` support in expo-audio (no more workarounds needed) -- All Expo packages use `^55.x.x` version scheme -- New Architecture is the default (Legacy Architecture dropped) -- Android compileSdkVersion/targetSdkVersion 36 - -**Expo SDK 54 Migration (Historical):** -- Migrated from `expo-av` to `expo-audio` -- New audio recording API (`AudioModule.AudioRecorder`) -- Status polling instead of callbacks -- See `EXPO_54_AUDIO_RECORDING_MIGRATION.md` - -**SvelteKit Web App:** -- Separate web app being built as companion -- Shares Supabase backend with mobile app -- See `SVELTEKIT_MIGRATION_ANALYSIS.md` for migration plan - -## Testing Strategy - -**Manual Testing:** -- Test on both iOS and Android before commits -- Verify web platform compatibility -- Check dark mode and all theme variants -- Test with different languages - -**Platform Matrix:** -- iOS (simulator + device) -- Android (emulator + device) -- Web (Chrome, Safari, Firefox) - -## Common Patterns - -### Creating a New Feature - -1. Create feature directory in `features/` -2. Add subdirectories: `components/`, `hooks/`, `services/`, `store/`, `types/` -3. Export public API via `index.ts` -4. Add feature-specific README if complex -5. Update this CLAUDE.md if architectural - -### Adding a New Route - -1. Add file in `app/` directory following Expo Router conventions -2. Use `(protected)/` group if authentication required -3. Use `[id]` for dynamic routes -4. Enable typed routes in `app.json` (already enabled) -5. Import route types from `expo-router` - -### Working with Zustand Stores - -```typescript -// Create store -export const useMyStore = create((set, get) => ({ - // state - data: null, - - // actions - setData: (data) => set({ data }), - - // computed/derived - getData: () => get().data, -})); -``` - -Stores are located in: -- Global: `store/store.ts` -- Feature-specific: `features/[feature]/store/` - -### Platform-Specific Code - -Use file extensions for platform-specific implementations: -- `file.ts`: Default (mobile) -- `file.web.ts`: Web platform -- `file.ios.ts`: iOS only -- `file.android.ts`: Android only - -Metro bundler automatically resolves based on platform. - -### Error Handling - -1. Use feature-specific error types -2. Provide user-friendly messages -3. Include retry mechanisms where appropriate -4. Log errors to console for debugging -5. Consider Sentry integration for production - -## Build and Deployment - -**EAS Build Profiles:** -- `development`: Dev client with debugging -- `preview`: Internal distribution (TestFlight/Google Play Internal) -- `simulator`: iOS simulator build -- `production`: Auto-increment version, store-ready - -**Environment Selection:** -EAS profiles automatically load correct environment via `EXPO_PUBLIC_USE_ENV_FILE` in `eas.json`. - -**Version Management:** -- iOS: `buildNumber` in `app.json` -- Android: `versionCode` in `app.json` -- Production profile auto-increments both - -## Important Files - -- `app.json`: Expo configuration, plugins, permissions -- `eas.json`: EAS Build configuration -- `package.json`: Dependencies and scripts -- `tailwind.config.js`: Theme colors and styling -- `eslint.config.js`: Linting rules -- `babel.config.js`: Babel configuration -- `metro.config.js`: Metro bundler configuration (if present) -- `types/supabase.ts`: Auto-generated Supabase types - -## Database Schema - -The app uses Supabase with the following key tables: -- `memos`: Audio recordings and transcriptions -- `memories`: AI-generated insights from memos -- `blueprints`: AI analysis templates -- `prompts`: AI task templates -- `blueprint_prompts`: Many-to-many join table -- `categories`: Organization categories -- `tags`: User-defined tags -- `memo_tags`: Many-to-many join table -- `spaces`: Collaborative workspaces -- `space_members`: User-space relationships -- `profiles`: User profiles and settings - -All tables use RLS policies based on JWT claims. - -## Auto-Delete Audio Files (30-Day Retention) - -When users enable `autoDeleteAudiosAfter30Days` in their settings, audio files older than 30 days are automatically deleted while preserving memo records (transcripts, metadata). - -**Setting Location:** `app_settings.memoro.autoDeleteAudiosAfter30Days` (default: `false`) - -**Two Cleanup Mechanisms:** - -1. **Cloud Storage Cleanup** (memoro-service): - - Daily cron job at 3 AM UTC via Google Cloud Scheduler - - Queries `storage.objects` table for files older than 30 days - - Deletes from Supabase Storage bucket `user-uploads` - - Updates memo `source` field: `{ audio_path: null, audio_deleted: true, audio_deleted_at: timestamp }` - -2. **Local Device Cleanup** (mobile app): - - Runs on app launch after successful authentication - - Throttled to once per 24 hours - - Uses `fileStorageService.cleanupOldFiles()` with 30-day retention - - Implementation: `features/storage/services/localAudioCleanup.ts` - -**Key Files:** -- `memoro-service/src/cleanup/` - Cloud cleanup service -- `mana-middleware/src/modules/users/services/user-settings.service.ts` - User settings query -- `apps/mobile/features/storage/services/localAudioCleanup.ts` - Local device cleanup -- `apps/mobile/features/auth/contexts/AuthContext.tsx` - Cleanup trigger after auth - -## Known Issues - -1. **Android 16+ Recording**: Must be in foreground to start recording -2. **Zero-byte Recordings**: Occasional issue on some Android devices (retry mechanism in place) -3. **Token Refresh**: Email may not be in refreshed token (stored separately as workaround) -4. **Web Platform**: Limited functionality vs native (no push notifications, haptics, etc.) - -## Additional Documentation - -- `apps/mobile/README.md`: Full mobile app documentation -- `apps/web/README.md`: Web app documentation -- `features/auth/README.md`: Authentication system details -- `features/audioRecordingV2/README.md`: Audio recording implementation -- `docs/blueprints_and_prompts.md`: AI processing system -- `docs/SPACES.md`: Collaboration features -- `SVELTEKIT_MIGRATION_ANALYSIS.md`: Web app migration plan +The previous 459-line "Memoro repository overview" describing memoro as +a standalone monorepo with `mana-middleware` and a bespoke auth bridge +was deleted in the audit cleanup of 2026-04-09. It pre-dated the +integration into the Mana monorepo and described an architecture that +no longer exists. Pre-consolidation reference is in git history. diff --git a/apps/moodlit/CLAUDE.md b/apps/moodlit/CLAUDE.md index 35f5f69e8..65c1c99f9 100644 --- a/apps/moodlit/CLAUDE.md +++ b/apps/moodlit/CLAUDE.md @@ -1,37 +1,17 @@ -# Moodlit — Ambient Lighting & Mood App +# Moodlit — consolidated into the unified Mana app -## Architecture +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/moodlit/apps/server/` and `apps/moodlit/apps/web/` +directories have been removed. Active code now lives in: -Local-first for moods/sequences, Hono/Bun server for preset library. +- **Backend compute routes**: [`apps/api/src/modules/moodlit/routes.ts`](../api/src/modules/moodlit/routes.ts) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/moodlit/`](../mana/apps/web/src/lib/modules/moodlit/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/moodlit/`](../mana/apps/web/src/routes/(app)/moodlit/) +- **Landing page** (still standalone): [`apps/moodlit/apps/landing/`](apps/landing/) -``` -Browser → IndexedDB (Moods, Sequences) - ↕ sync - mana-sync → PostgreSQL -``` +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -## Project Structure - -``` -apps/moodlit/ -├── apps/ -│ ├── web/ # SvelteKit web app (local-first) -│ ├── server/ # Hono/Bun (preset moods API) -│ └── landing/ # Astro landing page -└── package.json -``` - -## Commands - -```bash -pnpm dev:moodlit:web # SvelteKit dev server -pnpm dev:moodlit:server # Hono/Bun server (port 3073) -pnpm dev:moodlit:landing # Landing page -``` - -## Local-First Collections - -| Collection | Fields | -|-----------|--------| -| `moods` | name, colors (hex array), animation, isDefault | -| `sequences` | name, moodIds, duration (seconds) | +The previous standalone "Moodlit" guide was deleted in the audit cleanup +of 2026-04-09 — it had been inaccurate since the consolidation. +Pre-consolidation reference is in git history. diff --git a/apps/moodlit/package.json b/apps/moodlit/package.json index 9cdfe4bb5..287e6a422 100644 --- a/apps/moodlit/package.json +++ b/apps/moodlit/package.json @@ -1,8 +1,5 @@ { "name": "@mana/moodlit", "version": "0.0.1", - "private": true, - "scripts": { - "dev": "turbo run dev" - } + "private": true } diff --git a/apps/news/CLAUDE.md b/apps/news/CLAUDE.md index bf837a337..fa20f71a2 100644 --- a/apps/news/CLAUDE.md +++ b/apps/news/CLAUDE.md @@ -1,67 +1,17 @@ -# News Hub — AI News Reader & Personal Library +# News Hub — consolidated into the unified Mana app -## Architecture +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/news/apps/server/` and `apps/news/apps/web/` directories +have been removed. Active code now lives in: -Local-first for saved articles, Hono/Bun server for content extraction and AI feed. +- **Backend compute routes**: [`apps/api/src/modules/news/routes.ts`](../api/src/modules/news/routes.ts) (Mozilla Readability extraction, AI feed) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/news/`](../mana/apps/web/src/lib/modules/news/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/news/`](../mana/apps/web/src/routes/(app)/news/) +- **Landing page** (still standalone): [`apps/news/apps/landing/`](apps/landing/) -``` -Browser → IndexedDB (Saved Articles, Categories) - ↕ sync - mana-sync → PostgreSQL +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -Browser → Hono Server → Content Extraction (Mozilla Readability) - → AI Feed (from sync_changes) -``` - -## Project Structure - -``` -apps/news/ -├── apps/ -│ ├── web/ # SvelteKit web app (local-first) -│ ├── server/ # Hono/Bun (extraction, feed API) -│ └── landing/ # Astro marketing page -└── package.json -``` - -## Tech Stack - -| Layer | Technology | -|-------|-----------| -| **Web** | SvelteKit 2, Svelte 5 (runes), Tailwind CSS 4 | -| **Server** | Hono + Bun, Mozilla Readability, JSDOM | -| **Data** | Local-first (Dexie.js + mana-sync) | -| **Auth** | mana-auth (Better Auth + EdDSA JWT) | - -## Commands - -```bash -pnpm dev:news:web # SvelteKit dev server -pnpm dev:news:server # Hono/Bun server (port 3071) -pnpm dev:news:local # Web + Sync + Server (no auth) -pnpm dev:news:full # Everything incl. auth -``` - -## Hono Server Routes - -| Route | Auth | Description | -|-------|------|-------------| -| `GET /health` | No | Health check | -| `GET /api/v1/feed` | No | AI article feed (type, categoryId, limit, offset) | -| `GET /api/v1/feed/:id` | No | Single article | -| `POST /api/v1/extract/preview` | No | Preview URL content extraction | -| `POST /api/v1/extract/save` | JWT | Extract + return article data | - -## Local-First Collections - -| Collection | Purpose | -|-----------|---------| -| `articles` | Saved articles (user_saved) + AI feed cache | -| `categories` | Article categories | - -## Key Patterns - -- **Content Extraction**: Mozilla Readability + JSDOM for robust HTML parsing -- **Saved Articles**: Local-first via IndexedDB, sync to server -- **AI Feed**: Loaded from Hono server, not local-first (server-generated) -- **Auth**: Guest mode allowed, sync starts on login +The previous standalone "News Hub" guide was deleted in the audit cleanup +of 2026-04-09 — it had been inaccurate since the consolidation. +Pre-consolidation reference is in git history. diff --git a/apps/news/package.json b/apps/news/package.json index 9ecc86c4c..5a1d802d8 100644 --- a/apps/news/package.json +++ b/apps/news/package.json @@ -1,8 +1,5 @@ { "name": "@mana/news", "version": "0.0.1", - "private": true, - "scripts": { - "dev": "turbo run dev" - } + "private": true } diff --git a/apps/nutriphi/CLAUDE.md b/apps/nutriphi/CLAUDE.md index 89ea20f97..588d8b502 100644 --- a/apps/nutriphi/CLAUDE.md +++ b/apps/nutriphi/CLAUDE.md @@ -1,368 +1,17 @@ -# NutriPhi Project Guide +# NutriPhi — consolidated into the unified Mana app -## Overview +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/nutriphi/apps/backend/` and `apps/nutriphi/apps/web/` +directories have been removed. Active code now lives in: -**NutriPhi** is an AI-powered nutrition tracking app that allows users to photograph their meals and receive instant nutritional analysis. It uses Google Gemini for image analysis and provides personalized recommendations. +- **Backend compute routes**: [`apps/api/src/modules/nutriphi/routes.ts`](../api/src/modules/nutriphi/routes.ts) (Gemini meal-photo analysis + text analysis + recommendations) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/nutriphi/`](../mana/apps/web/src/lib/modules/nutriphi/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/nutriphi/`](../mana/apps/web/src/routes/(app)/nutriphi/) +- **Landing page** (still standalone): [`apps/nutriphi/apps/landing/`](apps/landing/) -| App | Port | URL | -|-----|------|-----| -| Backend | 3023 | http://localhost:3023 | -| Web App | 5180 | http://localhost:5180 | -| Landing Page | 4323 | http://localhost:4323 | +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -## Project Structure - -``` -apps/nutriphi/ -├── apps/ -│ ├── backend/ # Hono/Bun compute server (@nutriphi/server) -│ │ └── src/ -│ │ ├── main.ts -│ │ ├── app.module.ts -│ │ ├── db/ # Drizzle schemas -│ │ │ ├── schema/index.ts -│ │ │ └── db.ts -│ │ ├── meal/ # Meal CRUD -│ │ ├── goals/ # User goals -│ │ ├── favorites/ # Favorite meals -│ │ ├── analysis/ # Gemini AI integration -│ │ ├── stats/ # Daily/weekly statistics -│ │ ├── recommendations/ # AI hints & coaching -│ │ └── health/ -│ │ -│ ├── web/ # SvelteKit web application (@nutriphi/web) -│ │ └── src/ -│ │ ├── lib/ -│ │ │ ├── api/client.ts -│ │ │ ├── stores/ -│ │ │ │ ├── auth.svelte.ts -│ │ │ │ └── meals.svelte.ts -│ │ │ └── components/ -│ │ │ ├── Header.svelte -│ │ │ ├── DailySummary.svelte -│ │ │ ├── MealList.svelte -│ │ │ ├── AddMealButton.svelte -│ │ │ └── ProgressRing.svelte -│ │ └── routes/ -│ │ ├── +layout.svelte -│ │ ├── +page.svelte # Dashboard -│ │ ├── login/+page.svelte -│ │ └── add/+page.svelte # Photo/text input -│ │ -│ └── landing/ # Astro marketing page (@nutriphi/landing) -│ -├── packages/ -│ └── shared/ # Shared types, utils, constants (@nutriphi/shared) -│ └── src/ -│ ├── types/index.ts -│ ├── constants/index.ts -│ └── utils/index.ts -│ -├── package.json -└── CLAUDE.md -``` - -## Commands - -### Root Level (from monorepo root) - -```bash -# Start all apps -pnpm nutriphi:dev - -# Individual apps -pnpm dev:nutriphi:server # Backend (port 3015) -pnpm dev:nutriphi:web # Web app (port 5180) -pnpm dev:nutriphi:landing # Landing page (port 4323) -pnpm dev:nutriphi:app # Web + backend together - -# Database -pnpm nutriphi:db:push # Push schema to database -pnpm nutriphi:db:studio # Open Drizzle Studio -``` - -### Backend (apps/nutriphi/apps/backend) - -```bash -pnpm dev # Start with hot reload -pnpm build # Build for production -pnpm db:push # Push schema to database -pnpm db:studio # Open Drizzle Studio -``` - -### Web App (apps/nutriphi/apps/web) - -```bash -pnpm dev # Start dev server (port 5180) -pnpm build # Build for production -``` - -### Landing Page (apps/nutriphi/apps/landing) - -```bash -pnpm dev # Start dev server (port 4323) -pnpm build # Build for production -``` - -## Technology Stack - -| Layer | Technology | -|-------|------------| -| **Backend** | Hono + Bun, Drizzle ORM, PostgreSQL | -| **AI** | Google Gemini 2.5 Flash | -| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 | -| **Landing** | Astro 5.x, Tailwind CSS | -| **Auth** | Mana Auth (JWT) | - -## Architecture - -### Core Features - -1. **Photo Analysis** - Take a photo, Gemini identifies foods and calculates nutrition -2. **Text Input** - Alternative: describe your meal in text -3. **Full Nutrition** - Calories, macros, vitamins, minerals -4. **Daily Goals** - Set and track calorie/macro targets -5. **AI Coaching** - Personalized tips based on eating patterns -6. **Favorites** - Save frequently eaten meals -7. **Privacy-First** - Photos are never stored, only analysis results - -### API Endpoints - -#### Health -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/health` | GET | Health check | - -#### Analysis -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/analysis/photo` | POST | Analyze photo (Base64) | -| `/api/v1/analysis/text` | POST | Analyze text description | - -#### Meals -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/meals` | GET | List meals (query by date) | -| `/api/v1/meals` | POST | Create meal | -| `/api/v1/meals/:id` | GET | Get meal details | -| `/api/v1/meals/:id` | PATCH | Update meal | -| `/api/v1/meals/:id` | DELETE | Delete meal | - -#### Goals -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/goals` | GET | Get user goals | -| `/api/v1/goals` | POST | Set/update goals | -| `/api/v1/goals` | DELETE | Delete goals | - -#### Favorites -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/favorites` | GET | List favorites | -| `/api/v1/favorites` | POST | Create favorite | -| `/api/v1/favorites/:id/use` | POST | Increment usage count | -| `/api/v1/favorites/:id` | DELETE | Delete favorite | - -#### Stats -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/stats/daily` | GET | Daily summary | -| `/api/v1/stats/weekly` | GET | Weekly stats | - -#### Recommendations -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/v1/recommendations` | GET | List active recommendations | -| `/api/v1/recommendations/:id/dismiss` | POST | Dismiss recommendation | - -### Database Schema - -#### user_goals -| Column | Type | Description | -|--------|------|-------------| -| id | UUID | Primary key | -| user_id | UUID | User ID | -| daily_calories | INTEGER | Daily calorie target | -| daily_protein | INTEGER | Protein target (g) | -| daily_carbs | INTEGER | Carbs target (g) | -| daily_fat | INTEGER | Fat target (g) | - -#### meals -| Column | Type | Description | -|--------|------|-------------| -| id | UUID | Primary key | -| user_id | UUID | User ID | -| date | TIMESTAMP | Meal date/time | -| meal_type | VARCHAR | breakfast/lunch/dinner/snack | -| input_type | VARCHAR | photo/text | -| description | TEXT | AI-generated description | -| confidence | REAL | AI confidence (0-1) | - -#### meal_nutrition -| Column | Type | Description | -|--------|------|-------------| -| id | UUID | Primary key | -| meal_id | UUID | FK to meals | -| calories | REAL | Calories (kcal) | -| protein | REAL | Protein (g) | -| carbohydrates | REAL | Carbs (g) | -| fat | REAL | Fat (g) | -| fiber | REAL | Fiber (g) | -| sugar | REAL | Sugar (g) | -| vitamin_* | REAL | Various vitamins | -| calcium, iron, etc. | REAL | Minerals | - -#### favorite_meals -| Column | Type | Description | -|--------|------|-------------| -| id | UUID | Primary key | -| user_id | UUID | User ID | -| name | VARCHAR | Favorite name | -| nutrition | JSONB | Cached nutrition data | -| usage_count | INTEGER | Times used | - -#### recommendations -| Column | Type | Description | -|--------|------|-------------| -| id | UUID | Primary key | -| user_id | UUID | User ID | -| type | VARCHAR | hint/coaching | -| message | TEXT | Recommendation text | -| dismissed | BOOLEAN | User dismissed | - -## Environment Variables - -### Backend (.env) - -```env -NODE_ENV=development -PORT=3023 -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/nutriphi -MANA_AUTH_URL=http://localhost:3001 -CORS_ORIGINS=http://localhost:5180,http://localhost:4323 - -# Gemini AI (uses gemini-2.5-flash model) -GEMINI_API_KEY=your-gemini-api-key -``` - -> **Note:** Get your API key from https://aistudio.google.com/apikey - -### Web (.env) - -```env -PUBLIC_BACKEND_URL=http://localhost:3023 -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -## Shared Package (@nutriphi/shared) - -**Types:** -- `UserGoals` - Daily nutrition targets -- `Meal`, `MealNutrition` - Meal data -- `FavoriteMeal` - Saved favorites -- `DailySummary`, `WeeklyStats` - Statistics -- `AIAnalysisResult` - Gemini response format -- `Recommendation` - AI hints/coaching - -**Constants:** -- `DEFAULT_DAILY_VALUES` - Reference daily values -- `MEAL_TYPE_LABELS` - Localized meal names -- `NUTRIENT_INFO` - Labels, units, colors -- `CREDIT_COSTS` - Credit pricing - -**Utils:** -- `calculateProgress()` - Progress towards goals -- `sumNutrition()` - Sum multiple meals -- `formatNutrient()` - Display formatting -- `detectDeficiencies()` - Find nutrient gaps -- `suggestMealType()` - Based on time of day - -## Quick Start - -### 1. Create Database - -```bash -# PostgreSQL must be running -docker compose -f docker-compose.dev.yml up -d postgres - -# Create database -PGPASSWORD=devpassword psql -h localhost -U mana -d postgres -c "CREATE DATABASE nutriphi;" - -# Push schema -pnpm nutriphi:db:push -``` - -### 2. Set Gemini API Key - -Add to `.env.development`: -```env -GEMINI_API_KEY=your-gemini-api-key -``` - -### 3. Start Apps - -```bash -# Backend + Web together -pnpm dev:nutriphi:app - -# Or individually: -pnpm dev:nutriphi:server # Terminal 1 -pnpm dev:nutriphi:web # Terminal 2 -pnpm dev:nutriphi:landing # Terminal 3 -``` - -### 4. Open URLs - -- Web App: http://localhost:5180 -- Landing: http://localhost:4323 -- API Health: http://localhost:3023/api/v1/health - -## Testing API - -```bash -# Health Check -curl http://localhost:3023/api/v1/health - -# Login (get 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') - -# Analyze text -curl -X POST http://localhost:3023/api/v1/analysis/text \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"description": "Spaghetti Bolognese mit Parmesan"}' - -# Get daily summary -curl http://localhost:3023/api/v1/stats/daily \ - -H "Authorization: Bearer $TOKEN" -``` - -## Credit System - -| Action | Credits | -|--------|---------| -| Photo Analysis | 5 | -| Text Analysis | 2 | -| AI Coaching | 10 | - -## Privacy Features - -- Photos are NEVER stored on servers -- Photos are sent directly to Gemini, analyzed, then discarded -- Only nutrition results are saved -- Full data export available (GDPR) -- One-click account deletion - -## Color Theme - -| Color | Value | Usage | -|-------|-------|-------| -| Primary | #22C55E | Main actions, progress | -| Secondary | #F97316 | Accent, warnings | -| Accent | #14B8A6 | Highlights | -| Calories | #F59E0B | Calorie displays | -| Protein | #EF4444 | Protein displays | -| Carbs | #3B82F6 | Carb displays | -| Fat | #8B5CF6 | Fat displays | +The previous standalone "NutriPhi Project Guide" was deleted in the +audit cleanup of 2026-04-09 — it had been inaccurate since the +consolidation. Pre-consolidation reference is in git history. diff --git a/apps/picture/CLAUDE.md b/apps/picture/CLAUDE.md index 031c70493..7cba4e6e9 100644 --- a/apps/picture/CLAUDE.md +++ b/apps/picture/CLAUDE.md @@ -1,194 +1,20 @@ -# Picture App - CLAUDE.md +# Picture — consolidated into the unified Mana app -AI image generation app using Replicate API with freemium credit system. +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/picture/apps/backend/` and `apps/picture/apps/web/` +directories have been removed. Active code now lives in: -## Project Structure +- **Backend compute routes**: [`apps/api/src/modules/picture/routes.ts`](../api/src/modules/picture/routes.ts) (Replicate / image-gen orchestration, server-side credit deduction) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/picture/`](../mana/apps/web/src/lib/modules/picture/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/picture/`](../mana/apps/web/src/routes/(app)/picture/) +- **Landing page** (still standalone): [`apps/picture/apps/landing/`](apps/landing/) +- **Mobile app**: [`apps/picture/apps/mobile/`](apps/mobile/) -``` -apps/picture/ -├── apps/ -│ ├── backend/ # Hono/Bun server (port 3006) -│ ├── mobile/ # Expo React Native app -│ ├── web/ # SvelteKit web app -│ └── landing/ # Astro marketing page -└── packages/ # Shared code -``` +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -## Quick Start - -```bash -# From monorepo root -pnpm dev:picture:full # Start backend + web + auto DB setup - -# Individual apps -pnpm --filter @picture/server dev # Backend only (port 3006) -pnpm --filter @picture/web dev # Web only -pnpm --filter @picture/mobile dev # Mobile only -``` - -## Backend Architecture - -### Key Services - -| Service | Purpose | -|---------|---------| -| `GenerateService` | AI image generation with freemium/credit logic | -| `ReplicateService` | Replicate API integration | -| `StorageService` | MinIO/S3 storage via `@mana/shared-storage` | -| `CreditClientService` | Credit system via `@mana-core/nestjs-integration` | - -### Freemium Model - -- **Free tier**: 3 free generations per user -- **Paid tier**: 10 credits per generation -- **Enforcement**: Only in staging (`NODE_ENV=staging`) -- **Development**: Fail-open (no credit enforcement) - -### Environment Variables - -| Variable | Description | Required | -|----------|-------------|----------| -| `REPLICATE_API_TOKEN` | Replicate API key | Yes | -| `DATABASE_URL` | PostgreSQL connection | Yes | -| `S3_ENDPOINT` | MinIO/S3 endpoint | Yes | -| `MANA_AUTH_URL` | Auth service URL | Yes | -| `MANA_SERVICE_KEY` | Service key for credits | Staging only | -| `APP_ID` | App identifier | Yes | - ---- - -## TODO List - -### Testing Required - -- [ ] **Test freemium flow with new user** - - Create new user ID and verify 3 free generations work - - Verify `freeGenerationsRemaining` decrements correctly (3 → 2 → 1 → 0) - - Verify 4th generation still works in development (fail-open) - -- [ ] **Test staging credit enforcement** - - Set `NODE_ENV=staging` and test credit check - - Verify HTTP 402 returned when credits insufficient - - Test with valid `MANA_SERVICE_KEY` - -- [ ] **Test async generation (webhook mode)** - - Test generation without `waitForResult: true` - - Verify webhook receives completion callback - - Verify credits consumed on webhook success - -- [ ] **Test error handling** - - Test with invalid model ID - - Test with invalid Replicate API token - - Test storage upload failures - -- [ ] **Integration tests** - - Write Jest tests for `GenerateService` - - Mock `CreditClientService` calls - - Test all generation paths (free/paid, sync/async) - -### Features to Implement - -- [ ] **Add credit balance endpoint** - - GET `/api/v1/credits/balance` - Return user's credit balance - - Use `CreditClientService.getBalance()` - -- [ ] **Add generation history endpoint** - - GET `/api/v1/generate/history` - User's generation history - - Include credits used per generation - -- [ ] **Improve error messages** - - Add proper error codes for credit failures - - Return helpful messages for insufficient credits - -- [ ] **Rate limiting** - - Add rate limits for generation endpoints - - Prevent abuse of free tier - -### Web App Tasks - -- [ ] **Show free generations remaining** - - Display counter in UI - - Show warning when approaching limit - -- [ ] **Credit purchase flow** - - Integrate with mana-core credit purchase - - Show credit balance in header - -- [ ] **Generation queue UI** - - Show pending generations - - Poll for status updates - -### Mobile App Tasks - -- [ ] **Implement generation screen** - - Model selection - - Prompt input with suggestions - - Generation progress indicator - -- [ ] **Gallery view** - - Grid view of user's generated images - - Favorites functionality - -### DevOps Tasks - -- [ ] **Staging deployment** - - Deploy backend to staging server - - Configure `MANA_SERVICE_KEY` in staging - - Test credit system end-to-end - -- [ ] **Monitoring** - - Add logging for credit operations - - Track generation success/failure rates - - Monitor Replicate API usage - ---- - -## API Endpoints - -### Generate - -```bash -# Generate image (sync) -POST /api/v1/generate -{ - "prompt": "A beautiful sunset", - "modelId": "uuid", - "waitForResult": true -} - -# Check status -GET /api/v1/generate/:id/status - -# Cancel generation -DELETE /api/v1/generate/:id - -# Webhook (internal) -POST /api/v1/generate/webhook -``` - -### Models - -```bash -GET /api/v1/models # List all models -GET /api/v1/models/:id # Get model details -``` - -### Images - -```bash -GET /api/v1/images # List user's images -GET /api/v1/images/:id # Get image details -DELETE /api/v1/images/:id # Delete image -``` - ---- - -## Recent Changes - -### 2025-12-10: Credit System Integration - -- Added `@mana-core/nestjs-integration` for credit system -- Implemented freemium model (3 free, then 10 credits) -- Credit enforcement only in staging environment -- Updated `GenerateService` with `checkGenerationAccess()` -- Response includes `freeGenerationsRemaining` count +The previous standalone "Picture App" guide describing a per-product +backend with `GenerateService`, `ReplicateService` etc. was deleted in +the audit cleanup of 2026-04-09 — it had been inaccurate since the +consolidation. The freemium credit logic now lives in `apps/api` and +talks to `mana-credits`. Pre-consolidation reference is in git history. diff --git a/apps/planta/CLAUDE.md b/apps/planta/CLAUDE.md index 49cd3fdcd..7d5be689a 100644 --- a/apps/planta/CLAUDE.md +++ b/apps/planta/CLAUDE.md @@ -1,167 +1,22 @@ -# Planta Project Guide +# Planta — consolidated into the unified Mana app -## Project Structure +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/planta/apps/backend/` and `apps/planta/apps/web/` +directories have been removed. Active code now lives in: -``` -apps/planta/ -├── apps/ -│ ├── backend/ # Hono/Bun compute server (@planta/server) -│ └── web/ # SvelteKit web application (@planta/web) -├── packages/ -│ └── shared/ # Shared types, utils (@planta/shared) -└── package.json -``` +- **Backend compute routes**: [`apps/api/src/modules/planta/routes.ts`](../api/src/modules/planta/routes.ts) (Gemini Vision plant analysis, S3 upload) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/planta/`](../mana/apps/web/src/lib/modules/planta/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/planta/`](../mana/apps/web/src/routes/(app)/planta/) -## Commands +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -### Root Level (from monorepo root) +The previous standalone "Planta Project Guide" describing a per-product +backend with its own database, schema, and watering scheduler was deleted +in the audit cleanup of 2026-04-09 — it had been inaccurate since the +consolidation. Pre-consolidation reference is in git history. -```bash -pnpm planta:dev # Run all planta apps -pnpm dev:planta:web # Start web app -pnpm dev:planta:server # Start backend server -pnpm dev:planta:app # Start web + backend together -pnpm dev:planta:full # Start auth + backend + web with DB setup -``` - -### Backend (apps/planta/apps/backend) - -```bash -pnpm dev # Start with hot reload -pnpm build # Build for production -pnpm start:prod # Start production server -pnpm db:push # Push schema to database -pnpm db:studio # Open Drizzle Studio -``` - -### Web App (apps/planta/apps/web) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -pnpm preview # Preview production build -``` - -## Technology Stack - -- **Web**: SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS -- **Backend**: Hono + Bun, Drizzle ORM, PostgreSQL -- **AI**: Google Gemini Vision for plant analysis -- **Storage**: MinIO (S3-compatible) -- **Auth**: Mana Auth (JWT) -- **Types**: TypeScript 5.x - -## Architecture - -### Core Flow - -1. User uploads plant photo -2. Photo stored in S3/MinIO -3. Gemini Vision analyzes the image -4. Plant profile created with care recommendations -5. Watering schedule tracked - -### Backend API Endpoints - -| Endpoint | Method | Description | -| ------------------------------- | ------ | ------------------------ | -| `/api/health` | GET | Health check | -| `/api/plants` | GET | Get user's plants | -| `/api/plants` | POST | Create new plant | -| `/api/plants/:id` | GET | Get plant details | -| `/api/plants/:id` | PUT | Update plant | -| `/api/plants/:id` | DELETE | Delete plant | -| `/api/photos/upload` | POST | Upload plant photo | -| `/api/photos/:id` | DELETE | Delete photo | -| `/api/analysis/identify` | POST | Analyze photo with AI | -| `/api/analysis/:photoId` | GET | Get analysis results | -| `/api/watering/upcoming` | GET | Plants needing water | -| `/api/watering/:plantId/water` | POST | Log watering event | - -### Database Schema - -**plants** - User's plants - -- `id` (UUID) - Primary key -- `user_id` (TEXT) - User reference -- `name` (TEXT) - Plant nickname -- `scientific_name` (TEXT) - From AI analysis -- `common_name` (TEXT) - Common name -- `light_requirements` (TEXT) - low/medium/bright/direct -- `watering_frequency_days` (INT) - Days between watering -- `humidity` (TEXT) - low/medium/high -- `care_notes` (TEXT) - Care tips -- `health_status` (TEXT) - healthy/needs_attention/sick - -**plant_photos** - Plant photos - -- `id` (UUID) - Primary key -- `plant_id` (UUID) - FK to plants -- `storage_path` (TEXT) - S3 path -- `public_url` (TEXT) - Public URL -- `is_primary` (BOOLEAN) - Primary photo flag -- `is_analyzed` (BOOLEAN) - Analysis flag - -**plant_analyses** - AI analysis results - -- `id` (UUID) - Primary key -- `photo_id` (UUID) - FK to plant_photos -- `identified_species` (TEXT) - Detected species -- `confidence` (INT) - 0-100 confidence -- `health_assessment` (TEXT) - Health status -- `watering_advice` (TEXT) - Watering recommendation -- `general_tips` (JSONB) - Care tips array - -**watering_schedules** - Watering tracking - -- `id` (UUID) - Primary key -- `plant_id` (UUID) - FK to plants -- `frequency_days` (INT) - Interval -- `last_watered_at` (TIMESTAMP) - Last watering -- `next_watering_at` (TIMESTAMP) - Next watering - -### Environment Variables - -#### Backend (.env) - -``` -NODE_ENV=development -PORT=3022 -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/planta -MANA_AUTH_URL=http://localhost:3001 -GOOGLE_GEMINI_API_KEY=xxx -CORS_ORIGINS=http://localhost:5173,http://localhost:5191 -S3_ENDPOINT=http://localhost:9000 -S3_BUCKET=planta-storage -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -``` - -#### Web (.env) - -``` -PUBLIC_BACKEND_URL=http://localhost:3022 -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -## Shared Package - -### @planta/shared - -- Types: `Plant`, `PlantPhoto`, `PlantAnalysis`, `WateringSchedule` -- Utils: Date helpers, care level formatters - -## Code Style Guidelines - -- **TypeScript**: Strict typing with interfaces -- **Web**: Svelte 5 runes mode (`$state`, `$derived`, `$effect`) -- **Styling**: Tailwind CSS -- **Formatting**: Prettier with project config - -## Important Notes - -1. **Authentication**: Uses Mana Auth (JWT in Authorization header) -2. **Database**: PostgreSQL with Drizzle ORM -3. **Port**: Backend runs on port 3022 by default -4. **Storage**: Photos stored in MinIO (S3-compatible) -5. **AI**: Google Gemini Vision for plant identification +> **Note:** The orphaned `apps/planta/packages/shared/` package and the +> related sub-script entries in `apps/planta/package.json` referencing +> non-existent `@planta/server` / `@planta/web` filters are tracked as +> dead code in `docs/REFACTORING_AUDIT_2026_04.md` items #18/#29. diff --git a/apps/planta/package.json b/apps/planta/package.json index 0f7731adb..ad0d90911 100644 --- a/apps/planta/package.json +++ b/apps/planta/package.json @@ -3,14 +3,6 @@ "version": "0.1.0", "private": true, "description": "Planta - Plant Documentation & Care App", - "scripts": { - "dev": "turbo run dev", - "dev:server": "pnpm --filter @planta/server dev", - "dev:web": "pnpm --filter @planta/web dev", - "db:push": "pnpm --filter @planta/server db:push", - "db:studio": "pnpm --filter @planta/server db:studio", - "db:seed": "pnpm --filter @planta/server db:seed" - }, "devDependencies": { "typescript": "^5.9.3" }, diff --git a/apps/presi/CLAUDE.md b/apps/presi/CLAUDE.md index 738ea482e..04c9ce363 100644 --- a/apps/presi/CLAUDE.md +++ b/apps/presi/CLAUDE.md @@ -1,213 +1,18 @@ -# Presi Project Guide +# Presi — consolidated into the unified Mana app -## Project Structure +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/presi/apps/backend/` (NestJS) and `apps/presi/apps/web/` +directories have been removed. Active code now lives in: -``` -apps/presi/ -├── apps/ -│ ├── backend/ # NestJS API server (@presi/backend) -│ ├── web/ # SvelteKit web application (@presi/web) -│ └── landing/ # Astro marketing landing page (@presi/landing) -├── packages/ -│ └── shared/ # Shared types and utils (@presi/shared) -└── package.json -``` +- **Backend compute routes**: [`apps/api/src/modules/presi/routes.ts`](../api/src/modules/presi/routes.ts) (public share-link lookups, share management) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/presi/`](../mana/apps/web/src/lib/modules/presi/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/presi/`](../mana/apps/web/src/routes/(app)/presi/) +- **Landing page** (still standalone): [`apps/presi/apps/landing/`](apps/landing/) -## Commands +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -### Root Level (from monorepo root) - -```bash -pnpm presi:dev # Run all presi apps -pnpm dev:presi:web # Start web app (port 5178) -pnpm dev:presi:backend # Start backend server -pnpm dev:presi:app # Start web + backend together -pnpm presi:db:push # Push schema to database -pnpm presi:db:studio # Open Drizzle Studio -pnpm presi:db:seed # Seed database with sample data -``` - -### Web App (apps/presi/apps/web) - -```bash -pnpm dev # Start dev server (port 5178) -pnpm build # Build for production -pnpm preview # Preview production build -pnpm check # Run svelte-check -``` - -### Backend (apps/presi/apps/backend) - -```bash -pnpm dev # Start with hot reload -pnpm build # Build for production -pnpm start:prod # Start production server -pnpm db:push # Push schema to database -pnpm db:studio # Open Drizzle Studio -pnpm db:seed # Seed database -``` - -## Technology Stack - -- **Web**: SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS -- **Backend**: NestJS 10, Drizzle ORM, PostgreSQL -- **Types**: TypeScript 5.x - -## Architecture - -### Core Features - -- Create and manage presentation decks -- Add and edit slides with various content types -- Apply themes to presentations -- Share decks via share codes -- Present slides in full-screen mode - -### Backend API Endpoints - -| Endpoint | Method | Auth | Description | -| --------------------------- | ------ | ---- | ------------------------ | -| `/api/health` | GET | No | Health check | -| `/api/decks` | GET | Yes | Get user's decks | -| `/api/decks` | POST | Yes | Create new deck | -| `/api/decks/:id` | GET | Yes | Get deck details | -| `/api/decks/:id` | PUT | Yes | Update deck | -| `/api/decks/:id` | DELETE | Yes | Delete deck | -| `/api/decks/:id/slides` | GET | Yes | Get slides for deck | -| `/api/decks/:id/slides` | POST | Yes | Add slide to deck | -| `/api/slides/:id` | PUT | Yes | Update slide | -| `/api/slides/:id` | DELETE | Yes | Delete slide | -| `/api/slides/reorder` | PUT | Yes | Reorder slides | -| `/api/share/:code` | GET | No | Get shared deck (public) | -| `/api/share/deck/:id` | POST | Yes | Create share link | -| `/api/share/deck/:id/links` | GET | Yes | Get share links for deck | -| `/api/share/:shareId` | DELETE | Yes | Delete share link | - -### Data Models - -**Deck** - Presentation deck - -- `id` (string) - Unique identifier -- `userId` (string) - Owner user ID -- `title` (string) - Deck title -- `description` (string?) - Optional description -- `themeId` (string?) - Theme reference -- `isPublic` (boolean) - Visibility flag -- `createdAt` / `updatedAt` (timestamps) - -**Slide** - Individual slide in a deck - -- `id` (string) - Unique identifier -- `deckId` (string) - Parent deck reference -- `order` (number) - Position in deck -- `content` (SlideContent) - Slide content -- `createdAt` (timestamp) - -**SlideContent** - Content structure - -- `type`: 'title' | 'content' | 'image' | 'split' -- `title`, `subtitle`, `body`, `imageUrl`, `bulletPoints` - -**Theme** - Visual theme - -- `id`, `name`, `colors`, `fonts`, `isDefault` - -**SharedDeck** - Share link for deck - -- `id` (string) - Unique identifier -- `deckId` (string) - Reference to deck -- `shareCode` (string) - Unique share code (12 chars) -- `expiresAt` (timestamp?) - Optional expiration -- `createdAt` (timestamp) - -### Environment Variables - -#### Backend (.env) - -``` -NODE_ENV=development -PORT=3008 -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/presi -MANA_AUTH_URL=http://localhost:3001 -CORS_ORIGINS=http://localhost:5173,http://localhost:8081 -``` - -#### Web (.env) - -``` -PUBLIC_BACKEND_URL=http://localhost:3008 -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -## Shared Package - -### @presi/shared - -Located at `packages/shared/` - -**Types:** - -- `Deck`, `Slide`, `SlideContent` -- `Theme`, `ThemeColors`, `ThemeFonts` -- `SharedDeck` (for sharing feature) - -**DTOs:** - -- `CreateDeckDto`, `UpdateDeckDto` -- `CreateSlideDto`, `UpdateSlideDto` -- `ReorderSlidesDto` - -## Code Style Guidelines - -- **TypeScript**: Strict typing with interfaces -- **Web**: Svelte 5 runes mode (`$state`, `$derived`, `$effect`) -- **Backend**: NestJS modules with controllers and services -- **Styling**: Tailwind CSS -- **Formatting**: Prettier with project config - -## Web App Features - -The SvelteKit web app provides the main user interface: - -- **Authentication**: Login/Register/Forgot Password with Mana Auth -- **Deck Management**: Create, edit, delete presentation decks -- **Slide Editor**: Create slides with title, body, bullet points, images -- **Presentation Mode**: Fullscreen presentation with keyboard navigation - - Arrow keys / A/D for navigation - - F for fullscreen toggle - - ESC to exit - - Timer with start/pause - - Speaker notes toggle -- **Sharing**: Create share links for decks, public view without auth -- **Profile**: View user info and deck statistics -- **Settings**: Theme switching (light/dark/system), account info - -### Web App Structure - -``` -src/ -├── lib/ -│ ├── api/client.ts # API client with auth -│ └── stores/ -│ ├── auth.svelte.ts # Auth state (Svelte 5 runes) -│ └── decks.svelte.ts # Decks/slides state -├── routes/ -│ ├── +layout.svelte # App layout with header -│ ├── +page.svelte # Deck list (home) -│ ├── login/ # Login page -│ ├── register/ # Register page -│ ├── forgot-password/ # Password reset page -│ ├── deck/[id]/ # Deck editor with slides -│ ├── present/[id]/ # Presentation mode -│ ├── shared/[code]/ # Public shared deck view -│ ├── profile/ # User profile page -│ └── settings/ # Settings page -└── app.css # Global styles -``` - -## Important Notes - -1. **Authentication**: Uses Mana Auth (JWT in Authorization header) -2. **Database**: PostgreSQL with Drizzle ORM -3. **Ports**: Backend=3008, Web=5178 -4. **Landing**: Deployed on Cloudflare Pages +The previous "Presi Project Guide" referenced a NestJS backend that was +fully replaced by the consolidated Hono routes in apps/api during the +NestJS → Hono sweep. Deleted in the audit cleanup of 2026-04-09. +Pre-consolidation reference is in git history. diff --git a/apps/questions/package.json b/apps/questions/package.json index 71bffce78..52d6adb12 100644 --- a/apps/questions/package.json +++ b/apps/questions/package.json @@ -2,8 +2,5 @@ "name": "questions", "version": "0.1.0", "private": true, - "description": "Questions app - Collect questions and research answers", - "scripts": { - "dev": "turbo run dev" - } + "description": "Questions app - Collect questions and research answers" } diff --git a/apps/storage/CLAUDE.md b/apps/storage/CLAUDE.md index 1fcc71ce1..d03b3ed0b 100644 --- a/apps/storage/CLAUDE.md +++ b/apps/storage/CLAUDE.md @@ -1,294 +1,25 @@ -# Storage Project Guide +# Storage — consolidated into the unified Mana app -## Project Structure +This product was migrated into the unified Mana monorepo. The legacy +per-product `apps/storage/apps/backend/` and `apps/storage/apps/web/` +directories have been removed. Active code now lives in: -``` -apps/storage/ -├── apps/ -│ ├── backend/ # Hono/Bun compute server (@storage/server) - Port 3016 -│ ├── landing/ # Astro marketing landing page (@storage/landing) -│ └── web/ # SvelteKit web application (@storage/web) - Port 5185 -├── packages/ -│ └── shared/ # Shared types, utils, configs (@storage/shared) -└── package.json -``` +- **Backend compute routes**: [`apps/api/src/modules/storage/routes.ts`](../api/src/modules/storage/routes.ts) (S3/MinIO upload, presigned URLs, download) +- **Frontend module** (local-first): [`apps/mana/apps/web/src/lib/modules/storage/`](../mana/apps/web/src/lib/modules/storage/) +- **Web route**: [`apps/mana/apps/web/src/routes/(app)/storage/`](../mana/apps/web/src/routes/(app)/storage/) -## Commands +For monorepo-wide patterns (auth, sync, encryption, services), see the +[root `CLAUDE.md`](../../CLAUDE.md) and [`apps/mana/CLAUDE.md`](../mana/CLAUDE.md). -### Root Level (from monorepo root) +The previous "Storage Project Guide" describing a per-product NestJS-style +backend with `FilesController`, `FoldersController`, share/version logic, +etc. was deleted in the audit cleanup of 2026-04-09 — it had been +inaccurate since the consolidation. The current consolidated implementation +is much smaller (uses `mana-sync` for metadata CRUD). The audio-player +visualizer logic referenced in the old guide lives directly in the +storage module under `apps/mana/apps/web/src/lib/modules/storage/`. +Pre-consolidation reference is in git history. -```bash -pnpm storage:dev # Run all storage apps -pnpm dev:storage:web # Start web app -pnpm dev:storage:landing # Start landing page -pnpm dev:storage:backend # Start backend server -pnpm dev:storage:app # Start web + backend together -pnpm storage:db:push # Push schema to database -pnpm storage:db:studio # Open Drizzle Studio -pnpm storage:db:seed # Seed database -``` - -### Backend (apps/storage/apps/backend) - -```bash -pnpm dev # Start with hot reload -pnpm build # Build for production -pnpm start:prod # Start production server -pnpm db:push # Push schema to database -pnpm db:studio # Open Drizzle Studio -``` - -### Web App (apps/storage/apps/web) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -pnpm preview # Preview production build -``` - -### Landing Page (apps/storage/apps/landing) - -```bash -pnpm dev # Start dev server -pnpm build # Build for production -``` - -## Technology Stack - -- **Web**: SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS -- **Landing**: Astro 5.x, Tailwind CSS -- **Backend**: NestJS 11, Drizzle ORM, PostgreSQL -- **Storage**: S3-compatible (MinIO) -- **Types**: TypeScript 5.x - -## Architecture - -### Backend API Endpoints - -#### Files - -| Endpoint | Method | Description | -| --------------------------------- | ------ | -------------------------- | -| `/api/v1/health` | GET | Health check | -| `/api/v1/files` | GET | List files (with folderId) | -| `/api/v1/files/:id` | GET | Get file details | -| `/api/v1/files/upload` | POST | Upload file (multipart) | -| `/api/v1/files/:id/download` | GET | Download file | -| `/api/v1/files/:id` | PATCH | Update file (rename) | -| `/api/v1/files/:id/move` | PATCH | Move file to folder | -| `/api/v1/files/:id` | DELETE | Soft delete file | -| `/api/v1/files/:id/favorite` | POST | Toggle favorite | -| `/api/v1/files/:id/versions` | GET | List file versions | -| `/api/v1/files/:id/versions` | POST | Upload new version | -| `/api/v1/files/:id/tags` | POST | Update file tags | - -#### Folders - -| Endpoint | Method | Description | -| --------------------------------- | ------ | -------------------------- | -| `/api/v1/folders` | GET | List root folders | -| `/api/v1/folders/:id` | GET | Get folder with contents | -| `/api/v1/folders/:id/tree` | GET | Get folder tree (sidebar) | -| `/api/v1/folders` | POST | Create folder | -| `/api/v1/folders/:id` | PATCH | Update folder | -| `/api/v1/folders/:id/move` | PATCH | Move folder | -| `/api/v1/folders/:id` | DELETE | Soft delete folder | -| `/api/v1/folders/:id/favorite` | POST | Toggle favorite | - -#### Shares - -| Endpoint | Method | Description | -| --------------------------------- | ------ | -------------------------- | -| `/api/v1/shares` | GET | List user's shares | -| `/api/v1/shares` | POST | Create share link | -| `/api/v1/shares/:id` | PATCH | Update share settings | -| `/api/v1/shares/:id` | DELETE | Revoke share | -| `/api/v1/public/shares/:token` | GET | Access shared item (public)| -| `/api/v1/public/shares/:token/download` | GET | Download shared file | - -#### Tags - -| Endpoint | Method | Description | -| --------------------------------- | ------ | -------------------------- | -| `/api/v1/tags` | GET | List user's tags | -| `/api/v1/tags` | POST | Create tag | -| `/api/v1/tags/:id` | PATCH | Update tag | -| `/api/v1/tags/:id` | DELETE | Delete tag | - -#### Trash - -| Endpoint | Method | Description | -| --------------------------------- | ------ | -------------------------- | -| `/api/v1/trash` | GET | List trash items | -| `/api/v1/trash/:id/restore` | POST | Restore item | -| `/api/v1/trash/:id` | DELETE | Permanently delete | -| `/api/v1/trash` | DELETE | Empty trash | - -#### Search & Favorites - -| Endpoint | Method | Description | -| --------------------------------- | ------ | -------------------------- | -| `/api/v1/search?q=...` | GET | Search files & folders | -| `/api/v1/favorites` | GET | List favorites | - -### Database Schema - -**files** - File metadata - -- `id` (UUID) - Primary key -- `user_id` (VARCHAR) - User reference -- `name` (VARCHAR) - Display name -- `original_name` (VARCHAR) - Original filename -- `mime_type` (VARCHAR) - MIME type -- `size` (BIGINT) - File size in bytes -- `storage_path` (VARCHAR) - Full S3 path -- `storage_key` (VARCHAR) - S3 object key (unique) -- `parent_folder_id` (UUID) - Parent folder reference -- `current_version` (INTEGER) - Current version number -- `is_favorite` (BOOLEAN) - Favorite flag -- `is_deleted` (BOOLEAN) - Soft delete flag -- `deleted_at` (TIMESTAMP) - Deletion timestamp -- `created_at`, `updated_at` (TIMESTAMP) - -**folders** - Folder hierarchy - -- `id` (UUID) - Primary key -- `user_id` (VARCHAR) - User reference -- `name` (VARCHAR) - Folder name -- `description` (TEXT) - Optional description -- `parent_folder_id` (UUID) - Self-reference for hierarchy -- `path` (TEXT) - Materialized path (e.g., /root/subfolder) -- `depth` (INTEGER) - Depth in hierarchy -- `is_favorite` (BOOLEAN) - Favorite flag -- `is_deleted` (BOOLEAN) - Soft delete flag -- `deleted_at` (TIMESTAMP) - Deletion timestamp -- `created_at`, `updated_at` (TIMESTAMP) - -**file_versions** - Version history - -- `id` (UUID) - Primary key -- `file_id` (UUID) - File reference -- `version_number` (INTEGER) - Version number -- `storage_path` (VARCHAR) - S3 path for this version -- `storage_key` (VARCHAR) - S3 key for this version -- `size` (BIGINT) - Version size -- `comment` (TEXT) - Version comment -- `created_by` (VARCHAR) - User who created version -- `created_at` (TIMESTAMP) - -**shares** - Sharing links - -- `id` (UUID) - Primary key -- `user_id` (VARCHAR) - Owner reference -- `file_id` (UUID) - Shared file (nullable) -- `folder_id` (UUID) - Shared folder (nullable) -- `share_type` (VARCHAR) - 'file' or 'folder' -- `share_token` (VARCHAR) - Unique public token -- `access_level` (VARCHAR) - 'view', 'edit', 'download' -- `password` (VARCHAR) - Optional password hash -- `max_downloads` (INTEGER) - Download limit -- `download_count` (INTEGER) - Current downloads -- `expires_at` (TIMESTAMP) - Expiration date -- `created_at` (TIMESTAMP) - -**tags** - User tags - -- `id` (UUID) - Primary key -- `user_id` (VARCHAR) - User reference -- `name` (VARCHAR) - Tag name -- `color` (VARCHAR) - Tag color -- `created_at` (TIMESTAMP) - -**file_tags** - Many-to-many relation - -- `file_id` (UUID) - File reference -- `tag_id` (UUID) - Tag reference - -### Environment Variables - -#### Backend (.env) - -``` -NODE_ENV=development -PORT=3016 -DATABASE_URL=postgresql://mana:devpassword@localhost:5432/storage -MANA_AUTH_URL=http://localhost:3001 -CORS_ORIGINS=http://localhost:5173,http://localhost:5185,http://localhost:8081 -S3_ENDPOINT=http://localhost:9000 -S3_REGION=us-east-1 -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -STORAGE_S3_PUBLIC_URL=http://localhost:9000/storage-storage -MAX_FILE_SIZE=104857600 -MAX_FILES_PER_UPLOAD=10 -``` - -#### Web (.env) - -``` -PUBLIC_BACKEND_URL=http://localhost:3016 -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -``` - -## Shared Packages - -### @storage/shared - -- Types: `File`, `Folder`, `FileVersion`, `Share`, `Tag` -- Utils: File type detection, size formatting, path utilities - -## File Preview System - -The FilePreviewModal supports rich inline previews for various file types: - -| File Type | MIME / Extension | Preview | -|-----------|-----------------|---------| -| **Images** | `image/*` | Native `` via download URL | -| **Audio** | `audio/*` | Play button → global MiniPlayer/FullPlayer with frequency visualizer, queue from folder | -| **Video** | `video/*` | Native `