Die Einstellungen bekommen Reiter: Profil, Team, Fahrer (nur Teamleitung)
und Flag-Typen. /dashboard/teams wandert dorthin.
Neu ist die Hauptroute Fahrer - das Verzeichnis des Teams, fuer alle
sichtbar und fuer niemanden dort aenderbar. Angelegt, geaendert und mit
einem Konto verknuepft wird in den Einstellungen; serverseitig setzen das
die neuen Regeln der Collection durch.
Ein Fahrer bekommt sein Login entweder aus den vorhandenen Teammitgliedern
oder direkt neu angelegt. Die Einladung per E-Mail-Suche entfaellt: Sie
konnte nie funktionieren, weil users.listRule nur den eigenen Datensatz
freigibt - daher auch die abgeschnittenen IDs in der alten Mitgliederliste.
Events zeigen statt eines Status ihr Datum, mit Checkbox fuer mehrtaegig.
Gespeichert wird ein Enddatum nur, wenn es sich vom Beginn unterscheidet -
es ist damit zugleich das Kennzeichen fuer mehrtaegig statt einer zweiten
Wahrheit ueber dieselbe Sache. Listen gruppieren nach laufend, anstehend,
ohne Termin und vorbei.
Im Kopf teilen sich Name, Design-Umschalter und Logout eine Zeile, das
Team-Dropdown sitzt darunter; das Logo passt jetzt in die Leiste, statt
beschnitten zu wirken.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
Drei Migrationen, dazu der Snapshot in 1754400000_init_schema.js, aus dem
die TypeScript-Typen erzeugt werden - beide muessen zusammenpassen, sonst
laufen Schema und Typen auseinander.
riders.user verknuepft einen Fahrer mit einem Login, ohne cascadeDelete:
Ein geloeschtes Konto darf keine Zeiten mitreissen. Anlegen, Aendern und
Loeschen von Fahrern wird zur Sache der Teamleitung; bisher durfte das
jedes Teammitglied.
users.viewRule von "nur ich selbst" auf "eingeloggt" - ohne das laesst sich
zu einer bekannten ID kein Name anzeigen, was Mitgliederliste,
Fahrerkonten und den Melder eines Trail-Markers betrifft. Die listRule
bleibt eng, E-Mails bleiben ueber emailVisibility verborgen. users.createRule
erlaubt eingeloggten Nutzern das Anlegen von Konten, nicht der ganzen Welt.
events verliert status und bekommt starts/ends. Der Status war Handarbeit:
Wer vergass, ein Event abzuschliessen, hatte eine falsche Uebersicht. Ein
Datum weiss von selbst, was ansteht, laeuft und vorbei ist. ACHTUNG: Die
Migration entfernt das Feld samt seiner Werte.
trails bekommt status_changed und status_by. Einer Sperrung soll man
ansehen, ob sie von heute Morgen oder vom letzten Herbst ist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
Start und Stopp laufen ausschliesslich ueber TimeDialog. Der fuehrt immer
beide Angaben - Fahrer und Run -, auch wenn eine davon aus dem Kontext
vorbelegt ist: Beim Stoppen an der Strecke soll kein Zweifel bleiben, wen
man gerade erwischt.
DNF fragt nach, bevor es schreibt. Eine Zeit ohne Wertung laesst sich nicht
zurueckholen, ein Fehlgriff waere also endgueltig.
Tabelle und Toast-Leiste zeigen damit nur noch an; ihre Knoepfe oeffnen den
Dialog mit passender Vorbelegung. Der alte Stopp-Dialog und der
Fahrerlisten-Start entfallen - beides waren Ein-Tipp-Aktionen und damit
genau das, was hier nicht mehr sein soll.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
Die eigene Karte "Laufende Zeiten" entfaellt: Laufende stehen jetzt oben in
derselben Tabelle wie die gewerteten, mit "laeuft"-Badge statt Platzziffer.
Die Zeile ist Klickziel fuer den Stopp-Dialog, der Knopf daneben macht sie
auch per Tastatur bedienbar.
Neu ist eine dauerhaft sichtbare Leiste ueber allen Dashboard-Seiten,
solange irgendwo eine Zeit laeuft - bei mehreren eingeklappt mit Anzahl und
laengster Zeit, aufklappbar. Damit bleibt die Uhr im Blick, waehrend man
zwischen zwei Startern woanders unterwegs ist.
Der Stopp-Dialog wandert dafuer in eine eigene Komponente, die sich
Run-Seite und Leiste teilen, statt die Logik zweimal zu haben.
Monospace faellt appweit weg; einzige Ausnahme bleibt die grosse mitlaufende
Uhr im Stopp-Dialog. tabular-nums bleibt stehen: gleich breite Ziffern
derselben Schrift, ohne die eine laufende Uhr bei jedem Zehntel springt.
Tabellenkoepfe hatten den Hover-Hintergrund der Body-Zeilen geerbt; die
Regel sitzt jetzt im thead und gilt fuer alle Tabellen der App.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
Die Routen /dashboard/timing und /dashboard/results entfallen samt ihrer
Menueeintraege. Beides passiert jetzt dort, wo es hingehoert: am Run
innerhalb seines Events.
Die Run-Seite zeigt laufende Zeiten in Echtzeit, jede Zeile ist Klickziel
fuer den Stopp-Dialog (Stoppen oder DNF). Gestartet wird ueber einen Dialog
mit Fahrersuche. Dazu Rangliste mit CSV-Export und ein Abschnitt fuer
Zeiten ohne Wertung.
Die Fahrer-Seite zeigt alle Runs des Events aus Sicht eines Fahrers, mit
Zeit, Status und Platz - auch Runs ohne Zeit, damit sichtbar bleibt, was
fehlt.
enrich() im times-Store nimmt now als Parameter: Ohne das waere Date.now()
fuer Svelte keine Abhaengigkeit und laufende Zeiten stuenden still. Der
neue ticker() liefert diesen Wert, wodurch die {#key tick}-Bloecke der
alten Zeitnahmeseite entfallen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
Die Trail-Linie ist jetzt selbst die Bedienflaeche: Eine unsichtbare breite
Kopie der Linie faengt Hover und Klick ab, deshalb kann daneben gar nichts
mehr passieren. Geklickte Punkte werden auf die Linie projiziert, der
Setzmodus samt Button entfaellt.
Beim Ueberfahren wandert der Punkt in Karte und Hoehenprofil synchron mit,
inklusive Anzeige von Hoehe und Streckenkilometer. Die Kopplung laeuft in
beide Richtungen ueber die Distanz und interpoliert zwischen den
Stuetzpunkten, statt zum naechsten Vertex zu springen.
Ein Klick auf einen vorhandenen Marker oeffnet kein Setzen-Fenster mehr,
sondern ein Popup mit Typ, Notiz, Zeitpunkt und Melder. Fremde User-Namen
bleiben vorerst verborgen, weil users.listRule nur den eigenen Datensatz
freigibt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
Der vorige Fix wirkte nur im Dev-Modus. Live blieb die Trail-Linie aus,
obwohl das Worker-Asset ausgeliefert wurde (HTTP 200).
Ursache: Das per ?url eingebundene Asset ist nur eine Rohkopie der
Paketdatei. Deren erste Zeile ist ein relativer Import auf die
Nachbardatei:
import{...}from"./maplibre-gl-shared.mjs"
Diese Datei kopiert der Build nicht mit — unter
_app/immutable/assets/maplibre-gl-shared.mjs antwortet die Produktion mit
404. Der Worker startet dadurch, sein Import scheitert, und er antwortet auf
keine Nachricht mehr. GeoJSON-Quellen bleiben im Ladezustand, es wird keine
Linie gezeichnet. Im Dev-Modus fiel das nicht auf, weil Vite dort aus
node_modules aufloest, wo die shared-Datei danebenliegt.
?worker&url statt ?url laesst Vite den Worker samt Abhaengigkeiten zu einer
eigenstaendigen Datei buendeln.
Verifiziert am Build-Artefakt: Der Worker liegt nun unter
_app/immutable/workers/ und ist 469695 statt 19122 Bytes gross, enthaelt also
den shared-Code; eine Referenz auf maplibre-gl-shared existiert nicht mehr,
womit der 404 zur Laufzeit entfaellt. Ueber "node build" ausgeliefert
antwortet er mit HTTP 200 in voller Groesse. svelte-check ohne Fehler, alle
19 Tests bestehen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Karte zeigte Kacheln und stand am richtigen Ort, zeichnete aber weder in
der Trail-Uebersicht noch auf der Detailseite eine Linie. Das Hoehenprofil
aus denselben Versionsdaten wurde dagegen gerendert.
Ursache: MapLibre 6 liegt als drei Dateien vor (Haupteinstiegspunkt, shared,
worker) und laedt den Worker zur Laufzeit ueber import.meta.url aus dem
eigenen Verzeichnis nach. Vites Dependency-Optimierung buendelt jedoch nur
den Haupteinstiegspunkt nach node_modules/.vite/deps — daneben liegt keine
maplibre-gl-worker.mjs. Der Worker startet dadurch zwar und ist verbunden,
sein Import laeuft aber ins Leere: Er antwortet auf keine Nachricht mehr.
GeoJSON-Quellen bleiben deshalb dauerhaft im Ladezustand
(isSourceLoaded false, keine Source-Features, _updatePromise loest nie auf),
und es gibt nichts zu zeichnen. Rasterkacheln brauchen keinen Worker, deshalb
erschien die Karte trotzdem und der Fehler sah nach fehlenden Daten aus.
setWorkerUrl mit einem ?url-Import zeigt auf die echte Datei und umgeht das,
ohne in Vites Optimierung einzugreifen. Ein optimizeDeps.exclude waere der
naheliegendere Weg gewesen, bricht hier aber die Modulaufloesung von
SvelteKit.
Verifiziert: Linie erscheint in Uebersicht und Detailseite; die Daten waren
durchgehend intakt (LineString, 225 Koordinaten). svelte-check ohne Fehler,
alle 19 Tests bestehen, npm run build erfolgreich, und die Worker-Datei liegt
als Asset im Produktionsbuild und wird vom Client-Bundle referenziert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Auf der Login-Karte steht jetzt das Logo an Stelle des Titels "Zeitnahme",
ebenso entfaellt der Name im Seitentitel. Im Header stand ohnehin nur das
Logo, dort war nichts zu entfernen.
Logo.svelte nimmt dafuer eine class-Prop entgegen, damit dieselbe
Komponente im Header und auf der Login-Karte in unterschiedlicher Groesse
verwendet werden kann; der bisherige Default w-24 h-24 bleibt, der Header
aendert sich also nicht. Die Bilder skalieren nun ueber h-full w-full mit
dem Container statt ueber die nie gueltige Klasse "-24".
"Zeitnahme" als Bezeichnung der Funktion (Navigation, Ueberschrift der
Timing-Seite, Buttons) bleibt bestehen — entfernt wurde nur der
Produktname.
Verifiziert: svelte-check ohne Fehler, Login-Seite im Browser geprueft,
Logo in beiden Groessen isoliert gerendert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der Button "Als erledigt markieren" stand bisher an jedem Marker,
obwohl die updateRule der Collection Änderungen nur dem Ersteller,
Trail-Paten oder Team-Admins erlaubt — für alle anderen scheiterte
toggleResolved mit einer stillen unhandled Rejection. Neuer Getter
TrailMarkerStore.canEdit() spiegelt die Regel im Frontend; der Button
ist jetzt entsprechend gegated und Fehler landen als sichtbarer Alert
statt zu verschwinden.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBMBfip6BAG8VV9raSf6vu
elevation enthält einen Eintrag pro Originalpunkt, geojson.coordinates
nur die per Douglas-Peucker vereinfachten Punkte. Ein gemeinsamer Index
zeigte deshalb ab rund 3 % des Profils auf das Streckenende. Die neue
Distanz-Kopplung nutzt coord_distances (kumulative Distanz je
vereinfachter Koordinate), um beim Überfahren des Höhenprofils die
nächstgelegene Stelle auf der Karte zu finden.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBMBfip6BAG8VV9raSf6vu
- TrailMap: IntersectionObserver für WebGL-Kontexte (max 8-16 gleichzeitig)
→ Karte erst laden, wenn sichtbar, rootMargin 200px für frühes Laden
- Trail-Liste: formatKm zeigt 0 m als "0,0 km" statt "–"
- Trail-Anlegen: Enter im Namensfeld triggert save(), saving-Guard hinzugefügt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Neuer Getter canManage prüft, ob der aktive User Admin im Team ist
(analog zu canEdit in trails.svelte.ts)
- seedDefaults() wirft jetzt gleich wenn keine Admin-Rechte vorhanden
sind statt später 403 zu bekommen
- Bessere Fehlerbehandlung in der Schleife: bricht ab und zeigt,
wie viele Flags bereits angelegt wurden
- Kommentar zu DEFAULT_FLAGS erweitert um das Label-Matching-Verhalten
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwei neue Store-Klassen für das Trail-Feature:
- TrailFlagStore: Verwaltet die Meldungsarten (wie "Baum quer")
mit seedDefaults() für das Anlegen der Standardmenge
- TrailMarkerStore: Verwaltet verortete Meldungen auf Trails
mit toggleResolved() zum Abhaken (nicht Löschen)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
TrailStore verwaltet Trail-Datensätze mit Team-Filterung und Edit-Berechtigungen
für Trail-Paten, Team-Owner und Admins. TrailVersionStore verwaltet GPX-Versionen
mit Upload und Aktivierung (mit Prüfung auf Trail-Zugehörigkeit).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Operator auf >= und <= geändert (kumulativ zählen)
- Test "ignoriert Höhenschwankungen" ersetzt durch zwei separate Tests:
* "erfasst einen gleichmäßigen Anstieg auch in feinen Schritten" (300→303 in 1m Schritten = 3m)
* "ignoriert Rauschen, das um denselben Wert schwankt" (auf/ab ohne Gewinn = 0m)
- Fehlertext auf Brief-Fassung: "Die Datei ist kein gültiges XML."
- Test-Regex angepasst zum neuen Fehlertext
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Prüfe lat/lon auf null vor Number-Konvertierung (Number(null) ist 0, nicht NaN)
- Ergänze Test für übersprungene Punkte ohne Koordinaten
- Bessere Fehlermeldung für ungültiges XML
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
npm run generate-pocketbase-types liest das Collections-Array jetzt direkt
aus backend/pb_migrations/ (scripts/schema-to-json.mjs) und speist es über
pocketbase-typegen --json ein. Keine laufende Instanz, kein Token, kein
Netzwerkzugriff mehr nötig.
Damit stammen Schema und Typen aus derselben versionierten Quelle und können
nicht auseinanderlaufen. Vorher zeigten sie auf eine Remote-Instanz, deren
Stand niemand garantieren konnte — nach dem Neu-Deployment lieferte sie
prompt Typen ohne die fachlichen Collections.
frontend/.env enthält dadurch nur noch PUBLIC_PB_URL. PB_TYPEGEN_URL und
PB_TYPEGEN_TOKEN entfallen (letztere wurde ohnehin nirgends gelesen),
PB_SUPERUSER_TOKEN wird nicht mehr gebraucht. Die Datei enthält damit kein
Geheimnis mehr.
Verifiziert: ohne .env, ohne Token und ohne laufende Instanz entstehen alle
sechs Collections, svelte-check meldet 0 Fehler und 0 Warnungen.
Die System-Collections (_superusers, _mfas, ...) fehlen in den Typen, weil
sie bewusst nicht in der Migration stehen. Die Anwendung verwendet sie nicht.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die SvelteKit-App liegt unter frontend/, das Backend unter backend/ als
eigenständige PocketBase-Instanz mit Dockerfile, docker-compose und
versioniertem Schema.
- PocketBase-URL über PUBLIC_PB_URL konfigurierbar, Default bleibt die
produktive Instanz https://api.stammtisch-hersbruck.de
- Schema als Snapshot-Migration der sechs fachlichen Collections
(users, teams, events, runs, riders, times), abgezogen von der
produktiven Instanz. Verifiziert: ein Erststart gegen leere pb_data
legt alle sechs an, Felder und API-Rules stimmen überein.
- pb_data und pb_migrations als Bind-Mounts, damit Daten persistieren und
im Admin-UI erzeugte Migrationen im Repo landen
- Veraltete Dokumentation entfernt: pocketbase_schema.json nannte
Collections (stages, results, organizers), die es nicht gibt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>