# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash npm start # Dev server at http://localhost:4200/ (live reload) npm run build # Production build npm run watch # Build with watch mode (development config) npm test # Run unit tests (Jasmine/Karma in Chrome) ``` No linter is configured in this project. ## Architecture Angular 18 single-page app for managing calendar events for the "Nachklang" organization. Uses Angular Material for UI, RxJS for async data, and reactive forms. No NgRx — state lives in component local variables. **Environments** (`src/environments/`): `apiUrl` and `adminAppUrl`. - Dev: `http://localhost:3000` (expects backend running locally) / `http://localhost:3002` - Prod: `https://api.nachklang.art` / `https://admin.nachklang.art` ## Authentication **This app has no login form and no accounts of its own.** Since the auth cutover (`docs/calendar-auth-migration.md` in the API repo) it shares one identity with the tickets, feedback and admin apps: accounts live in the admin app, and the session is an httpOnly cookie on `.nachklang.art` that the *API's* host sets. Consequences that cannot be designed around: - `withCredentials: true` is mandatory on every call (`api.service.ts` sets it once). Without it the browser sends no cookie and the API answers 401. - This app can never read or verify the session. It calls `GET /admin/me` and believes the answer; the API's `requireAppAccess('calendar')` is the actual gate. - 401 and 403 mean different things and must not be collapsed. 401 means "nobody is signed in" and is the only one worth redirecting to the login page - redirecting on 403 produces a loop where signing in succeeds and lands straight back on the refusal. See `failure` in `admin.component.ts`. - Sign-out ends the session for *all four* apps; there is only one. The dev server must be reachable at a port the API trusts. `ng serve` defaults to 4200, which is in better-auth's dev `localhostOrigins` and in the admin app's `NEXT_PUBLIC_ALLOWED_REDIRECT_ORIGINS`; another port fails sign-out and the return redirect, not the sign-in. **The public calendar stays anonymous.** `GET /calendar/events/public/json` needs no session at all, because nachklang.art reads it to show the next upcoming event. **Routing** (`app.routing.ts`): - `/` → `LandingpageComponent` - `/admin` → `AdminComponent` (main calendar management page) - `**` → `NotfoundComponent` **Service layer** (`src/app/services/`): - `api.service.ts` — all HTTP calls to the backend REST API; carries the session cookie via `withCredentials`, holds no credential itself - `admin-auth.service.ts` — where signing in happens: the admin app's login URL (with a `?redirect=` back here) and sign-out - `utils.service.ts` — caches the signed-in user's display name for unsaved draft rows. Cosmetic only; the server takes the author from the session **Data models** (`src/app/models/`): `Event`, `User`, `Session` **Admin page** (`src/app/pages/admin/`) is the core of the app. It owns events state and passes it down to `EventsTableComponent` via `@Input()`. Key features: multi-calendar support (public, members, choir, management, birthdays), event filtering (future/past/all), sorting, and an event-move dialog (`EventMovePopupComponent` via Angular Material Dialog). **Calendar-specific behavior:** Birthday calendar auto-sets recurrence to YEARLY. Events have a `status` field (`DRAFT` / `DELETED`). **Session lifecycle:** `AdminComponent` calls `me()` on init. 401 redirects to the admin app's login carrying this URL as the return target; 403 (or a signed-in account without the `calendar` permission) shows a refusal with Reload/Sign out; anything else shows "cannot be reached". None of those three redirect, on purpose. **Dead since the cutover, kept only because deleting files needs a decision:** `src/app/models/session.ts` and the unrouted `LoginComponent`.