Commit graph

73 commits

Author SHA1 Message Date
Daniel Michelberger
b3d77dddbe fix: doppeltes Padding in Karten mit Knopfzeile
Die Karte bringt py-4 mit; Inhalt und Knopfzeile brachten es ein zweites
Mal. Unter dem letzten Knopf standen dadurch 32 statt 16 px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:11:28 +02:00
Daniel Michelberger
66e351e54f feat: Team wird zum Menuepunkt mit Submenue
Das Teamdropdown stand als eigenes Bedienelement neben dem Namen — ein
zweiter Schalter fuer eine Sache, die schon einen Menuepunkt hat. Jetzt
klappt der Menuepunkt Team beim Ueberfahren auf: die eigenen Teams, ein
Klick wechselt und oeffnet die Teamseite, darunter "Team erstellen".

Die Zeiger- und Tastenhandler von bits-ui werden am Trigger bewusst
ueberschrieben. Deren onclick ruft preventDefault und macht den Link
tot, ihr pointerdown klappt zu, was der Hover gerade aufgeklappt hat —
zwei Herren ueber dasselbe open, das Ergebnis war Flackern statt
Navigation. Das Menue verzichtet zudem auf Fokusfang und Scrollsperre;
beides gehoert zu einem Dialog, nicht zu etwas, ueber das der Zeiger nur
streift.

Der Anlegedialog liegt jetzt als CreateTeamDialog vor: Es gibt zwei
Einstiege, und ein neues Team muss an beiden dasselbe bedeuten — Team
anlegen, Standardsatz Flag-Typen dazu, ab dann ist es das aktive.

Das Logo ragt mit -my-3 leicht ueber die Kopfleiste hinaus, verlinkt
aufs Dashboard und teilt sich den Hover mit dessen Menuepunkt. Der
aktive Menuepunkt traegt das Primary-Design.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:11:28 +02:00
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
Daniel Michelberger
8c11095b17 feat: Ein Fahrer hat einen Namen, nicht zwei
Vorname und Nachname getrennt zu fuehren versprach eine Ordnung, die es
hier nie gab: sortiert wird ueber den ganzen Namen, angezeigt wird der
ganze Name, gesucht wird ueber beides zugleich. Dafuer musste jede Maske
zwei Felder anbieten und jede Anzeige sie wieder zusammensetzen — und
wer schlicht "Schorsch" heisst, stand vor der Frage, welches der beiden
Felder das ist.

Das Feld name gibt es an riders laengst; es lag nur brach, weil die
Anwendung firstname und lastname benutzte. Es uebernimmt deren Inhalt —
kein neues Feld, keine zweite Spalte, nichts, was auf bestehenden
Instanzen erst entstehen muesste.

Zurueck geht es nur ungenau: Die Umkehrung trennt am ersten Leerzeichen
und raet damit bei jedem Doppelvornamen falsch. Das ist der Preis dafuer,
dass die Trennung ueberhaupt verschwindet.

fullName heisst jetzt riderName und ist nur noch ein Feldzugriff — die
Funktion bleibt als die eine Stelle, an der ein namenloser Fahrer zu
einem leeren String wird statt als undefined durch die Oberflaeche zu
geistern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:10:30 +02:00
Daniel Michelberger
eac2455ee3 chore(schema): team_invites in den Snapshot nachziehen
Damit eine frische Instanz die Collection direkt aus dem Snapshot
bekommt und die Typen aus derselben versionierten Quelle entstehen wie
das Schema.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:10:08 +02:00
Daniel Michelberger
bb0a51378f refactor: Marker und Kommentare in die Kennzahlenzeile der Trailcard
Sie standen in einer eigenen, abgetrennten Zeile unter Laenge und
Hoehenmetern, obwohl sie dasselbe sind: eine Zahl zum Trail. Jetzt eine
Zeile, ganz rechts die offenen Meldungen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:09:41 +02:00
Daniel Michelberger
3ba8ec4c86 feat: Trailstatus in relativer Zeit
Ein Datum muss man erst gegen den heutigen Tag rechnen, um zu wissen, ob
eine Sperrung von gestern stammt oder vom letzten Sommer — der Abstand
sagt es sofort. Das genaue Datum steht daneben im Tooltip und geht
dadurch nicht verloren.

