# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **CalChat** is a calendar mobile app with AI support. The core concept is managing calendar events through a chat interface with an AI chatbot. Users can add, edit, and delete events via natural language conversation. This is a fullstack TypeScript monorepo with npm workspaces. ## Commands ### Root (monorepo) ```bash npm install # Install all dependencies for all workspaces npm run format # Format all TypeScript files with Prettier ``` ### Client (apps/client) - Expo React Native app ```bash npm run start -w @calchat/client # Start Expo dev server npm run android -w @calchat/client # Start on Android npm run ios -w @calchat/client # Start on iOS npm run web -w @calchat/client # Start web version npm run lint -w @calchat/client # Run ESLint npm run build:apk -w @calchat/client # Build APK locally with EAS ``` ### Server (apps/server) - Express.js backend ```bash npm run dev -w @calchat/server # Start dev server with hot reload (tsx watch) npm run build -w @calchat/server # Compile TypeScript npm run start -w @calchat/server # Run compiled server (port 3000) ``` ## Technology Stack | Area | Technology | Purpose | |------|------------|---------| | Frontend | React Native | Mobile UI Framework | | | Expo | Development platform | | | Expo-Router | File-based routing | | | NativeWind | Tailwind CSS for React Native | | | Zustand | State management | | | FlashList | High-performance lists | | | EAS Build | Local APK/IPA builds | | Backend | Express.js | Web framework | | | MongoDB | Database | | | Mongoose | ODM | | | GPT (OpenAI) | AI/LLM for chat | | | X-User-Id Header | Authentication (simple, no JWT yet) | | | pino / pino-http | Structured logging | | | react-native-logs | Client-side logging | | Planned | iCalendar | Event export/import | ## Architecture ### Workspace Structure ``` apps/client - @calchat/client - Expo React Native app apps/server - @calchat/server - Express.js backend packages/shared - @calchat/shared - Shared TypeScript types and models ``` ### Frontend Architecture (apps/client) ``` src/ ├── app/ # Expo-Router file-based routing │ ├── _layout.tsx # Root Stack layout │ ├── index.tsx # Entry redirect │ ├── login.tsx # Login screen │ ├── register.tsx # Registration screen │ ├── (tabs)/ # Tab navigation group │ │ ├── _layout.tsx # Tab bar configuration (themed) │ │ ├── chat.tsx # Chat screen (AI conversation) │ │ ├── calendar.tsx # Calendar overview │ │ └── settings.tsx # Settings screen (theme switcher, logout) │ ├── event/ │ │ └── [id].tsx # Event detail screen (dynamic route) │ └── note/ │ └── [id].tsx # Note editor for event (dynamic route) ├── components/ │ ├── AuthGuard.tsx # Auth wrapper: loads user, shows loading, redirects if unauthenticated │ ├── BaseBackground.tsx # Common screen wrapper (themed) │ ├── BaseButton.tsx # Reusable button component (themed, supports children) │ ├── Header.tsx # Header component (themed) │ ├── AuthButton.tsx # Reusable button for auth screens (themed, with shadow) │ ├── ChatBubble.tsx # Reusable chat bubble component (used by ChatMessage & TypingIndicator) │ ├── TypingIndicator.tsx # Animated typing indicator (. .. ...) shown while waiting for AI response │ ├── EventCardBase.tsx # Shared event card layout with icons (used by EventCard & ProposedEventCard) │ ├── EventCard.tsx # Calendar event card (uses EventCardBase + edit/delete buttons) │ ├── EventConfirmDialog.tsx # AI-proposed event confirmation modal (skeleton) │ └── ProposedEventCard.tsx # Chat event proposal (uses EventCardBase + confirm/reject buttons) ├── Themes.tsx # Theme definitions: THEMES object with defaultLight/defaultDark, Theme type ├── logging/ │ ├── index.ts # Re-exports │ └── logger.ts # react-native-logs config (apiLogger, storeLogger) ├── services/ │ ├── index.ts # Re-exports all services │ ├── ApiClient.ts # HTTP client with X-User-Id header injection, request logging, handles empty responses (204) │ ├── AuthService.ts # login(), register(), logout() - calls API and updates AuthStore │ ├── EventService.ts # getAll(), getById(), getByDateRange(), create(), update(), delete() │ └── ChatService.ts # sendMessage(), confirmEvent(), rejectEvent(), getConversations(), getConversation() └── stores/ # Zustand state management ├── index.ts # Re-exports all stores ├── AuthStore.ts # user, isAuthenticated, isLoading, login(), logout(), loadStoredUser() │ # Uses expo-secure-store (native) / localStorage (web) ├── ChatStore.ts # messages[], isWaitingForResponse, addMessage(), addMessages(), updateMessage(), clearMessages(), setWaitingForResponse(), chatMessageToMessageData() ├── EventsStore.ts # events[], setEvents(), addEvent(), updateEvent(), deleteEvent() └── ThemeStore.ts # theme, setTheme() - reactive theme switching with Zustand ``` **Routing:** Tab-based navigation with Chat, Calendar, and Settings as main screens. Auth screens (login, register) outside tabs. Dynamic routes for event detail and note editing. **Authentication Flow:** - `AuthGuard` component wraps the tab layout in `(tabs)/_layout.tsx` - On app start, `AuthGuard` calls `loadStoredUser()` and shows loading indicator - If not authenticated, redirects to `/login` - `index.tsx` simply redirects to `/(tabs)/chat` - AuthGuard handles the rest - This pattern handles Expo Router's navigation state caching (avoids race conditions) ### Theme System The app supports multiple themes (light/dark) via a reactive Zustand store. **Theme Structure (`Themes.tsx`):** ```typescript export type Theme = { chatBot, primeFg, primeBg, secondaryBg, messageBorderBg, placeholderBg, calenderBg, confirmButton, rejectButton, disabledButton, buttonText, textPrimary, textSecondary, textMuted, eventIndicator, borderPrimary, shadowColor }; export const THEMES = { defaultLight: { ... }, defaultDark: { ... } } as const satisfies Record; ``` **Usage in Components:** ```typescript import { useThemeStore } from "../stores/ThemeStore"; const MyComponent = () => { const { theme } = useThemeStore(); return ; }; ``` **Theme Switching:** ```typescript const { setTheme } = useThemeStore(); setTheme("defaultDark"); // or "defaultLight" ``` **Note:** `shadowColor` only works on iOS. Android uses `elevation` with system-defined shadow colors. ### Backend Architecture (apps/server) ``` src/ ├── app.ts # Entry point, DI setup, Express config ├── controllers/ # Request handlers + middleware (per architecture diagram) │ ├── AuthController.ts # login(), register(), refresh(), logout() │ ├── ChatController.ts # sendMessage(), confirmEvent(), rejectEvent(), getConversations(), getConversation() │ ├── EventController.ts # create(), getById(), getAll(), getByDateRange(), update(), delete() │ ├── AuthMiddleware.ts # authenticate() - X-User-Id header validation │ └── LoggingMiddleware.ts # httpLogger - pino-http request logging ├── logging/ │ ├── index.ts # Re-exports │ ├── logger.ts # pino config with redact for sensitive data │ └── Logged.ts # @Logged() class decorator for automatic method logging ├── routes/ # API endpoint definitions │ ├── index.ts # Combines all routes under /api │ ├── auth.routes.ts # /api/auth/* │ ├── chat.routes.ts # /api/chat/* (protected) │ └── event.routes.ts # /api/events/* (protected) ├── services/ # Business logic │ ├── interfaces/ # DB-agnostic interfaces (for dependency injection) │ │ ├── AIProvider.ts # processMessage() │ │ ├── UserRepository.ts # findById, findByEmail, findByUserName, create + CreateUserData │ │ ├── EventRepository.ts │ │ └── ChatRepository.ts │ ├── AuthService.ts │ ├── ChatService.ts │ └── EventService.ts ├── repositories/ # Data access (DB-specific implementations) │ ├── index.ts # Re-exports from ./mongo │ └── mongo/ # MongoDB implementation │ ├── models/ # Mongoose schemas │ │ ├── types.ts # Shared types (IdVirtual interface) │ │ ├── UserModel.ts │ │ ├── EventModel.ts │ │ └── ChatModel.ts │ ├── MongoUserRepository.ts # findById, findByEmail, findByUserName, create │ ├── MongoEventRepository.ts │ └── MongoChatRepository.ts ├── ai/ │ ├── GPTAdapter.ts # Implements AIProvider using OpenAI GPT │ ├── index.ts # Re-exports GPTAdapter │ └── utils/ # Shared AI utilities (provider-agnostic) │ ├── index.ts # Re-exports │ ├── eventFormatter.ts # formatExistingEvents() for system prompt │ ├── systemPrompt.ts # buildSystemPrompt() - German calendar assistant prompt │ ├── toolDefinitions.ts # TOOL_DEFINITIONS - provider-agnostic tool specs │ └── toolExecutor.ts # executeToolCall() - handles getDay, proposeCreate/Update/Delete, searchEvents ├── utils/ │ ├── jwt.ts # signToken(), verifyToken() - NOT USED YET (no JWT) │ ├── password.ts # hash(), compare() using bcrypt │ ├── eventFormatters.ts # getWeeksOverview(), getMonthOverview() - formatted event listings │ └── recurrenceExpander.ts # expandRecurringEvents() - expand recurring events into occurrences └── scripts/ └── hash-password.js # Utility to hash passwords for manual DB updates ``` **API Endpoints:** - `POST /api/auth/login` - User login - `POST /api/auth/register` - User registration - `POST /api/auth/refresh` - Refresh JWT token - `POST /api/auth/logout` - User logout - `GET /api/events` - Get all events (protected) - `GET /api/events/range` - Get events by date range (protected) - `GET /api/events/:id` - Get single event (protected) - `POST /api/events` - Create event (protected) - `PUT /api/events/:id` - Update event (protected) - `DELETE /api/events/:id` - Delete event (protected) - `POST /api/chat/message` - Send message to AI (protected) - `POST /api/chat/confirm/:conversationId/:messageId` - Confirm proposed event (protected) - `POST /api/chat/reject/:conversationId/:messageId` - Reject proposed event (protected) - `GET /api/chat/conversations` - Get all conversations (protected) - `GET /api/chat/conversations/:id` - Get messages of a conversation with cursor-based pagination (protected) - `GET /health` - Health check - `POST /api/ai/test` - AI test endpoint (development only) ### Shared Package (packages/shared) ``` src/ ├── index.ts ├── models/ │ ├── index.ts │ ├── User.ts # User, CreateUserDTO, LoginDTO, AuthResponse │ ├── CalendarEvent.ts # CalendarEvent, CreateEventDTO, UpdateEventDTO, ExpandedEvent │ ├── ChatMessage.ts # ChatMessage, Conversation, SendMessageDTO, CreateMessageDTO, │ │ # GetMessagesOptions, ChatResponse, ConversationSummary, │ │ # ProposedEventChange, EventAction, RespondedAction, UpdateMessageDTO │ └── Constants.ts # DAYS, MONTHS, Day, Month, DAY_INDEX, DAY_INDEX_TO_DAY, │ # DAY_TO_GERMAN, DAY_TO_GERMAN_SHORT, MONTH_TO_GERMAN └── utils/ ├── index.ts └── dateHelpers.ts # getDay() - get date for specific weekday relative to today ``` **Key Types:** - `User`: id, email, userName, passwordHash?, createdAt?, updatedAt? - `CalendarEvent`: id, userId, title, description?, startTime, endTime, note?, isRecurring?, recurrenceRule? - `ExpandedEvent`: Extends CalendarEvent with occurrenceStart, occurrenceEnd (for recurring event instances) - `ChatMessage`: id, conversationId, sender ('user' | 'assistant'), content, proposedChanges? - `ProposedEventChange`: id, action ('create' | 'update' | 'delete'), eventId?, event?, updates?, respondedAction? - Each proposal has unique `id` (e.g., "proposal-0") for individual confirm/reject - `respondedAction` tracks user response per proposal (not per message) - `Conversation`: id, userId, createdAt?, updatedAt? (messages loaded separately via lazy loading) - `CreateUserDTO`: email, userName, password (for registration) - `LoginDTO`: identifier (email OR userName), password - `CreateEventDTO`: Used for creating events AND for AI-proposed events - `GetMessagesOptions`: Cursor-based pagination with `before?: string` and `limit?: number` - `ConversationSummary`: id, lastMessage?, createdAt? (for conversation list) - `UpdateMessageDTO`: proposalId?, respondedAction? (for marking individual proposals as confirmed/rejected) - `RespondedAction`: 'confirm' | 'reject' (tracks user response to proposed events) - `Day`: "Monday" | "Tuesday" | ... | "Sunday" - `Month`: "January" | "February" | ... | "December" ### Database Abstraction The repository pattern allows swapping databases: - **Interfaces** (`services/interfaces/`) are DB-agnostic - **Implementations** (`repositories/mongo/`) are DB-specific - To add MySQL: create `repositories/mysql/` with TypeORM entities ### Mongoose Model Pattern All Mongoose models use a consistent pattern for TypeScript-safe `id` virtuals: ```typescript import { IdVirtual } from './types'; const Schema = new Schema, {}, {}, IdVirtual>( { /* fields */ }, { virtuals: { id: { get() { return this._id.toString(); } } }, toJSON: { virtuals: true, transform: (_, ret) => { delete ret._id; delete ret.__v; return ret; } } } ); ``` Repositories use `doc.toJSON() as unknown as Type` casting (required because Mongoose's TypeScript types don't reflect virtual fields in toJSON output). ### Logging Structured logging with pino (server) and react-native-logs (client). **Server Logging:** - `pino` with `pino-pretty` for development, JSON in production - `pino-http` middleware logs all HTTP requests (method, path, status, duration) - `@Logged()` class decorator for automatic method logging on repositories and services - Sensitive data (password, token, etc.) automatically redacted via pino's `redact` config **@Logged Decorator Pattern:** ```typescript @Logged("MongoEventRepository") export class MongoEventRepository implements EventRepository { ... } @Logged("GPTAdapter") export class GPTAdapter implements AIProvider { ... } ``` The decorator uses a Proxy to intercept method calls lazily, preserves sync/async nature, and logs start/completion/failure with duration. **Log Summarization:** The `@Logged` decorator automatically summarizes large arguments to keep logs readable: - `conversationHistory` → `"[5 messages]"` - `existingEvents` → `"[3 events]"` - `proposedChanges` → logged in full (for debugging AI issues) - Long strings (>100 chars) → truncated - Arrays → `"[Array(n)]"` **Client Logging:** - `react-native-logs` with namespaced loggers (apiLogger, storeLogger) - ApiClient logs all requests with method, endpoint, status, duration - Log level: debug in __DEV__, warn in production ## MVP Feature Scope ### Must-Have - Chat interface with AI assistant (text input) for event management - Calendar overview - Manual event CRUD (without AI) - View completed events - Simple reminders - One note per event - Recurring events ### Nice-to-Have - iCalendar import/export - Multiple calendars - CalDAV synchronization with external services ## Development Environment ### MongoDB (Docker) ```bash cd apps/server/docker/mongo docker compose up -d # Start MongoDB + Mongo Express docker compose down # Stop services ``` - MongoDB: `localhost:27017` (root/mongoose) - Mongo Express UI: `localhost:8083` (admin/admin) ### Environment Variables Server requires `.env` file in `apps/server/`: ``` JWT_SECRET=your-secret-key JWT_EXPIRES_IN=1h MONGODB_URI=mongodb://root:mongoose@localhost:27017/calchat?authSource=admin OPENAI_API_KEY=sk-proj-... USE_TEST_RESPONSES=false # true = static test responses, false = real GPT AI LOG_LEVEL=debug # debug | info | warn | error | fatal NODE_ENV=development # development = pretty logs, production = JSON ``` ## Current Implementation Status **Backend:** - **Implemented:** - `AuthController`: login(), register() with error handling - `AuthService`: login() supports email OR userName, register() checks for existing email AND userName - `AuthMiddleware`: Validates X-User-Id header for protected routes - `MongoUserRepository`: findById(), findByEmail(), findByUserName(), create() - `utils/password`: hash(), compare() using bcrypt - `scripts/hash-password.js`: Utility for manual password resets - `dotenv` integration for environment variables - `ChatController`: sendMessage(), confirmEvent(), rejectEvent() - `ChatService`: processMessage() with test responses (create, update, delete actions), confirmEvent() handles all CRUD actions - `MongoEventRepository`: Full CRUD implemented (findById, findByUserId, findByDateRange, create, update, delete) - `EventController`: Full CRUD (create, getById, getAll, getByDateRange, update, delete) - `EventService`: Full CRUD with recurring event expansion via recurrenceExpander - `utils/eventFormatters`: getWeeksOverview(), getMonthOverview() with German localization - `utils/recurrenceExpander`: expandRecurringEvents() using rrule library for RRULE parsing - `ChatController`: getConversations(), getConversation() with cursor-based pagination support - `ChatService`: getConversations(), getConversation(), processMessage() uses real AI or test responses (via USE_TEST_RESPONSES), confirmEvent()/rejectEvent() update respondedAction and persist response messages - `MongoChatRepository`: Full CRUD implemented (getConversationsByUser, createConversation, getMessages with cursor pagination, createMessage, updateMessage, updateProposalResponse) - `ChatRepository` interface: updateMessage() and updateProposalResponse() for per-proposal respondedAction tracking - `GPTAdapter`: Full implementation with OpenAI GPT (gpt-4o-mini model), function calling for calendar operations, collects multiple proposals per response - `ai/utils/`: Provider-agnostic shared utilities (systemPrompt, toolDefinitions, toolExecutor, eventFormatter) - `ai/utils/systemPrompt`: Includes RRULE documentation - AI knows to create separate events when times differ by day - `utils/recurrenceExpander`: Handles RRULE parsing, strips `RRULE:` prefix if present (AI may include it) - `logging/`: Structured logging with pino, pino-http middleware, @Logged decorator - All repositories and GPTAdapter decorated with @Logged for automatic method logging - CORS configured to allow X-User-Id header - **Stubbed (TODO):** - `AuthController`: refresh(), logout() - `AuthService`: refreshToken() - JWT authentication (currently using simple X-User-Id header) **Shared:** Types, DTOs, constants (Day, Month with German translations), ExpandedEvent type, and date utilities defined and exported. **Frontend:** - **Authentication fully implemented:** - `AuthStore`: Manages user state with expo-secure-store (native) / localStorage (web) - `AuthService`: login(), register(), logout() - calls backend API - `ApiClient`: Automatically injects X-User-Id header for authenticated requests, handles empty responses (204) - `AuthGuard`: Reusable component that wraps protected routes - loads user, shows loading, redirects if unauthenticated - Login screen: Supports email OR userName login - Register screen: Email validation, checks for existing email/userName - `AuthButton`: Reusable button component with themed shadow - `Header`: Themed header component (logout moved to Settings) - `(tabs)/_layout.tsx`: Wraps tabs with AuthGuard for protected access - `index.tsx`: Simple redirect to chat (AuthGuard handles auth) - **Theme system fully implemented:** - `ThemeStore`: Zustand store with theme state and setTheme() - `Themes.tsx`: THEMES object with defaultLight/defaultDark variants - All components use `useThemeStore()` for reactive theme colors - Settings screen with theme switcher (light/dark) - `BaseButton`: Reusable themed button component - Tab navigation (Chat, Calendar, Settings) implemented with themed UI - Calendar screen fully functional: - Month navigation with grid display and Ionicons (chevron-back/forward) - MonthSelector dropdown with infinite scroll (dynamically loads months, lazy-loaded when modal opens, cleared on close for memory efficiency) - Events loaded from API via EventService.getByDateRange() - Orange dot indicator for days with events - Tap-to-open modal overlay showing EventCards for selected day - Supports events from adjacent months visible in grid - Uses `useFocusEffect` for automatic reload on tab focus - Chat screen fully functional with FlashList, message sending, and event confirm/reject - **Multiple event proposals**: AI can propose multiple events in one response - Arrow navigation between proposals with "Event X von Y" counter - Each proposal individually confirmable/rejectable - **Typing indicator**: Animated dots (. .. ...) shown after 500ms delay while waiting for AI response - Messages persisted to database via ChatService, loaded via `useFocusEffect` when screen gains focus - Tracks conversationId for message continuity across sessions - ChatStore with addMessages() for bulk loading, chatMessageToMessageData() helper - KeyboardAvoidingView for proper keyboard handling (iOS padding, Android height) - Auto-scroll to end on new messages and keyboard show - keyboardDismissMode="interactive" and keyboardShouldPersistTaps="handled" - `EventService`: getAll(), getById(), getByDateRange(), create(), update(), delete() - fully implemented - `ChatService`: sendMessage(), confirmEvent(), rejectEvent(), getConversations(), getConversation() - fully implemented with cursor pagination - `EventCardBase`: Shared base component with event layout (header, date/time/recurring icons, description) - used by both EventCard and ProposedEventCard - `EventCard`: Uses EventCardBase + edit/delete buttons for calendar display - `ProposedEventCard`: Uses EventCardBase + confirm/reject buttons for chat proposals (supports create/update/delete actions) - `Themes.tsx`: Theme definitions with THEMES object (defaultLight, defaultDark) including all color tokens (textPrimary, borderPrimary, eventIndicator, secondaryBg, shadowColor, etc.) - `EventsStore`: Zustand store with setEvents(), addEvent(), updateEvent(), deleteEvent() - stores ExpandedEvent[] - `ChatStore`: Zustand store with addMessage(), addMessages(), updateMessage(), clearMessages(), isWaitingForResponse/setWaitingForResponse() for typing indicator - loads from server on mount and persists across tab switches - `ThemeStore`: Zustand store with theme/setTheme() for reactive theme switching across all components - `ChatBubble`: Reusable chat bubble component with Tailwind styling, used by ChatMessage and TypingIndicator - `TypingIndicator`: Animated typing indicator component showing `. → .. → ...` loop while waiting for AI response - Event Detail and Note screens exist as skeletons ## Building ### Local APK Build with EAS ```bash npm run build:apk -w @calchat/client ``` This uses the `preview` profile from `eas.json` which builds an APK with: - `arm64-v8a` architecture only (smaller APK size) - No credentials required (`withoutCredentials: true`) - Internal distribution **Requirements:** Android SDK and Java must be installed locally. **EAS Configuration:** `apps/client/eas.json` contains build profiles: - `development`: Development client with internal distribution - `preview`: APK build for testing (used by `build:apk`) - `production`: Production build with auto-increment versioning **App Identity:** - Package name: `com.gilmour109.calchat` - EAS Project ID: `b722dde6-7d89-48ff-9095-e007e7c7da87` ## Documentation Detailed architecture diagrams are in `docs/`: - `api-routes.md` - API endpoint overview (German) - `technisches_brainstorm.tex` - Technical concept document (German) - `architecture-class-diagram.puml` - Backend class diagram - `frontend-class-diagram.puml` - Frontend class diagram - `component-diagram.puml` - System component overview