# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. @AGENTS.md ## Commands ```bash npm run dev # Dev server at http://localhost:4200/ - the port is required, see Authentication npm run build # Production build (output: standalone; postbuild copies .next/static in) npm run lint # ESLint (eslint-config-next) npm test # Vitest, pure logic in lib/ (runs with TZ=America/New_York on purpose) ``` `NEXT_PUBLIC_API_BASE_URL` and `NEXT_PUBLIC_ADMIN_APP_URL` must be set (`.env.local`, from `.env.local.example`); `lib/env.ts` fails the build without them. ## Docs `docs/` is kept current as part of every change - features in `features.md`, surprises in `gotchas.md`, API requests in `api-wishlist.md`. - [docs/architecture.md](docs/architecture.md) - routes, auth, data flow, time handling - [docs/features.md](docs/features.md) - what the app does - [docs/gotchas.md](docs/gotchas.md) - read before assuming how the API, Next or Base UI behaves - [docs/api-wishlist.md](docs/api-wishlist.md) - API changes this app would like - [docs/deployment.md](docs/deployment.md) - Plesk build/run, switchover, rollback - [docs/rewrite-plan.md](docs/rewrite-plan.md) - the Angular → Next rewrite: decisions and progress log ## Architecture Next.js 16 (App Router) + React 19 + TypeScript + Tailwind 4 + shadcn (Base UI primitives), the same stack and design system as `nachklang-feedback`. Rewritten from Angular in September 2026. German UI. - `app/page.tsx` - public landing page (server component, `revalidate = 300`). - `app/admin/` - the Verwaltung: `layout.tsx` is the session gate, `page.tsx` the event list, `login/page.tsx` a forwarder to the admin app. - `components/admin/` - list, toolbar, chips, `EventSheet` (create/edit/duplicate/delete) and `EventForm`, `PublishDialog`, `SubscriptionsDialog`, hooks `useEvents` / `useListState`. - `lib/` - all logic that isn't rendering, and all of it is unit-tested: `api.ts`, `dates.ts` (Berlin time), `event-form.ts` (form rules, validation, payloads), `filter.ts`, `url-state.ts`, `calendars.ts` (the one calendar list), `events.ts` (parsing the API's raw rows). - `proxy.ts` - cookie-presence gate on `/admin/*`. Rules worth knowing before editing (details in `docs/`): - **Every wall-clock time is Europe/Berlin**, whatever the browser's zone. Forms hold strings, the API gets UTC instants. Whole-day events are 00:00-23:59 Berlin, end inclusive. - **Always send the whole event.** The API's PUT defaults missing fields (`status` → `PUBLIC`, `repeatFrequency` → `''`). - **Calendars live in `lib/calendars.ts`** and must match `calendarNames` in the API. - **After `npx shadcn add`**, point `cn` imports back at `@/lib/utils` and drop the `cn` / `next-themes` packages it adds (see `docs/gotchas.md`). ## 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: - `credentials: "include"` is mandatory on every call (`lib/api.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. `/admin/me` answers 200 for an account without the calendar permission - check `apps`. - 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 `app/admin/layout.tsx`. - 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. `npm run dev` pins 4200 (`next dev -p 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 - and so does this app's landing page, server-side. ## Local testing Use a throwaway database, never shared infrastructure: the API repo's `docker/init` scripts seed a MariaDB container (podman works) including the `dev@nachklang.art` account; start the API from a scratch directory with its own `.env`. The exact commands are in the progress log of `docs/rewrite-plan.md` ("Local test environment").