date-fns lag bereits im Projekt, es brauchte keine neue Library.
parseDate nimmt PocketBases "2026-09-12 08:30:00.000Z" mit Leerzeichen
statt T; die meisten Browser schlucken das, garantiert ist es nicht.

formatDate auf der Trail-Seite war eine Kopie von formatDateTime und
kommt jetzt aus $lib/time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:09:41 +02:00
Daniel Michelberger
70c2cdd101 fix: Zeigefinger fuer alles Anklickbare
Tailwind 4 nimmt Knoepfen den Zeigefinger und ueberlaesst sie dem
Browser-Standard — was am Ende ein Pfeil ist, der nichts verspricht.
Eine Basisregel statt einer Klasse an jedem einzelnen Element.
Abgeschaltete Elemente behalten den Pfeil.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:09:30 +02:00
Daniel Michelberger
c817e208cd fix: Kartenkacheln von OpenFreeMap statt CARTO
CARTO liefert die Kacheln inzwischen mit einem eingebrannten
"API KEY REQUIRED" quer ueber der Karte — und zwar mit HTTP 200,
weshalb es wie ein Darstellungsfehler aussah und nicht wie eine Sperre.
Der Schluessel aus PUBLIC_CARTO_API_KEY aenderte daran nichts: Die
Kachel kam byteidentisch zurueck wie ohne ihn, als key=, api_key= und
apikey=.

OpenFreeMap braucht keinen Schluessel und kennt kein Limit; die Stile
heissen dort positron und dark und sind genau die, die CARTO als
Positron und Dark Matter gepraegt hat.

Weil das Vektorkacheln sind, tauscht der Themenwechsel jetzt den ganzen
Stil statt nur der Kachel-URLs. Ein Stilwechsel wirft Quellen und Layer
weg, deshalb legt style.load die Trail-Linie neu an — die Marker nicht,
die haengen als DOM-Elemente an der Karte und nicht am Stil.

PUBLIC_CARTO_API_KEY wird nicht mehr gelesen und kann aus .env und der
Deployment-Konfiguration verschwinden.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:09:30 +02:00
Daniel Michelberger
69588528e8 feat: Ladeanzeige als Mountainbike-Laufrad
Stollenreifen, Felge, gekreuzte Speichen, Nabe — gezeichnet in
currentColor und groessengesteuert wie ein Lucide-Icon, damit es den
LoaderCircle ueberall ersetzen kann.

Das Ventil sitzt bewusst nur an einer Stelle: Ohne diese Unwucht sieht
ein achtfach symmetrisches Rad im Stillstand aus, egal wie schnell es
sich dreht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:09:30 +02:00
Daniel Michelberger
7ac5e9ab4f feat: Einladungslinks, mit denen sich Fahrer selbst ein Konto anlegen
Bisher legte die Teamleitung jedem Fahrer von Hand ein Konto an und gab
das Passwort weiter. Bei zwanzig Leuten in einer WhatsApp-Gruppe ist ein
Link die kuerzere Strecke.

Die oeffentliche Seite der Einladung laeuft ueber eigene Routen in
pb_hooks, nicht ueber die Collection: Ohne Login ist dort nichts
sichtbar, auch nicht mit Token. Der Token kommt vom Server, ein
mitgeschickter wird abgewiesen.

Dazu der Login-Vorspann vor jeder Regel, die @request.auth.id erwaehnt.
Das ist ein Loch und keine Kosmetik: Eine leere Relation ist in
PocketBase gleich dem leeren @request.auth.id einer anonymen Anfrage —
ein Team ohne Admins stuende sonst offen im Netz.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
2026-09-07 22:09:14 +02:00
Daniel Michelberger
83a1693ef1 refactor: Schnellzugriff raus, Event-Kacheln komplett verlinkt
Die Sidebar fuehrt ohnehin ueberall hin, der Schnellzugriff war eine
zweite Navigation ohne Mehrwert.

