"Run" hiess in dieser App immer schon der abgesteckte Abschnitt eines Events, auf dem gefahren und gestoppt wird — also das, was im Rennsport Stage heisst. "Run" ist daneben der einzelne Durchgang eines Fahrers, und genau der steht hier als `times`. Zwei Bedeutungen fuer ein Wort, an einer Stelle, an der beide vorkommen. Die Migration benennt um statt neu anzulegen: Collection und Feld behalten ihre IDs, PocketBase benennt Tabelle und Spalte um, die Daten bleiben stehen. Gesucht wird ueber die Collection-ID und nicht ueber den Namen, damit sie auch auf einer frischen Datenbank durchlaeuft, deren Snapshot die Collection bereits `stages` nennt. Im Frontend wandert `times.run` zu `times.stage`, der Store heisst stages.svelte.ts, und aus /events/[id]/runs/[runId] wird /events/[id]/stages/[stageId]. Nicht umbenannt: `running`, `allRunning` und RunningTimesToast. Das sind laufende Zeiten und keine Stages — dieselben Buchstaben, andere Sache. Und die Artikel: Der Run war maskulin, die Stage ist feminin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
7.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
stammtisch-hersbruck.de is a SvelteKit 5 application using Svelte 5's new runes syntax ($state, $props, etc.) with PocketBase as the backend. The app manages events, stages, riders, times and teams for a motorsport/cycling event organization.
The repository is a monorepo: the SvelteKit app lives in frontend/, the PocketBase instance in backend/.
Development Commands
Alle Frontend-Befehle laufen aus frontend/:
cd frontend
# Start development server (runs on http://stammtisch-hersbruck.de.localhost:31337)
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
# Type-check Svelte files
npm run check
# Type-check with watch mode
npm run check:watch
# Generate TypeScript types from the schema migration in backend/pb_migrations
# (no running instance, no token, no network access needed)
npm run generate-pocketbase-types
Backend (PocketBase) aus backend/:
cd backend
# PocketBase lokal starten (Admin-UI auf http://127.0.0.1:8090/_/)
docker compose up -d --build
# Stoppen
docker compose down
Architecture
Backend Integration (PocketBase)
- API Base URL: konfigurierbar über
PUBLIC_PB_URLinfrontend/.env. Default ist die produktive Instanzhttps://api.stammtisch-hersbruck.de; für das lokale Backend ausbackend/aufhttp://127.0.0.1:8090umstellen. - Schema: versioniert als Migration in
backend/pb_migrations/, kommt perCOPYins Image (kein Bind-Mount — der würde das Image-Verzeichnis überdecken). Im Admin-UI erzeugte Migrationen liegen deshalb zunächst nur im Container und müssen mitdocker compose cpins Repo geholt werden; siehebackend/README.md. - Type-safe client: The PocketBase client is typed using auto-generated types in
frontend/src/lib/types.d.ts - Collections: users, teams, team_invites, events, stages, riders, times, trails, trail_versions, trail_flags, trail_markers, trail_comments, event_participants, event_series
- Regeln: Jede Zugriffsregel, die
@request.auth.iderwähnt, beginnt mit@request.auth.id != "" && (…). Ohne diesen Vorspann ist eine leere Relation gleich dem leeren@request.auth.ideiner anonymen Anfrage — ein Team ohne Admins stünde damit offen im Netz. Siehebackend/pb_migrations/1754501700_rules_require_login.js. - Hooks:
backend/pb_hooks/enthält serverseitiges JS für das, was keine Collection-Regel abbilden kann — derzeit die öffentlichen Routen der Einladungslinks. Jeder Route-Handler läuft in einer eigenen JS-Laufzeit; gemeinsamer Code gehört in ein Modul und wird perrequire(`${__hooks}/…`)im Handler geholt. Details inbackend/README.md. - Authentication: Handled through
src/lib/stores/pocketbase.svelte.tswith theAuthStoreclass - File handling: Use
getFileURL(record, file, options)helper for PocketBase file URLs
State Management Pattern
The app uses Svelte 5 runes for state management with a custom store pattern:
-
Global stores in
src/lib/stores/:pocketbase.svelte.ts: PocketBase client, auth, and collection operationsapp.svelte.ts: Global app state (theme, navigation, confirm dialogs, hotkeys)teams.svelte.ts: Example of collection-specific store pattern
-
Collection store pattern:
- Each collection has a context-based store (see
teams.svelte.tsas template) - Use
setTeamContext()in parent andgetTeamContext()in children - Stores provide:
records,refresh(),create(),edit(),remove() - The
collectionshelper inpocketbase.svelte.tsprovides reusable CRUD operations
- Each collection has a context-based store (see
-
Auth flow:
- Auth state lives in
authstore frompocketbase.svelte.ts - Cookie-based persistence available via
auth.cookieflag - Auth store syncs with PocketBase
authStore.onChange()
- Auth state lives in
UI Components
- UI Library: Using shadcn-svelte components (bits-ui based)
- Component path alias:
@/components/ui/*maps to$lib/components/ui/* - Styling: Tailwind CSS 4.x with custom configuration
- Dark mode: Handled by
mode-watcherpackage, toggle withtoggleMode() - Tooltips: Global
tooltipaction available fromapp.svelte.tsusing tippy.js
Important Patterns
Svelte 5 Runes: This project uses Svelte 5 syntax exclusively:
$state()for reactive state (notletwith$:)$props()for component props$derived()for computed values{@render children?.()}for slot content
Type Generation: After modifying the schema migration in backend/pb_migrations/, run npm run generate-pocketbase-types to update frontend/src/lib/types.d.ts. The script reads the collections array straight out of the migration (frontend/scripts/schema-to-json.mjs) — schema and types come from the same versioned source and cannot drift apart. Commit the regenerated file.
Async Data Loading: Root layout (src/routes/+layout.svelte) shows pattern:
{#await load()}
<!-- loading state -->
{:then _}
<!-- main content -->
{/await}
Confirm Dialogs: Use app.confirm.request() for user confirmations (see events.svelte.ts remove pattern).
Hotkeys: Use app.hotkey(event, condition, callback) which auto-ignores input fields and contenteditable elements.
Tooltips: Never use the title attribute for tooltips — appwide. <Button>
takes a tooltip="…" prop (it also becomes the aria-label, so icon-only
buttons keep an accessible name); every other element uses the action:
use:tooltip={{ content: '…' }} from app.svelte.ts. Both render through
tippy.js, which the action initializes, updates and destroys.
Project Configuration
- Repo-Struktur: Monorepo mit
frontend/(SvelteKit) undbackend/(PocketBase). Ein einziges Git-Repo im Root. - Dev server port: 31337 (strict mode, custom domain:
stammtisch-hersbruck.de.localhost) - Path alias:
@/*resolves to./src/lib/*(configured in frontend/svelte.config.js) - Adapter:
@sveltejs/adapter-node— Deployment läuft über Coolify mitnode build.adapter-autoerkennt Coolify nicht und erzeugt keinbuild/. - Node:
enginesverlangt^20.19 || ^22.12 || >=24; mitengine-strict=true(.npmrc) brichtnpm cisonst mitEBADENGINEab. - Deployment-Env:
PUBLIC_PB_URLmuss dort gesetzt sein — sie wird zur Buildzeit eingesetzt, und die lokale.envist gitignored.
File Structure Notes
- Routes are in
src/routes/following SvelteKit conventions. Routennamen immer auf Englisch (/invite/[token], nicht/einladung/[token]) — Oberflächentexte sind deutsch, Pfade nicht. - Der Menüpunkt Team (
/dashboard/team) führt Team und Kader an einer Stelle: Kennzahlen, Teamdaten, Einladungslinks und die Fahrerliste samt der Verwaltungsknöpfe der Teamleitung. Unter Einstellungen steht nur noch, was einen selbst betrifft (Profil, Flag-Typen). - Ein Fahrer, ein Name, eine Richtung: Ein Konto entsteht immer am Fahrer
(
riders.createLogin), es gibt kein Verknüpfen bestehender Konten und kein Lösen. Angezeigt wird ausschließlich der Fahrername — der Kontoname wäre eine zweite Wahrheit, die niemand geradeziehen könnte, weilusers.updateRulenur den Kontoinhaber selbst ändern lässt. - Reusable components in
src/lib/components/ - Stores use
.svelte.tsextension for Svelte 5 runes - Static assets in
static/ components.jsonconfigures shadcn-svelte CLI
When Working with This Codebase
- Always use Svelte 5 runes syntax, never legacy Svelte syntax
- Use the official Svelte MCP server to validate Svelte code
- Collection stores should follow the pattern in
teams.svelte.ts - PocketBase operations go through the
collectionshelper for consistency - UI components should use the shadcn-svelte imports from
@/components/ui/