stammtisch-hersbruck/CLAUDE.md
Daniel Michelberger 893509be9d refactor: Aus Runs werden Stages, bis in die Datenbank
"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
2026-09-07 22:16:22 +02:00

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_URL in frontend/.env. Default ist die produktive Instanz https://api.stammtisch-hersbruck.de; für das lokale Backend aus backend/ auf http://127.0.0.1:8090 umstellen.
  • Schema: versioniert als Migration in backend/pb_migrations/, kommt per COPY ins 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 mit docker compose cp ins Repo geholt werden; siehe backend/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.id erwähnt, beginnt mit @request.auth.id != "" && (…). Ohne diesen Vorspann ist eine leere Relation gleich dem leeren @request.auth.id einer anonymen Anfrage — ein Team ohne Admins stünde damit offen im Netz. Siehe backend/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 per require(`${__hooks}/…`) im Handler geholt. Details in backend/README.md.
  • Authentication: Handled through src/lib/stores/pocketbase.svelte.ts with the AuthStore class
  • 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:

  1. Global stores in src/lib/stores/:

    • pocketbase.svelte.ts: PocketBase client, auth, and collection operations
    • app.svelte.ts: Global app state (theme, navigation, confirm dialogs, hotkeys)
    • teams.svelte.ts: Example of collection-specific store pattern
  2. Collection store pattern:

    • Each collection has a context-based store (see teams.svelte.ts as template)
    • Use setTeamContext() in parent and getTeamContext() in children
    • Stores provide: records, refresh(), create(), edit(), remove()
    • The collections helper in pocketbase.svelte.ts provides reusable CRUD operations
  3. Auth flow:

    • Auth state lives in auth store from pocketbase.svelte.ts
    • Cookie-based persistence available via auth.cookie flag
    • Auth store syncs with PocketBase authStore.onChange()

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-watcher package, toggle with toggleMode()
  • Tooltips: Global tooltip action available from app.svelte.ts using tippy.js

Important Patterns

Svelte 5 Runes: This project uses Svelte 5 syntax exclusively:

  • $state() for reactive state (not let with $:)
  • $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) und backend/ (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 mit node build. adapter-auto erkennt Coolify nicht und erzeugt kein build/.
  • Node: engines verlangt ^20.19 || ^22.12 || >=24; mit engine-strict=true (.npmrc) bricht npm ci sonst mit EBADENGINE ab.
  • Deployment-Env: PUBLIC_PB_URL muss dort gesetzt sein — sie wird zur Buildzeit eingesetzt, und die lokale .env ist 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, weil users.updateRule nur den Kontoinhaber selbst ändern lässt.
  • Reusable components in src/lib/components/
  • Stores use .svelte.ts extension for Svelte 5 runes
  • Static assets in static/
  • components.json configures 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 collections helper for consistency
  • UI components should use the shadcn-svelte imports from @/components/ui/