Die Kacheln unter "Heute und demnaechst" sind jetzt vollflaechig
verlinkt statt ueber einen Button darin -- dasselbe Muster wie in der
Event- und der Trailliste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Ah9PvkL2xB7YkuadnfHRz
2026-09-03 12:40:52 +02:00
Daniel Michelberger
e7892be60b docs: TODO an den zusammengelegten Fahrer-Reiter angepasst
Der Pfad hiess noch "Einstellungen -> Fahrer -> Konto"; der Reiter heisst
seit e3addda "Fahrer & Konten". Dazu zwei Punkte, die beim Zusammenlegen
entstanden sind: dass die Regel "jedes Teammitglied ist ein Fahrer" nur in
der Oberflaeche gilt, und dass "Als Fahrer uebernehmen" den Namen raet.

Migrationsstand erneut gegen die produktive Instanz geprueft - unveraendert
fuenf offen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 18:22:49 +02:00
Daniel Michelberger
e3adddafda refactor: Fahrer und Konten in einem Reiter, Team wird zur Uebersicht
Dieselben Menschen wurden an zwei Stellen gepflegt: Mitglieder unter Team,
Fahrer unter Fahrer - und die Zuordnung dazwischen von Hand. Jedes
Teammitglied ist aber ein Fahrer; nur hat nicht jeder Fahrer ein Konto.

"Fahrer & Konten" fuehrt beides in einer Liste: Kader, daneben wer sich
anmelden kann und mit welcher Rolle. Angelegt wird eine Person als Fahrer,
das Konto ist eine Checkbox im selben Dialog - mit E-Mail, sobald sie
gesetzt ist. Rollen, Entfernen aus dem Team und die Kontosuche wandern
ebenfalls hierher.

Konten im Team ohne Fahrer bekommen eine eigene Zeile samt "Als Fahrer
uebernehmen". Sie verschwaenden sonst aus der Ansicht, obwohl sie Mitglied
sind.

"Team" bleibt als Uebersicht: Kennzahlen des aktiven Teams, Name, Logo,
Wechseln, Verlassen, Loeschen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 16:18:47 +02:00
Daniel Michelberger
b1594d99c4 docs: TODO gegen die produktive Instanz abgeglichen
Sechs der elf Migrationen sind laengst gelaufen - der Backend-Deploy hat
alles bis 1754500900 mitgenommen. Die Datei behauptete weiter, es stehe
alles aus, samt eines Symptoms ("Only superusers can perform this action"),
das es so nicht mehr gibt.

Die Tabelle sagt jetzt je Migration, was gelaufen ist und was nicht, und
benennt, welche Funktionen bis zum naechsten Deploy ins Leere laufen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 16:14:35 +02:00
Daniel Michelberger
fae50d3209 feat: Fahrer werden zum Kader, Events bekommen Teilnehmer und Serien
Damit sind alle offenen Punkte aus TODO.md gebaut.

Fahrer gehoeren ab jetzt dem Team, nicht mehr einem Event. Wer bei einem
Termin dabei ist, steht in event_participants - dort haengt auch die
Startnummer, denn die gilt je Event: Derselbe Fahrer kann in zwei Rennen
zwei Nummern haben und beim Stammtisch gar keine. Die vorhandenen Daten
wandern in der Migration mit.

Events tragen zwei Schalter. timing blendet Runs und Zeitnahme ein - der
woechentliche Stammtisch braucht sie nicht. participation regelt, wie man
dazukommt: offen (jeder traegt sich selbst ein), bestaetigung (Anmeldung,
Ersteller oder Team-Admin bestaetigt) oder geschlossen. Durchgesetzt wird
das serverseitig, nicht nur im Formular.

