stammtisch-hersbruck/CLAUDE.md
Daniel Michelberger 45835cd45c refactor: Team und Kader an einem Ort
Frueher lag beides in den Einstellungen und ein blasses Verzeichnis
zusaetzlich unter "Fahrer". Wer einen Fahrer anlegen wollte, landete
erst im Verzeichnis, dann in den Einstellungen. Jetzt gibt es einen Ort,
und die Verwaltungsknoepfe stehen an den Zeilen, zu denen sie gehoeren.

Der Einladungslink sitzt im Kopf der Seite statt in einer eigenen
Section; die ausgegebenen Links stehen mit im Dialog, weil sie sonst mit
der Section auch das Kopieren und das Zuruecknehmen verloren haetten.

Die Adminrolle wandert aus der Tabellenzeile in den Personendialog. Ein
Knopf, der ohne Rueckfrage Rechte vergibt, gehoert nicht neben die Icons
fuers Entfernen und Loeschen. Der Bearbeiten-Knopf steht dafuer an jeder
Zeile, auch an Konten ohne Fahrer — sonst waere deren Rolle nirgends
mehr erreichbar.

Die Liste "Meine Teams" entfaellt: Wechseln und Anlegen stehen im
Submenue am Menuepunkt Team. Verlassen gilt nur dem aktiven Team und
steht deshalb jetzt im Kopf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:11:13 +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, runs, 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, runs, 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/