This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A SvelteKit GUI for planning university exams (Prüfungsplanung) at HM (Hochschule München). It is a thin frontend over a separate GraphQL backend (the plexams server). This repo holds no business logic or persistence — it queries/mutates the backend and renders the result. The UI language is German; the domain vocabulary below is essential to navigating the code.
npm run dev # dev server (vite)
npm run devhost # dev server exposed on the network (--host)
npm run build # production build (adapter-node → ./build)
npm run preview # serve the production build on :4173
npm run check # svelte-check type checking (checkJs is on; .js files are type-checked)
npm run lint # prettier --check + eslint
npm run format # prettier --write
npm test # playwright (builds + previews first, see playwright.config.js)
npx playwright test tests/test.js # run a single test fileCodegen / schema:
npm run codegen # regenerate src/lib/__generated__/graphql.ts from schema.graphql
npm run update-schema.graphql # refetch schema from $PLEXAMS_SERVER, then codegenThe backend URL comes from .env:
PLEXAMS_SERVER— private, used server-side (defaulthttp://localhost:8080/query).PUBLIC_PLEXAMS_SERVER— exposed to the client.
All data flows through this one GraphQL endpoint. schema.graphql is the committed copy of the backend schema; regenerate it with update-schema.graphql when the backend changes.
There are two GraphQL access patterns, both using the graphql-request library directly (there is no normalized cache):
-
SSR loads —
src/routes/**/+page.server.jsexport aload()that callsrequest(env.PLEXAMS_SERVER, gql\...`)` and returns data to the page. Use this for initial page render. -
Client-side proxy endpoints —
src/routes/api/**/+server.jsexposeGET/POSThandlers that forward a query or mutation to the backend (import { env } from '$env/dynamic/private'). Svelte componentsfetch('/api/...')these for interactivity (e.g. assigning an exam to a slot, planning a room). Mutations go through these/apiendpoints, never directly from the browser. When adding interactivity, add an/api/<name>/+server.jsthat wraps the GraphQL call andfetchit from the component.
GraphQL queries are written inline as gql\...`template strings in each+page.server.js/+server.js— there are no shared.graphql` document files. The same large exam selection set is duplicated across many files; when changing a query's fields, search for the other copies.
src/client.tsimports$houdiniand looks like a Houdini GraphQL setup, but Houdini is not configured or used (nohoudini.config.js, no.houdini). Treat it as dead scaffolding; the real client isgraphql-request.src/lib/__generated__/graphql.ts(from graphql-codegen) is generated but currently imported only for a couple of TypeScript types (e.g. inNav.svelte). It is not a runtime client.
Route folders mirror the planning workflow. Key domain terms:
- Exam / Prüfung — has an
ancode(the primary identifier across the system), amainExamer, registrations, conflicts, constraints. - ZPA — the central exam-administration system; source of exams to plan.
- Primuss — the registration system; source of student registrations (
studentRegs). - NTA — Nachteilsausgleich, disability accommodations (extra time, room-alone, etc.); a cross-cutting concern attached to exams and students (
mtknr= student id). - Slot — a (day, time) cell in the plan grid; exams are assigned to slots.
- Room planning — assigning rooms to slotted exams; KDP / EXaHM / SEB are special computer-exam room categories. Anny is an external room-booking source.
- Invigilation / Aufsicht — assigning supervisors to exam rooms per day/slot.
- Constraints — per-exam scheduling rules (fixed day/time, excluded days, same-slot, online, room requirements).
src/lib/ is grouped by these domains (exam/, slot/, nta/, invigilator/, examGroups/, examsInPlan/, zpa/). Nav.svelte is the authoritative index of available pages and the workflow order.
- Svelte 5 with legacy (non-runes) syntax — components use
export letprops and$storeauto-subscriptions, not$props()/$state(). Match the existing style; don't introduce runes unless migrating deliberately. - Mixed
.js/.tsfor server files — both are type-checked (checkJs: true,strict: true). New server files can be either; follow the neighbours in the folder. - Styling is Tailwind v4 + daisyUI; theme switching uses
theme-change(see the theme list inNav.svelte). - Prettier: tabs, single quotes, no trailing commas, printWidth 100. Run
npm run formatbefore committing. - Route param matchers live in
src/params/(string,integer), used as[mtknr=string].
Playwright is configured but tests/test.js is the unmodified SvelteKit scaffold (it asserts "Welcome to SvelteKit" and does not reflect this app). There is effectively no real test coverage yet — don't rely on the suite passing as a signal.