Der Container ging sofort wieder aus, „exited:unhealthy", und im Deploy-Log
stand nichts daneben: Gebaut, gestartet, fertig. Ausgegangen ist er, weil
nginx seine Konfiguration nicht las.
Schuld war ein `sed` im Dockerfile, das `gzip_static on;` vor die erste Zeile
schieben sollte, die mit `}` beginnt. Das ist in der nginx.conf des Bildes
aber nicht das Ende von `http { … }`, sondern das von `events { … }` — und
dort ist die Anweisung ungültig.
Sie steht jetzt im eigenen Server-Block, wo sie hingehört, und das `sed` ist
weg. Eine Zeile, die eine fremde Konfigurationsdatei mit einem Muster
umschreibt, ist ohnehin eine Wette darauf, wie die Datei innen aussieht.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LTw6xVYgjMy9AQtfs1Gdyu
Diese Adresse führte bis September 2026 eine Anwendung: Zeitnahme, Kader,
Trails, Renntage. Die heißt jetzt Kurbeler, liegt unter kurbeler.de und führt
den Stammtisch als eines von mehreren Teams. Was hier bleibt, ist die Adresse
— und die soll niemanden ins Leere laufen lassen.
Übrig sind drei vorgerenderte Seiten: Wappen, ein Satz, ein Knopf, dazu
Impressum und Datenschutz. Kein Konto, keine Datenbank, kein Formular, keine
Cookies, keine Schriften oder Karten von fremden Servern. Die
Datenschutzerklärung ist deshalb kurz und nicht die von Kurbeler mit
Streichungen — eine Erklärung, die von Konten und Benachrichtigungen spricht,
wäre auf dieser Seite schlicht falsch.
`adapter-static` statt `adapter-node`, ausgeliefert von nginx: Für drei Seiten
ohne eine dynamische Angabe einen JavaScript-Prozess laufen zu lassen, der auf
Anfragen wartet, um jedes Mal dieselbe Datei herauszugeben, wäre Beschäftigung
ohne Arbeit. Von den Abhängigkeiten bleiben die zwölf, die zum Bauen nötig
sind; zur Laufzeit keine einzige. Das Bild ist damit 492 kB statt elf
Megabyte — zwei alte Grafiken von je 2,4 MB lagen ungenutzt in `static/`.
Das Backend bleibt unangetastet. Es wird vom Wegweiser nicht benutzt, aber
darin liegen die Daten des alten Deployments.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LTw6xVYgjMy9AQtfs1Gdyu
Ein Event beginnt mit zwanzig Anmeldungen, nicht mit einer. Bisher hiess
das: zwanzigmal denselben Dialog oeffnen, einen Namen aus einer
Auswahlliste suchen, eine Nummer tippen, speichern.
Jetzt steht der Kader als Liste da — Haken und Startnummer nebeneinander,
darueber eine Suche und "Alle"/"Keinen". Der Knopf zaehlt mit, was
ausgewaehlt ist.
Geprueft wird vor dem Schreiben, nicht dabei: doppelte Nummern innerhalb
der Auswahl und gegen die schon vergebenen. Eine Kollision soll nicht
erst auffallen, wenn die halbe Liste eingetragen ist.
Der alte Dialog bleibt, aber nur noch fuers Aendern einer Startnummer —
sein Anlege-Zweig samt Fahrer-Auswahlliste ist entfallen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Das Schema traegt es bereits: times.comment gibt es seit dem ersten
Snapshot und wurde von der Anwendung nie benutzt. Der Grund steht jetzt
dort — optional, aber nach dem Event ist eine Strafe ohne Grund schwer
zu verteidigen. Er haengt im Tooltip an der Korrektur und steht als
eigene Spalte im CSV.
"Strafzeit fuer alle" ist raus. Eine Strafe trifft einen Fahrer; was
fuer alle gleich gilt, ist keine Strafe, sondern eine andere Stage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
bg-destructive ist ein helles Signalrot; auf der hellen Kopfleiste
flimmerte das Symbol mehr als es meldete. red-800 steht ruhiger und
traegt das Weiss besser.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Die Offline-Meldung stand als eigener Streifen unter der Kopfleiste —
eine zweite Statusflaeche neben der, die es dafuer schon gibt. Sie sitzt
jetzt in der Leiste unten rechts, ganz oben und mit fallender
Dringlichkeit darunter: fehlende Verbindung, nicht uebertragene Zeiten,
laufende Uhren. Das Symbol in der Kopfleiste wird offline weiss auf rot.
In der Zeitentabelle ist die ganze Zeile anklickbar statt nur die Spalte
"Zeit" — und nur, solange die Uhr laeuft. Eine gewertete Zeit hat im
Zeit-Dialog nichts zu holen; was an ihr noch zu aendern waere, aendert
die Korrektur nebenan.
Name und Korrektur halten den Klick auf: Der Name fuehrt zum Fahrer,
nicht in den Dialog. Die Zeit bleibt bei laufender Uhr ein echter Knopf,
denn ein <tr> nimmt keinen Fokus — ohne ihn waere die Zeile mit der
Tastatur nicht erreichbar.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Beim Test aufgefallen: Wer im Funkloch stoppte, sah die Zeit weiter als
laufend — der Ausgang hatte den Stopp, der Datensatz nicht. Ein zweiter
Druck legte einen zweiten Stopp daneben, und beim Uebertragen gewann der
letzte. Welche Zeit am Ende zaehlte, war nicht mehr zu sagen.
Drei Dinge dagegen. Ein Stopp im Ausgang traegt sich sofort auch am
Datensatz ein, die Zeit ist damit sichtbar beendet. Ein zweiter Stopp
fuer dieselbe Zeit wird abgewiesen, mit Ansage statt still. Und ist der
Ausgang leer, laedt der Store einmal neu und tauscht die vorlaeufigen
Zeiten gegen die echten samt ihren IDs.
Dabei fiel die groessere Luecke auf: Ein offline gestarteter Lauf stand
nirgends und liess sich deshalb gar nicht stoppen — die Oberflaeche
kennt nur, was im Store steht. Er bekommt jetzt einen vorlaeufigen
Datensatz mit einer local:-ID, laeuft sichtbar mit und laesst sich
stoppen, bevor der Server ueberhaupt von ihm weiss.
Dazu der Verbindungswaechter: /api/health im Takt, Symbol in der
Kopfleiste und eine Meldung, die stehen bleibt, bis der Server wieder
antwortet. navigator.onLine allein taugt dafuer nicht — im WLAN ohne
Uplink und hinter einem Portal steht es unbeirrt auf true. Kehrt die
Verbindung zurueck, geht der Ausgang von selbst raus.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Wer auf eine laufende Uhr sieht, will sie stoppen; wer auf eine
gestoppte sieht, will sie richtigen. Der Weg ueber den Knopf oben und
die Fahrerauswahl fuehrte zur selben Stelle, nur laenger — und verlangte
die Auswahl eines Fahrers, der in der Zeile laengst dranstand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
startedBy und stoppedBy standen laengst im Datensatz und wurden bei
jedem Start und Stopp geschrieben — nur sah sie niemand. Bei einer
strittigen Messung ist das die erste Frage, also steht es jetzt unter
dem Namen an der Zeit.
Und die Korrektur: times.correction gab es, sie wird auf die Dauer
gerechnet und im CSV exportiert, aber setzen konnte sie kein Mensch.
Jetzt zwei Wege dorthin — an der einzelnen Zeit ueber die Spalte, die
schon da war, und ueber alle gewerteten Zeiten der Stage auf einmal.
Der Wert wird gesetzt und nicht addiert. Zweimal denselben Dialog
abzuschicken darf keine doppelte Strafe ergeben.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Ohne Service Worker war "offline" nur so lange wahr, wie der Tab offen
blieb: Ein Neuladen an der Stage endete in einer weissen Seite — samt
den Zeiten, die noch im Ausgang lagen.
Drei Sorten Anfragen, drei Strategien. Was zum Build gehoert, liegt
unter seiner Versions-URL und aendert sich nie: Cache zuerst.
Seitenaufrufe und lesende Anfragen an PocketBase: Netz zuerst, Antwort
merken, ohne Netz die zuletzt gesehene Fassung — nur so steht nach einem
Neuladen im Funkloch ueberhaupt ein Kader da, gegen den sich stoppen
laesst.
Schreibende Anfragen werden nie aus dem Cache beantwortet: Ein Start,
der aus dem Cache "gelingt", waere eine Zeit, die es nicht gibt. Dafuer
ist der Ausgang zustaendig. Und /api/clock ist ausgenommen — eine
gemerkte Uhrzeit ist das Gegenteil von dem, wozu die Route da ist.
Beim Abmelden raeumt der Worker seinen API-Vorrat weg. Sonst stuenden
auf einem geteilten Geraet die Daten des vorigen Nutzers offline weiter
da.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Eine Stage laesst sich nicht wiederholen. Zwei Dinge durften deshalb
nicht so bleiben, wie sie waren.
Erstens die Uhr. Zeiten entstehen auf den Geraeten an der Strecke — nur
so laesst sich im Funkloch ueberhaupt stoppen. Damit steckte aber der
Versatz zweier Telefonuhren in jeder Dauer, bei der einer am Start und
ein anderer im Ziel drueckt. Jedes Geraet gleicht sich jetzt gegen den
Server ab (Cristian: t0 merken, Serverzeit holen, t1 merken, Versatz =
T + RTT/2 - t1), sieben Proben, Median ueber die schnellste Haelfte. Der
Versatz liegt im localStorage und wird auf jeden Zeitstempel gerechnet.
Dafuer eine eigene Route /api/clock in den Hooks: Der Date-Header jeder
gewoehnlichen Antwort hat nach RFC 9110 Sekundenaufloesung, allein
daraus folgte ein Fehler von bis zu einer halben Sekunde — auf einer
Stage der Unterschied zwischen Platz eins und Platz drei.
Zweitens das Netz. Scheitert die Uebertragung eines Starts oder Stopps
an einem Funkloch, wandert er in einen Ausgang im localStorage statt in
einen Fehler und geht raus, sobald wieder Empfang da ist. Die
massgebliche Zeit ist die des Druckens, nicht die der Uebertragung. Ein
Stopp kann dabei auf einen Start warten, der selbst noch aussteht.
Unterschieden wird streng zwischen Funkloch und Absage: Eine abgelehnte
Anfrage — fehlende Rechte, ungueltige Daten — wird gemeldet und nicht
aufgefangen, sonst sammelte der Ausgang stumm Eintraege, die auch beim
naechsten Versuch scheitern.
Die Leiste der laufenden Zeiten zeigt nicht uebertragene Zeiten ganz
oben und erscheint dafuer auch dann, wenn gerade nichts laeuft.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Die Leiste der laufenden Zeiten steht jetzt offen: Wer an der Strecke
steht, will sie sehen und nicht erst danach fragen. Und die ganze Zeile
ist der Knopf, nicht ein Icon an ihrem Rand — ein 16-px-Ziel neben einer
fingerbreiten Zeile ist eine Einladung zum Danebentippen. Der Name der
Stage verlinkt dafuer nicht mehr auf ihre Seite: ein Link im Knopf waere
weder gueltiges HTML noch eindeutig zu treffen.
Im Zeit-Dialog steht der Fahrer als Kachel statt in einer Auswahlliste —
die Startnummer gross wie am Trikot (font-startnumber: Impact, mit
Haettenschweiler und Arial Narrow Bold als Ersatz), der Name darunter.
Eine Liste verlangt aufklappen, zielen und treffen; drei Schritte fuer
etwas, das ein Zeitnehmer zwanzigmal am Tag macht.
Starten und Stoppen sind Flaechen von 96 px Hoehe, Symbol ueber Wort,
statt einer Zeile im Fussbereich. DNF steht daneben, gleich hoch, damit
es sich treffen laesst, aber schmaler, damit es nicht versehentlich
getroffen wird.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Icon-Auswahl: Das Raster ist jetzt die Auswahl. Daneben stand ein
Freitextfeld fuer jeden Namen von lucide.dev samt Anleitung. Das
verlangte, einen Katalog von tausend Icons zu kennen und den Namen
fehlerfrei abzutippen, und belohnte den Tippfehler mit einem leeren
Kaestchen. Fehlt ein Icon, gehoert es in FLAG_ICON_SUGGESTIONS.
Hoehenprofil in Weiss zu 90 % statt in Blau.
Profil: kein Speichern-Knopf mehr. Es sind zwei Felder, beide gehoeren
nur dem Kontoinhaber — der Name wird uebernommen, sobald das Feld den
Fokus verliert oder Enter kommt, das Bild, sobald eines gewaehlt ist.
Der Satz zur E-Mail-Adresse faellt weg: Das Feld ist abgeschaltet, das
sagt schon alles, was er erklaeren wollte.
Der Passwortwechsel steckt hinter einem Knopf in einem Dialog. Er ist
die Ausnahme; drei Felder dafuer standen dauerhaft offen auf der Seite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
"Höchstens einlösbar" war kein Satz und kein Begriff, sondern beides
halb. Aus den Feldern wird "Gültigkeit in Tagen" und "Anzahl der
Einlösungen", aus den Hinweisen darunter ganze Saetze statt
Gleichungen ("0 = beliebig oft").
Auch der Hinweis an jedem Link liest sich jetzt wie Sprache: "unbegrenzt
gueltig" statt "ohne Ablauf", "noch nicht eingeloest" statt "0x
genutzt".
Und der Knopf "Logo" sagt jetzt, was er tut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Der Link lag in einem einzeiligen Feld. So etwas bricht nicht um, es
scrollt — im Dialog sah man ein Stueck aus der Mitte und musste raten,
ob der richtige Link kopiert wird.
Jetzt steht er in voller Laenge auf einer eigenen Zeile, darunter der
Hinweis und die beiden Knoepfe. break-all statt break-words, weil eine
URL keine Leerzeichen hat, an denen sich trennen liesse.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
<header> und <main> waren beide `relative z-10`. Bei gleichem z-index
gewinnt, was spaeter im DOM steht — also <main>. Das aufgeklappte
Team-Panel haengt unter die Kopfleiste hinein in diese Flaeche und lag
dadurch dahinter: Beim Hinunterfahren traf der Zeiger <main> statt das
Panel, die Komponente sah ein Verlassen und klappte nach delayDuration
zu, bevor ein Eintrag zu treffen war.
Ein z-50 am Panel half nicht — die Kopfleiste ist mit ihrem eigenen
z-index ein Stapelkontext, aus dem kein Kind herausragt. Also die
Kopfleiste selbst hoeher.
Nachgestellt und A/B geprueft: mit z-10 schliesst das Menue beim
Hinunterfahren, mit z-20 bleibt es offen und der Eintrag laesst sich
anklicken.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Zwei Fehler, einer je Anlauf.
Der Inhalt gehoert ohne Viewport gerendert: Dann haengt er am
NavigationMenu.Item, das ohnehin `relative` ist, und steht damit unter
"Team". Mit Viewport ist er eine eigene Flaeche unter der ganzen Leiste
und beginnt an deren linkem Rand — unter "Dashboard".
Und der Standardabstand mt-1.5 war ein toter Streifen zwischen
Menuepunkt und Panel. Wer den Zeiger hinunterbewegte, verliess beides
zugleich; nach delayDuration klappte das Menue zu, bevor ein Eintrag zu
treffen war. Ein Hover-Menue darf keine Luecke haben.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Mit viewport={false} haengt der Inhalt per top-full/left-0 am naechsten
positionierten Vorfahren, und das ist die Leiste selbst und nicht der
Menuepunkt. Das Panel stand dadurch am linken Rand statt unter "Team":
Der Zeiger lief auf dem Weg dorthin ins Leere, und nach delayDuration
klappte das Menue zu, bevor jemand einen Eintrag treffen konnte.
Jetzt mit Viewport, so wie die Komponente gedacht ist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Das Team-Submenue stand auf einem DropdownMenu, und alles, was ein
Menuepunkt braucht und ein Dropdown nicht kann, stand hier als
Handarbeit daneben: ein Zeitgeber fuers Aufklappen, ueberschriebene
Zeigerhandler gegen das Flackern beim Klick, unterdrueckter Fokus gegen
den Ring nach dem Zeigerbesuch.
NavigationMenu kann das von sich aus. Es hat openOnHover samt
delayDuration, es haelt seinen eigenen onclick zurueck, wenn per Zeiger
aufgeklappt wurde ("if opened via pointer move, we prevent the click
event"), es faengt keinen Fokus und sperrt kein Scrollen. Der Trigger
bleibt ueber das child-Snippet ein Link auf die Teamseite.
viewport={false}: Das Panel haengt unter seinem Menuepunkt, statt als
animierte Flaeche ueber die ganze Leiste zu fahren. Bei einem einzigen
aufklappbaren Punkt waere das grosses Kino ohne Stueck.
Das mobile Menue bekommt die Teams stattdessen eingerueckt und
aufgeklappt untergestellt. Auf dem Touchscreen gibt es kein
Ueberfahren — mit dem Menue allein waere seit dem Wegfall der Liste
"Meine Teams" gar kein Wechsel mehr moeglich gewesen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
bits-ui gibt den Fokus beim Schliessen an den Trigger zurueck. Bei einem
Menue, das per Klick aufgeht, ist das richtig — hier schliesst der
Zeiger, und zurueck blieb ein Fokusring auf einem Menuepunkt, ueber den
jemand nur gefahren ist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
"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
Bis zuletzt loeschte die Anwendung einen Fahrer, ohne sein Konto aus
teams.users zu nehmen. Zurueck blieb ein Konto ohne Fahrer — im Kader
eine Zeile, aus der kein Weg zurueckfuehrte: Ein bestehendes Konto
nachtraeglich an einen Fahrer zu haengen gibt es bewusst nicht, weil
dieselbe Person sonst zweimal in der Liste stehen koennte.
riders.remove nimmt die Mitgliedschaft inzwischen mit; diese Migration
raeumt auf, was vorher liegengeblieben ist, und stellt die Regel her,
auf die sich die Oberflaeche verlassen darf: Wer im Team ist, steht im
Kader.
Kein Rueckweg: Die hier angelegten Fahrer sind hinterher nicht mehr von
denen zu unterscheiden, die die Teamleitung selbst angelegt hat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
Das Bild steht links ueber Name und Adresse und ist zugleich der Knopf:
Das Dateifeld liegt unsichtbar darunter, angeklickt wird das Bild. Ein
Label "Bild" ueber einem Dateifeld sagte nichts, was das Bild nicht
schon zeigt. Dazu eine Vorschau der Wahl — ohne sie bliebe sie
unsichtbar, weil es keinen Dateinamen mehr zu lesen gibt.
Loeschen des eigenen Kontos erlaubt users.deleteRule laengst
(id = @request.auth.id); es fehlte nur der Weg dorthin. Die Fahrer
bleiben stehen: riders.user haengt ohne Kaskade daran, also behaelt das
Team Namen, Zeiten und Teilnahmen und die Person verliert nur ihr Login.
Wer ein Team besitzt, kommt nicht durch. teams.owner ist ein Pflichtfeld
ohne Kaskade; PocketBase muesste es beim Loeschen leeren und braeche
dabei den Datensatz. Der Versuch endete in einem Fehler, den niemand
deuten kann — deshalb steht die Bedingung vorne und im Klartext.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
"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
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
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
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
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
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