Serien erzeugen echte Einzeltermine statt einer Wiederholungsregel, aus der
Termine nur gerechnet werden. Nur so laesst sich beantworten, wer am 12.
kommt, und nur so faellt eine einzelne Woche aus. Gerechnet wird auf
Kalendertagen: ueber die Zeitumstellung hinweg kippte ein Termin sonst auf
den Vortag - dafuer gibt es einen Test.

Ausserdem: Teammitglieder duerfen den Zustand eines Trails melden. Die Regel
gibt genau die drei Statusfelder frei und schliesst jedes andere aus; ohne
das haette man Mitgliedern das ganze Objekt oeffnen muessen. Und bestehende
Konten lassen sich wieder per E-Mail ins Team holen, wofuer users.listRule
sich oeffnet.

Die neuen Regeln sind ungetestet - weder Docker noch eine lokale
PocketBase-Instanz standen zur Verfuegung. TODO.md sagt, was nach dem Deploy
durchzuspielen ist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 16:03:17 +02:00
Daniel Michelberger
45a4191f01 refactor: Tooltips ueber tippy statt title-Attribute, appweit
title war fuer Hinweise die falsche Wahl: Es erscheint erst nach einer
Sekunde, sieht auf jedem System anders aus und laesst sich auf Touch-Geraeten
gar nicht aufrufen. Tippy war laengst als Abhaengigkeit da, aber kaum
benutzt.

<Button> nimmt jetzt eine tooltip-Prop, die zugleich als aria-label dient -
ohne das haetten die vielen Icon-Knoepfe beim Wegfall von title ihren Namen
fuer Screenreader verloren. Alle anderen Elemente nutzen die Action direkt.

