managarten/apps/calendar/CLAUDE.md
Till JS 22a73943e1 chore: complete ManaCore → Mana rename (docs, go modules, plists, images)
Final cleanup of references missed in previous rename commits:

- Dockerfiles: PUBLIC_MANA_CORE_AUTH_URL → PUBLIC_MANA_AUTH_URL
- Go modules: github.com/manacore/* → github.com/mana/* (7 go.mod files)
- launchd plists: com.manacore.* → com.mana.* (14 files renamed + content)
- Image assets: *_Manacore_AI_Credits* → *_Mana_AI_Credits* (11 files)
- .env.example files: ManaCore brand strings → Mana
- .prettierignore: stale apps/manacore/* paths → apps/mana/*
- Markdown docs (CLAUDE.md, /docs/*): mana-core-auth → mana-auth, etc.

Excluded from rename: .claude/, devlog/, manascore/ (historical content),
client testimonials, blueprints, npm package refs (@mana-core/*).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:26:10 +02:00

23 KiB

Calendar Project Guide

Übersicht

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.

App Port URL
Server 3014 http://localhost:3014
Web App 5179 http://localhost:5179
Landing Page 4322 http://localhost:4322
Mobile 8081 Expo Go

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)

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

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)

pnpm dev                         # Start dev server
pnpm build                       # Build for production
pnpm preview                     # Preview production build

Landing Page (apps/calendar/apps/landing)

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)

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

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)

PUBLIC_BACKEND_URL=http://localhost:3014
PUBLIC_MANA_AUTH_URL=http://localhost:3001

Mobile (.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

<script lang="ts">
  import { viewStore } from '$lib/stores/view.svelte';

  // Reaktiver State
  let loading = $state(false);

  // Abgeleiteter Wert
  let formattedDate = $derived(
    format(viewStore.currentDate, 'MMMM yyyy', { locale: de })
  );

  // Side Effect
  $effect(() => {
    console.log('Date changed:', viewStore.currentDate);
  });
</script>

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

# 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

# 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

Testing API (mit curl)

# 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

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