Die Action selbst konnte bisher nur erzeugen. Sie beherrscht jetzt
Aktualisieren und Aufraeumen: Ein wechselnder Text ("Erledigt" / "Wieder
oeffnen") muss auch im Tooltip wechseln, und eine Instanz, die ihr Element
ueberlebt, haenge als leere Blase im Dokument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 15:45:49 +02:00
Daniel Michelberger
4fa0de8753 feat: Logo pro Team, auch als Wasserzeichen im Seitenhintergrund
teams.logo nimmt eine Datei auf; hochgeladen wird sie in den
Team-Einstellungen. Ohne eigenes Logo bleibt das Wappen aus static/ als
Rueckfalloption - fuer den Stammtisch Hersbruck aendert sich damit nichts,
fuer jedes weitere Team waere es fremdes Vereinswappen gewesen.

Dasselbe Logo liegt gedreht und sehr blass in der rechten unteren Ecke der
Seite. Es haengt fest im Bildschirm, faengt keine Klicks ab und ist fuer
Screenreader nicht vorhanden; im Dunkelmodus etwas kraeftiger, sonst
verschwaende es im dunklen Grund.

In TODO.md ausserdem der Plan fuer Ausfahrten: Sie werden kein zweites
Konzept neben Events, sondern Schalter daran - timing, participation
(offen/bestaetigung/geschlossen) und Serien, die echte Einzeltermine
erzeugen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 15:42:04 +02:00
Daniel Michelberger
fb2652dcbf feat: Trail-Karten zeigen Marker und Kommentare, Icons fuer Laenge und Hoehe
Laenge und Hoehenmeter tragen dieselben Icons wie der Kopf der Trail-Seite.
Dazu kommen Anzahl der Marker und Kommentare - und ein Punkt daneben, wenn
davon etwas aus den letzten 14 Tagen stammt. Die blosse Zahl sagt nichts
darueber, ob sich gerade etwas tut.

Kommentare bekommen dafuer einen eigenen Store. Sie lagen bisher direkt in
der Trail-Seite, was richtig war, solange es sie nur dort gab; fuer die
Liste waere ein zweiter Ladeweg auf dieselben Daten einer zu viel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 15:36:22 +02:00
Daniel Michelberger
27d34ad0d3 feat: Superadmin-Rolle und Standard-Flags fuer neue Teams
Die Rolle superadmin kommt als Feld an users, der erste ist
stammtisch@dne.name. Vergeben wird sie nur in PocketBase: Die updateRule
schliesst das Feld ueber @request.body.superadmin:isset = false aus, sonst
koennte sich jeder selbst befoerdern - jeder darf schliesslich seinen
eigenen Datensatz aendern. Besondere Rechte in der App haengen noch nicht
daran.

Flag-Typen bleiben ausdruecklich Sache der Teams. Ein neues Team bekommt
beim Anlegen den hartkodierten Standardsatz aus der App und kann ihn danach
frei aendern; eine appweite Verwaltung entfaellt damit. Ein Team ohne
Flag-Typen koennte zwar Marker aufnehmen, aber nichts einordnen.

Im Kopf stehen Avatar und Name links, Design-Umschalter und Logout rechts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 15:03:49 +02:00
Daniel Michelberger
561d7cbbc7 docs: Symptom der noch nicht gelaufenen Migrationen in TODO.md benennen
"Only superusers can perform this action" beim Anlegen eines Mitglieds ist
kein Fehler im Frontend, sondern die unveraenderte users.createRule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 13:13:39 +02:00
Daniel Michelberger
27c83946df feat: Erzeugte Avatare und Verfasser an Kommentaren
Ohne hinterlegtes Bild stehen Initialen auf einem Farbton, der sich aus der
User-ID errechnet: Derselbe Mensch bekommt ueberall dasselbe Bild, ohne dass
es irgendwo gespeichert werden muss. Bewusst kein externer Avatar-Dienst -
der wuerde bei jedem Seitenaufruf verraten, wer hier unterwegs ist.

Kommentare zeigen jetzt ihren Verfasser, aber nur als Bild; der Name steht
im Tooltip. In einer schmalen Spalte neben der Karte ist fuer mehr kein
Platz.

Verwendet wird der Avatar ausserdem in der Kopfzeile, im Profil und in der
Mitgliederliste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 13:13:22 +02:00
Daniel Michelberger
fe8e301e65 feat: Marker mit eigenem Typ und frei waehlbarem Icon
Auf dem Trail passt nicht immer ein vorgefertigter Typ - "Wespennest am
Anlieger" legt niemand vorher an. Der Marker-Dialog fuehrt deshalb ein
Dropdown, dessen erste Zeile "Eigener Typ" ist und ein Textfeld oeffnet.

Das Icon steht ab jetzt am Marker selbst. Ein gewaehlter Typ schlaegt seines
vor, ueberschreiben laesst es sich jederzeit: Dieselbe Art Hindernis sieht
nicht immer gleich aus. Der Picker ist deshalb immer sichtbar und wandert
als eigene Komponente auch in die Flag-Verwaltung.

Schema: trail_markers.flag ist nicht mehr Pflicht, dafuer gibt es label und
icon. Bestehende Marker bleiben gueltig - ohne eigene Angaben faellt die
Anzeige auf den Typ zurueck.

Damit braucht ein Trail auch keine Flag-Typen mehr, um ueberhaupt Meldungen
aufnehmen zu koennen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 13:13:22 +02:00
Daniel Michelberger
6b1c57ba17 docs: Offene Punkte und Deploy-Schritte in TODO.md festhalten
Vier Entscheidungen stehen aus (Rollenmodell mit Superadmin, appweite
Flag-Typen, Melderecht fuer den Trail-Status, Kader statt Event-Fahrer) und
drei Dinge passieren ausserhalb des Repos: PUBLIC_CARTO_API_KEY in Coolify,
die drei neuen Migrationen samt Datenverlust bei events.status, und einmal
Typen neu erzeugen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 12:59:57 +02:00
Daniel Michelberger
f8247ae3e7 feat: Trail-Seite neu geordnet, echte dunkle Karte, sichtbare Flag-Icons
Der Kopf traegt jetzt Name, Zustand und Kennzahlen: Der Status sitzt als
farbiges Dropdown direkt hinter dem Namen und nennt darunter, wann und von
wem er gemeldet wurde. Laenge und Hoehenmeter stehen mit Icons daneben.

Karte und Hoehenprofil links, Marker, Kommentare und Versionen rechts
daneben in einer Spalte. Die Reiter entfallen - alles ist gleichzeitig zu
sehen. Versionen sind Archiv und deshalb eingeklappt; der GPX-Upload sitzt
dort statt im Kopf.

Marker lassen sich jetzt auch loeschen und auch ueber das Hoehenprofil
setzen: Die angeklickte Stelle wird auf dieselbe Linie zurueckgerechnet,
die ein Klick auf der Karte trifft.

Die Flag-Icons wurden bisher gespeichert, aber nirgends gezeichnet. Sie
erscheinen jetzt in der Karte, im Popup, in der Markerliste und in der
Verwaltung. Erlaubt ist jeder Name von lucide.dev - moeglich macht das ein
Glob ueber die Icon-Dateien, der jedes Icon zu einem eigenen, erst bei
Bedarf geladenen Chunk macht. Die Liste im Dialog sind nur Vorschlaege.

Die Karte kommt von CARTO statt von OSM direkt: zurueckhaltend gezeichnet,
sodass die Trail-Linie darueber steht, und mit einer echten dunklen
Fassung. Das vorherige Abdunkeln der OSM-Kacheln ergab nur ein dunkleres
Bild, keine dunkle Karte. In der Kachelvorschau der Liste sind Zoom,
Massstab und Quellenangabe abgeschaltet - dort ist die Karte Bild, nicht
Werkzeug; die grosse Karte traegt die Angabe weiterhin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 12:56:17 +02:00
Daniel Michelberger
7c3cf29354 feat: Einstellungen als Reiter, Fahrerverzeichnis, datierte Events
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
2026-09-02 12:56:01 +02:00
Daniel Michelberger
1cd708e0ff feat(schema): Fahrerkonten, Event-Daten und Herkunft des Trail-Status
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
2026-09-02 12:55:47 +02:00
Daniel Michelberger
cc55bd66a5 feat: Ein Dialog fuer die Zeitnahme, keine Aktionen mehr inline
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
2026-09-02 12:55:33 +02:00
Daniel Michelberger
514b5de27b feat: Laufende Zeiten in der Rangliste und als sticky Leiste, Monospace raus
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
2026-09-02 12:13:00 +02:00
Daniel Michelberger
245f8d7924 feat: Zeitnahme und Ergebnisse an Run und Fahrer statt an eigene Seiten
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
2026-09-02 12:08:14 +02:00
Daniel Michelberger
a8049e7406 feat: Marker nur noch auf dem Trail setzen, Karte und Hoehenprofil koppeln
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
2026-09-02 12:08:02 +02:00
Daniel Michelberger
f29361b7f3 fix: E-Mail-Adresse aus dem Dashboard-Header entfernen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
2026-09-02 11:39:56 +02:00
Daniel Michelberger
0432540d5a fix: MapLibre-Worker vollstaendig buendeln, damit die Linie auch live erscheint
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>
2026-08-14 16:51:58 +02:00
Daniel Michelberger
6273f20484 fix: Trail-Linie auf der Karte durch expliziten MapLibre-Worker-Pfad
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>
2026-08-14 16:23:21 +02:00
Daniel Michelberger
0ae0840c29 fix: Deployment-Build durch jsdom-Downgrade auf 29 reparieren
npm ci brach beim Deployment mit EBADENGINE ab: jsdom 30 verlangt
^22.22.2 || ^24.15.0 || >=26.0.0, Nixpacks liefert aber Node 24.10.0.
24.10 ist aelter als 24.15 und faellt damit durch; zusammen mit
engine-strict=true in .npmrc bricht die Installation hart ab.

Anders als bei frueheren Node-Problemen liegt es nicht an unserer eigenen
engines-Angabe (^20.19 || ^22.12 || >=24) — die ist erfuellt. Die
Anforderung kommt allein aus jsdom, und zwar erst seit Version 30; jsdom 29
verlangt nur >=24.0.0. Ein NIXPACKS_NODE_VERSION haette nicht geholfen, weil
Nixpacks nur Major-Versionen aufloest und damit ohnehin bei 24.10 landet.

jsdom ist eine devDependency und wird ausschliesslich als Testumgebung fuer
Vitest genutzt (vite.config.ts). Fuer den Produktionsbuild ist sie
irrelevant, wird aber mitinstalliert, weil Nixpacks NPM_CONFIG_PRODUCTION
auf false setzt.

Verifiziert: npm ci laeuft in einem node:24.10-slim-Container durch, also
auf exakt der Version, an der das Deployment gescheitert ist. Alle 19 Tests
in src/lib/gpx.test.ts bestehen unter jsdom 29.1.1, npm run build erzeugt
das build-Verzeichnis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 15:59:31 +02:00
Daniel Michelberger
e54a2c611a chore: PocketBase auf 0.39.11 aktualisieren
Der Pin stand auf 0.39.6, aktuelles Release ist 0.39.11. Reine Patch-Updates
innerhalb 0.39, keine Schema- oder API-Aenderungen, aber sicherheitsrelevant:
0.39.7 ersetzt ozzo-validation durch einen eigenen Fork, nachdem die
Originalbibliothek den Besitzer gewechselt hat; 0.39.8 und 0.39.11 ziehen
Sicherheitsfixes der golang.org/x-Abhaengigkeiten nach.

Version an beiden aktiven Stellen gesetzt (Dockerfile-ARG und der Default in
docker-compose.yaml). Die Planungsdokumente unter docs/ bleiben unangetastet,
sie halten den Stand ihrer Entstehung fest.

.env.example um den Deployment-Fall ergaenzt: Beim Deployment gibt es keine
.env-Datei, dieselben Variablen werden in Coolify als Environment Variables
gesetzt. Ausserdem der Hinweis, dass SUPERUSER_* nur greift, solange noch kein
Superuser existiert — nachtraegliche Aenderungen wirken nicht mehr.

Verifiziert: Image gebaut, Binary meldet 0.39.11, alle Migrationen laufen
gegen eine frische Datenbank durch, alle elf Collections vorhanden,
Team-Anlage als normaler User erfolgreich.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 15:52:00 +02:00
Daniel Michelberger
dc393502c2 feat: Login mit Logo statt Produktname
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>
2026-08-14 15:45:36 +02:00
Daniel Michelberger
7df0e9a5ba fix: Trail-Migration auf frischer Datenbank lauffähig machen
Auf einer leeren Datenbank brach der Start des Backends ab, damit war die
Anwendung insgesamt nicht benutzbar. Zwei Ursachen, beide nur ohne
bestehende pb_data sichtbar, weil angewendete Migrationen nicht erneut
laufen.

Erstens verwies trail_flags.deleteRule auf @collection.trail_markers,
obwohl trail_markers erst danach angelegt wird. Die verschaerfte Regel war
nachtraeglich in die bereits angewendete Migration eingebaut worden,
zusaetzlich zu den korrekten Folgemigrationen 200/300. Hier steht nun
wieder die referenzfreie Ursprungsregel; den Zielzustand setzen die
Nachtraege.

Zweitens enthaelt 1754400000_init_schema.js als Snapshot des Live-Schemas
die trail-Collections bereits, wodurch diese Migration sie ein zweites Mal
anlegte und am eindeutigen Collection-Namen scheiterte. Ein Guard
ueberspringt sie, wenn trails schon existiert.

Verifiziert gegen ein leeres Datenverzeichnis: alle Migrationen laufen
durch, alle elf Anwendungs-Collections vorhanden, die Nachtraege korrekt
im Endzustand (geklammerte deleteRule, coord_distances, avatar.maxSize),
Team-Anlage als normaler User erfolgreich.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 15:43:14 +02:00
Daniel Michelberger
1257456308 docs: Plan-Korrekturen aus der Umsetzung nachziehen
- {@html} der Trail-Beschreibung mit DOMPurify absichern
- fehlender Key in der Status-Schleife

Beides fiel beim Autofixer-Lauf in Task 8 auf und stand so im Plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 19:30:44 +02:00
Daniel Michelberger
ccc1d931ab fix: Flag-Verwaltung dauerhaft über die Navigation erreichbar machen
/dashboard/settings/flags war nur über einen Link im Leerzustand
verlinkt — sobald ein Team einen Flag-Typ hat, verschwand der einzige
Weg dorthin. Neuer Navigationspunkt "Einstellungen" im Dashboard-Menü.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBMBfip6BAG8VV9raSf6vu
2026-08-06 19:28:54 +02:00
Daniel Michelberger
331c4cfed1 fix: Marker-Aktionen nur für Berechtigte anbieten
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
2026-08-06 19:28:46 +02:00
Daniel Michelberger
7b1df79a37 fix: Höhenprofil-Hervorhebung über Distanz statt Index koppeln
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
2026-08-06 19:28:28 +02:00
Daniel Michelberger
08af521d19 fix: Bestätigungsdialog und Farbvalidierung bei Flag-Verwaltung
- Ersetze native confirm() durch app.confirm.request() mit themingfähigem Dialog
- Erweitere Fehlermeldung beim Löschen mit Hinweis auf Marker-Abhängigkeiten
- Validiere Hex-Farben (#rrggbb) vor dem Speichern
- Konsistenz mit Projektmuster (events, runs, riders, times)
2026-08-06 19:11:38 +02:00
Daniel Michelberger
c44ec4377f feat: Verwaltung der Flag-Typen
Die Flag-Verwaltungsseite ermöglicht Team-Admins und -Besitzern, die Marker-Typen
für Trails zu erstellen, bearbeiten und zu löschen. Einfache Mitglieder sehen die
Flags nur. Hinweistexte informieren über Berechtigungen und ermöglichen Nicht-Admins,
die Benutzeroberfläche zu verstehen.
2026-08-06 19:06:58 +02:00
Daniel Michelberger
8e256d0184 fix: HTML-Beschreibung sanitizen und Key in Status-Schleife ergänzen
DOMPurify filtert trail.description vor {@html}, da nur Paten/Admins das
editor-Feld setzen dürfen, ungefiltert aber Skriptcode bei allen
Team-Mitgliedern ausführen könnte. Der Sanitizer läuft browser-gated, da
DOMPurify serverseitig kein DOM hat. Zusätzlich Key in der
Status-Buttons-Schleife ergänzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:56:03 +02:00
Daniel Michelberger
a45c5d2908 feat: Trail-Detailseite mit Karte, Höhenprofil, Markern und Kommentaren
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:41:53 +02:00
Daniel Michelberger
7bc128601a fix: Karte lazy-loaded, formatKm bei 0, Enter im Dialog
- 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>
2026-08-06 18:34:58 +02:00
Daniel Michelberger
60d5891bc9 feat: Trail-Liste mit Vorschaukarten
Übersichtsseite zeigt alle Trails des Teams als Grid mit Kartvorschau,
Status-Badge und Kennzahlen. Dialog für Trail-Erstellung mit GPX-Upload
im nächsten Schritt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:27:33 +02:00
Daniel Michelberger
a0ccafaa91 fix: Marker beim Aufräumen entfernen, Fokusstil für Marker-Knöpfe 2026-08-06 18:23:53 +02:00
Daniel Michelberger
b1f6f8d1e5 docs: maplibre-gl hat keinen Default-Export — Importzeile im Plan korrigieren 2026-08-06 18:19:32 +02:00