Release 1.0.0 - #336
Merged
Merged
Release 1.0.0#336
Conversation
Bumps the nextjs group with 1 update: [next](https://github.com/vercel/next.js). Updates `next` from 16.3.3 to 16.3.4 - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](vercel/next.js@v16.3.3...v16.3.4) --- updated-dependencies: - dependency-name: next dependency-version: 16.3.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nextjs ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the react group with 4 updates: [react](https://github.com/react/react/tree/HEAD/packages/react), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). Updates `react` from 19.2.8 to 19.3.0 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.3.0/packages/react) Updates `@types/react` from 19.2.18 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `react-dom` from 19.2.8 to 19.3.0 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.3.0/packages/react-dom) Updates `@types/react-dom` from 19.2.7 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `@types/react` from 19.2.18 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `@types/react-dom` from 19.2.7 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) --- updated-dependencies: - dependency-name: react dependency-version: 19.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: react - dependency-name: "@types/react" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: react - dependency-name: react-dom dependency-version: 19.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: react - dependency-name: "@types/react-dom" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: react - dependency-name: "@types/react" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: react - dependency-name: "@types/react-dom" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: react ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the dev-tools group with 3 updates: [@eslint/eslintrc](https://github.com/eslint/eslintrc), [eslint](https://github.com/eslint/eslint) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `@eslint/eslintrc` from 3.3.6 to 3.3.7 - [Release notes](https://github.com/eslint/eslintrc/releases) - [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md) - [Commits](eslint/eslintrc@eslintrc-v3.3.6...eslintrc-v3.3.7) Updates `eslint` from 10.8.1 to 10.10.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.8.1...v10.10.0) Updates `typescript-eslint` from 8.67.0 to 8.70.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@eslint/eslintrc" dependency-version: 3.3.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-tools - dependency-name: eslint dependency-version: 10.10.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-tools - dependency-name: typescript-eslint dependency-version: 8.70.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-tools ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [zod](https://github.com/colinhacks/zod) from 4.4.3 to 4.6.2. - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](colinhacks/zod@v4.4.3...v4.6.2) --- updated-dependencies: - dependency-name: zod dependency-version: 4.6.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 26.2.0 to 26.5.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.5.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
…buchung beim Bearbeiten Eine ausgebuchte Preiskategorie ließ die Anmeldung mit "Die Preisoption … ist ausgebucht." scheitern — auch bei Kursen mit Warteliste. Sie gilt jetzt wie ein voller Kurs: mit Warteliste kommt die ganze Anmeldung darauf, ohne wird sie weiterhin abgelehnt. "Automatisch" bei der Erfassung durch das Team entscheidet genauso. findFullPriceTier liefert dafür die volle Kategorie, statt zu werfen; assertPriceTierCapacity baut darauf auf. updateMyRegistration prüfte die Kurskapazität nur bei Kursen ohne Warteliste. Mit Warteliste konnte eine bestätigte Anmeldung Teilnehmer hinzufügen, blieb bestätigt und überbuchte den Kurs. Die Prüfung gilt jetzt unabhängig von der Warteliste, allerdings nur für bestätigte Anmeldungen und nur für das, was die Änderung dazu belegt: in einem bewusst überbuchten Kurs lässt sich weiter die Telefonnummer ändern oder ein Teilnehmer abmelden. Anmeldungen auf der Warteliste belegen keine Plätze und werden nicht mehr geprüft — wer wegen einer vollen Kategorie dort steht, konnte sonst gar nichts mehr ändern. Die Bearbeitungsseite hält es genauso.
Der Wartelisten-Hinweis erschien nur bei einem komplett vollen Kurs. Bei 2 freien Plätzen und 4 Teilnehmern gab es keinen Hinweis, und der Toast meldete "Ihre Anmeldung war erfolgreich.", obwohl der Server die ganze Anmeldung auf die Warteliste gesetzt hatte — nur die Mail stimmte. Schritt 3 warnt jetzt, sobald die Teilnehmer nicht in die freien Plätze des Kurses oder einer Preiskategorie passen: mit Warteliste, dass die Anmeldung nicht aufgeteilt wird und alle gemeinsam nachrücken, ohne Warteliste mit der Bitte, die Anzahl zu reduzieren. Die Anzahlung ist dann als "nach Platzbestätigung" ausgewiesen. Der Toast richtet sich nach dem Status, den der Server vergeben hat. Die Team-Erfassung zählt eine volle Kategorie ebenfalls als fehlende Plätze und fragt dann nach der Überbuchung.
Reichten die freien Plätze nicht für alle Teilnehmer, kam die ganze Anmeldung auf die Warteliste – auch wenn ein Teil der Gruppe noch Platz gehabt hätte. create und createByStaff nehmen jetzt confirmedParticipantIndexes entgegen: die Gewählten werden eine bestätigte Anmeldung, die übrigen eine eigene auf der Warteliste. Beide tragen dieselbe registrationGroupId, weil Kapazität, Rechnungen und Anzahlung an der Anmeldung hängen, nicht am Teilnehmer. Aufgeteilt wird nur, wenn der Kurs eine Warteliste führt und nicht ohnehin alle Platz haben; beim Team nur ohne ausdrücklichen Status. Die Auswahl wird in der Transaktion noch einmal gegen Kurs und Preiskategorien geprüft. Passt sie inzwischen nicht mehr, schlägt das Absenden fehl, statt still anders aufzuteilen. Der Geschwisterkindrabatt wird über alle nicht stornierten Teile berechnet und jedem Teil der Anteil seiner Teilnehmer zugerechnet – getrennte Geschwister verlieren ihn nicht, auch nicht beim Bearbeiten oder nachträglichen Gewähren. Genehmigen, Ablehnen und "zum vollen Preis bestätigen" gelten für alle Teile, mit einer Mail über die Summe. confirmAtFullPrice setzt den Status nicht mehr auf bestätigt; eine wartende Anmeldung hätte den Kurs damit überbucht. Die Anzahlung gilt je Teil und ist erst mit der Platzbestätigung fällig. Eine "bereits eingegangene" Anzahlung verbucht das Team beim bestätigten Teil. Statt zweier widersprüchlicher Mails geht eine gemeinsame hinaus, mit Preis, Anzahlung und Link je Teil; das Kursteam bekommt eine Benachrichtigung "teilweise Warteliste". getById liefert die übrigen Teile als groupParts, bei Zugriff über einen Zugangslink mit eigenem Token. Migration 20260915120000_registration_groups: nur eine neue Spalte samt Index.
Reichen die Plätze nicht für alle, bietet Schritt 3 die Wahl: ganze Anmeldung auf die Warteliste (wie bisher) oder die freien Plätze jetzt nutzen. Dann wählen die Anmeldenden, wer sie bekommt – vorbelegt in Listenreihenfolge, ohne Teilnehmer, deren Kategorie voll ist. Teilnehmer, die nicht mehr passen würden, sind nicht wählbar; eine leere Auswahl sperrt das Absenden. Werden Geschwister getrennt, sagt der Hinweis, dass der Rabatt erhalten bleibt. Die Wahl steht vor Preis und Anzahlung, weil beide ihr folgen: der Anzahlungsblock gilt aufgeteilt den bestätigten Teilnehmern und nennt die spätere Anzahlung der wartenden. Die Team-Erfassung bekommt dafür den Status "Aufteilen". Nach Änderungen an der Teilnehmerliste beginnt die Auswahl neu. Waren die Plätze beim Absenden schon vergeben, lädt das Formular sie neu. Der Toast nennt, wie viele bestätigt sind und wie viele warten.
Die Detailseite einer Anmeldung nennt die übrigen Teile mit Status, Teilnehmern und Link – über einen Zugangslink mit dessen Token, damit auch Anmeldende ohne Konto hinüberkommen. "Meine Anmeldungen" und die Teilnehmerliste im Dashboard kennzeichnen geteilte Anmeldungen, die Detailseite im Dashboard verlinkt die weiteren Teile.
… reichen Das Nachrücken bestätigte nur ganze Anmeldungen und blieb an der ersten stehen, die nicht passte – auch wenn ein Platz frei war und die Familie mit drei Kindern zwei davon gern genommen hätte. Werden Plätze frei, geht die Warteliste weiter der Reihe nach durch: - Passt eine Anmeldung ganz, wird sie bestätigt, wie bisher. - Passt nur ein Teil, bekommt sie ein Nachrück-Angebot: die Anmeldenden wählen, wer nachrückt, oder lehnen ab. Bis dahin hält die Warteliste an, höchstens sieben Tage und nie über den Kursbeginn hinaus. - Abgelehnt oder verfallen: die Plätze gehen an die Nächsten, die Anmeldung behält ihren Platz. promotionOfferPassedSeats merkt sich, bei wie vielen nutzbaren Plätzen – erst wenn mehr frei werden, kommt ein neues Angebot. - Passt niemand (etwa volle Kategorie), bleibt die Warteliste stehen, wie bisher. Annehmen (acceptPromotionOffer) teilt die Anmeldung wie beim Aufteilen während der Anmeldung: die Gewählten werden eine bestätigte Anmeldung derselben Gruppe, die übrigen warten auf ihrem Platz weiter. Preise und Geschwisterkindrabatt werden über alle Teile der Gruppe neu berechnet, die Anzahlung je Teil; eine schon eingegangene Anzahlung geht mit zum bestätigten Teil. Wer weniger wählt, als Plätze frei sind, gibt den Rest an die Nächsten weiter. Antworten dürfen die Anmeldenden (auch über den Zugangslink) und das Kursteam; das Team erfährt die Antwort in der App. Plätze zählt das Nachrücken jetzt nach Preiskategorie-id statt nach Label (loadSeatAvailability), wie die übrigen Prüfungen – Teil von #315. Abgelaufene Angebote schließt jeder Lauf selbst, bevor er entscheidet. updateStatus räumt ein Angebot, sobald eine Anmeldung nicht mehr wartet. getById liefert ein laufendes Angebot mit den nutzbaren Plätzen. Mails: Angebot (mit Frist und Link), Angebot abgelaufen, Erinnerung ans Kursteam. Migration 20260915140000_waitlist_promotion_offers: drei neue Spalten samt Index.
POST /api/cron/waitlist-offers stößt für jeden Kurs mit abgelaufenem Angebot das Nachrücken an – das schließt das Angebot, gibt die Plätze an die Nächsten und verschickt die Mails – und erinnert das Kursteam einmal, sobald ein Angebot in höchstens zwei Tagen abläuft. Die Route läuft auch ohne SMTP: das Weitergeben der Plätze darf nicht am Mailversand hängen; offene Erinnerungen gehen dann beim nächsten Lauf. Auf mittwald braucht es dafür einen mStudio-Cronjob mit scripts/trigger-waitlist-offers.mjs (Vorschlag: stündlich), eingetragen in der Deployment-Checkliste. Das Dockerfile kopiert das Skript ins Image – und jetzt auch trigger-newsletter-cleanup.mjs, das laut Checkliste dort liegen sollte, aber fehlte.
Läuft ein Angebot, zeigt die Detailseite der Anmeldung die Frist und
dieselbe Auswahl wie beim Aufteilen während der Anmeldung: vorbelegt,
wer in die freien Plätze passt, alle übrigen nicht wählbar. Dazu
"Nachrücken lassen" und "Angebot ablehnen" mit Rückfrage. Sind
inzwischen genug Plätze für alle frei, rücken alle nach. Waren die
Plätze beim Absenden schon vergeben, lädt die Seite sie neu.
Der Hinweis zu den fehlenden Plätzen spricht dort von der wartenden
Anmeldung ("nicht genug für alle 3 Teilnehmer") statt vom Anmelden.
"Meine Anmeldungen" kennzeichnet ein laufendes Angebot mit
"Nachrücken möglich", die Detailseite im Dashboard mit der Frist.
…rien prüfen updateStatus prüfte beim Bestätigen einer wartenden Anmeldung nur die Kurskapazität. Das Kursteam konnte so eine Anmeldung in eine volle Preiskategorie bestätigen – etwa die Wasserburg-Einzelzimmer, während der Kurs insgesamt noch Plätze hatte. Die Kategorien werden jetzt mit findFullPriceTier geprüft, nach id wie in allen übrigen Prüfungen; Teilnehmer aus der Zeit vor der id-Migration über ein eindeutiges Label. Das Nachrücken zählt seit den vorigen Commits ebenso. Closes #315
Archivo mit Breitenachse als Variable für die öffentliche Website (das Dashboard bleibt bei Inter). Neue Farb-Tokens (Tinte, Papier, Messing-Tinte, Haarlinie, Nachtdruck), schmale Schnitte, Satzbreite, Füllzeile, Fokus- und Markierungsfarben sowie reduzierte Bewegung.
Gleiche Links, Texte und Logik; neue Darstellung ohne Rundungen und Schatten, 44px-Bedienflächen, Menüs mit Tintenrahmen, Tastaturfokus für die Aufklappmenüs. Banner mit lesbarem Kontrast (Tinte auf Orange).
Titelblatt mit Bildstrecke (anhaltbar, ohne Autoplay bei reduzierter Bewegung), „Kommende Termine“ als Programmliste mit offenen Anmeldungen vorn (oranges Datumsfeld, Frist, Anmelden), Aktuelles als Druckspalten mit Logo ohne Titelbild, Register „Beliebte Seiten & Partner“ und Schlussseite auf Papier. Inhalte und Texte bleiben unverändert.
PRODUCT.md hält Zielgruppen, Ziele, Markenvorgaben und belegte Inhalte fest; DESIGN.md beschreibt das Programmheft-Designsystem der öffentlichen Website (Tokens, Typografie, Regeln, Komponenten) als Grundlage für den Rollout auf die übrigen Seiten.
„Lust auf Posaunenchor?“ steht als bewusster Blickfang auf voller oranger Fläche mit der Schaltfläche „Mehr erfahren“. DESIGN.md beschreibt den Schlussaufruf als eigene Komponente und passt Do/Don't daran an.
…itive Die Startseite setzt sich jetzt aus wiederverwendbaren Programmheft- Bausteinen zusammen: Köpfe (Heading, SectionHead, ArrowLink), Abschnitt (PageSection), Wegzeilen und Registerzeilen (WayList, WayRow), Programm- zeilen mit Datumsslot und offener Anmeldung (ProgrammeList, ProgrammeRow, DateSlot), Bezirksmarke, Beitragsspalten (NewsColumns), Schaltflächen (ButtonLink) und Schlussaufruf (ClosingCall). Die Startseite rendert unverändert: alle Blöcke stehen pixelgenau an derselben Position. Neu im CSS sind die blaue Zeilenfüllung für Förderverein-Zeilen und der Fließtext-Link `.link-ink`.
PublicPage setzt Innenseiten jetzt im Programmheft: ein Seitenkopf auf Papier (PageHead) mit Brotkrumen, genau einem <h1>, Satzstrich und Leitsatz; die klebende zweite Titelleiste (PageHeader) entfällt. Die Seite steht vollständig in Archivo, auch Abschnitte, die noch nicht übertragen sind. - `color` entfällt: Bezirksfarben färben keine Köpfe mehr, der Förderverein setzt `tone="foerderverein"` (blauer Satzstrich). - Öffentliche Varianten statt ui/card, ui/badge, ui/alert, die dem Dashboard bleiben: Panel, Tag, Note. - PersonRow/PersonList ersetzen PeopleCard (Förderverein, Posaunenrat). - Wegzeilen ersetzen ParticipationCard auf Über uns, Spiele, Materialien. - Kopfzeilen der Kurs-, Termin-, Anmelde- und Ensembleseiten mit Etiketten und Bezirksmarke statt weißer Pillen; der negative Abstand unter dem alten Kopf entfällt. - Der Ladezustand von /termine nutzt denselben Seitenkopf.
Alle Texte bleiben, nur die Darstellung wechselt: Einstiegsmöglichkeiten als Registerzeilen, Förderverein mit blauem Satzstrich, Fakten in Haarlinienspalten und blau füllenden Wegzeilen, „Warum Posaunenchor?“ als Merkpunkte, Mitgliedsbeiträge im Tabellensatz, Downloads als Wegzeilen, Ehrungen und Versicherungsfrist als Hinweise, der Schadensfall als Kasten mit Unterlagenliste. „Noch Fragen?“ schließt die Seite als oranger Schlussaufruf; der Newsletter steht deshalb davor. Neue Bausteine: PointList (Merkpunkte) und ValueTable (Beträge). ParticipationCard und SectionHeader werden nicht mehr gebraucht.
- Beta-Etikett ohne Sperrung und Versalien (Tag); Banner-Varianten Warnung und Wartung als Tintenfläche, das ungenutzte „success“ entfällt. - themeColor im Dunkelmodus auf den Nachtgrund #141517. - Ungenutzte animate-homepage-*-Animationen entfernt. - hero-carousel.tsx und upcoming-courses-carousel.tsx gelöscht.
Kurs-, Termin- und Anmeldeseite zeigten die Datenbankwerte (LEHRGANG, KONZERT) statt der Bezeichnungen, die auch das Programm der Startseite nutzt.
…tmachen - Förderverein-Abschnitt als volle Druckfläche in Fördervereinsblau (PageSection surface="foerderverein", `.print-field`: Tinte auch im Nachtdruck, Zeilen füllen sich papierweiß). - Fotofeld neben „Warum Posaunenchor?“ mit Bildnachweis; bis eigene Fotos gepflegt sind, aus der Bildstrecke der Startseite. - Ein Strichsystem: der Seitenkopf schließt vollbreit wie die Abschnitte (Nacht: Nachtlinie). - Längere Vorspanne stehen unter dem Titel statt in der schmalen Spalte. - Kasten im Nachtdruck gleich gebaut; Ehrungen ohne zweiten Kasten. - Etiketten sind nie umrandet: Art, Dauer, Ort und Bezirk im kompakten Kopf als Meta-Text, der Outline-Ton von Tag entfällt. - Mitmachen: 5/7-Raster, Tabelle und Hinweis über die volle Spalte, bewusster Umbruch „Einstiegs-“.
Auf Wunsch des Eigentümers alternieren die Köpfe ab 64rem wie linke und rechte Heftseiten (neuer Baustein `Split`, 4/12 Kopf neben 8/12 Inhalt, Kopf im DOM immer zuerst). Das Raster kehrt damit zu 4/8 zurück; das Fotofeld neben „Warum Posaunenchor?“ entfällt.
DESIGN.md beschreibt jetzt, was diese Sitzung gebaut hat: Seitenkopf der Innenseiten (normal und kompakt), Abschnitt mit Split und blauer Druckfläche, Merkpunkte, Beträge, Personenzeile, Etikett, Hinweis, Kasten, wiederverwendbarer Schlussaufruf, Banner-Varianten, Schaltflächen auf Papier und Textlinks im Fließtext. Neu sind die One Field Rule (höchstens eine blaue Förderverein-Fläche neben Titelblatt und Schlussaufruf) sowie Do's und Don'ts zu Köpfen, Etiketten und Kästen. Der veraltete Hinweis auf die alten Bausteine ist ersetzt.
Die Einstiegsseite richtet sich an Neue: Jungbläserarbeit rückt nach vorn, „Warum Posaunenchor?“ steht vor dem Förderverein, Anrede durchgehend „du“, aktuelle Förderverein-Angaben. Mitgliedschaft, Beiträge, Ehrungen und Instrumentenversicherung stehen wörtlich unverändert auf /mitmachen/mitgliedschaft (Anrede „Sie“), verlinkt aus Abschnitt, Menü und Fußzeile und aufgenommen in Sitemap und Suche.
Geschwister-Ermäßigung sind 20 % der Lehrgangskosten ab dem zweiten Kind, die Geschenk-CD gibt es für alle Neumitglieder. Die abgelaufenen Sonderaktionen 2025 samt Verlosung entfallen.
Beitragsliste und ähnliche Beiträge als Druckspalten mit Haarlinien statt Karten, Filterleiste flach, Beiträge ohne Titelbild zeigen das Logo auf Tinte. Beitragsseite mit ruhigem Kopf, Meta-Zeile und Bildfeld im Text; Lightbox ohne Rundungen und Weichzeichner. Suche erstmals mit Seitenrahmen und nach Art gruppierten Trefferzeilen. Newsletter-Seiten als Innenseiten mit flachen Eingabefeldern. Texte, Filter, RSS und Double-Opt-in unverändert.
…gung fix(anmeldung): Zahlungs-Knöpfe in Safari, Vorbelegung freier Kategorie
…ingaben 'Mitglied seit' wird nicht gebraucht: Das Feld ist aus Anlegen, Bearbeiten, Detailseite und Liste entfernt. Die Spalte in der Datenbank bleibt, Bearbeiten schickt den Wert nicht mehr mit und laesst einen gespeicherten also unangetastet. Das Rollen-Auswahlfeld stand niedriger als die Eingabefelder daneben: Der Auswahl-Baustein setzte standardmaessig text-sm (38px), Input erbt die normale Groesse (42px). Jetzt erbt auch die Auswahl -- das betrifft jedes Formular, in dem beide nebeneinander stehen. Kompakte Auswahlfelder in Filterleisten geben text-sm weiterhin selbst mit. Gemessen in Chromium und WebKit: Foerderverein und Termine 42/42 px, Kurse 46/46 px (dort eigens groessere Felder).
fix(förderverein): Ohne „Mitglied seit“, Auswahlfelder so hoch wie Eingaben
Der Erfahrungsbericht von "Michael K., Chorleiter seit 2018" hat nie jemand gesagt -- ein Platzhalter, der schon vor der Neugestaltung auf der Seite stand. Der Abschnitt entfaellt; auf die Unterstuetzungsliste folgt direkt der Schlussaufruf. Es war das einzige Zitat dieser Art auf der Website.
fix(ehrenamt): Erfundenes Zitat entfernt
feat(spiele): Spiele im Programmheft
Die letzten beiden Bausteine im alten Design. Sie oeffnen sich mitten aus dem
fertigen Setup heraus ("Eigenes Set ...", aus Noten-Lesen und aus Griffe) —
dort traf neues auf altes Aussehen. Gemessen steht der Spielebereich damit
auf 0 Alttreffern, vorher 50 in der Bibliothek und 54 im Editor.
- Huelle: Papier/Nacht mit 2px-Tintenrahmen ab `sm`, Kopf und Fuss auf
Haarlinien. Schatten und Rundungen ersatzlos weg.
- Auswahl ist ein Druckfeld (`on-orange bg-primary text-ink`): Filter-Chips,
Schluessel-Chips, Notenchips, Schluessel-Abzeichen.
- Orange-als-Text nur ueber `text-primary-ink` / nachts `text-primary`; die
Hauptaktionen laufen ueber den gemeinsamen Button in Tinte statt weiss auf
Orange, was 1,99:1 ergeben haette.
- Rot bleibt Rot, aber gestuft: Loeschen oeffnet als Umriss, die Bestaetigung
ist gefuellt.
- Klickziele von rund 26-30px auf 44px; Schliessen-Knopf 44x44.
Vorlese-Namen: Das Verschmelzungsproblem der Auswahlkarten gibt es hier
nicht. Das tatsaechliche Problem war Mehrdeutigkeit — in einer Liste mit
mehreren Saetzen hiessen die Knoepfe viermal gleich ("Verwenden", "Link
kopieren", "Bearbeiten", "Loeschen"), je Oktave nochmal ("Stammtoene",
"Leeren"). Jetzt satzbezogen: "<Name> verwenden", "Stammtoene der Oktave 4
hinzufuegen". Sichtbarer Text unveraendert.
Nachgeprueft statt geglaubt: nur diese zwei Dateien geaendert, 0 Alttreffer
je Datei, tsc 0, jest 57 Suiten / 681 Tests gruen.
Drei Entscheidungen, die zur Gegenlesung offenstehen:
- Der Huellenvertrag (GameBarSlot/GameDock) ist hier bewusst NICHT angewandt:
Die Bibliothek ist ein modaler Dialog ueber dem Spiel, kein Spielschirm;
eine Fusszeile im Dock laege hinter dem Overlay.
- Der Inline-Link "anmelden" bleibt 14px hoch. 44px wuerden den Satz
zerreissen, und die Programmheft-Seiten fuehren Textlinks ueberall inline.
- Neu importiert werden `Button` und `fieldControlClasses` aus
`_components/programmheft`. Das koppelt den Spielebereich an die
Heft-Bausteine; alternativ waeren es zwei Klassenkonstanten.
Luecke, die offen bleibt: Anlegen, Bearbeiten und Loeschen sind
`protectedProcedure` und wurden nicht gegen den Server durchgespielt — dafuer
braeuchte es eine Anmeldung. Der gesamte Client-Pfad des Editors ist bedient
(Zeichenzaehler, Oktavauswahl, Schluesselwechsel mit Bereichswarnung,
Abbrechen, Loeschruecknahme), nur die Serverantwort auf "Speichern" fehlt.
CodeQL meldete am Release-PR vier Warnungen hoher Stufe im Klartext- Helfer aus #311: - Tags wurden in einem Durchgang entfernt. Aus <<b>script> macht das <script> -- das Entfernen selbst setzt ein neues Tag zusammen. Jetzt wird wiederholt, bis sich nichts mehr aendert. - Entitaeten wurden nach dem Abraeumen und nacheinander aufgeloest: Aus <script> wurde hinterher wieder <script>, und &lt; wurde doppelt entschluesselt. Jetzt in einem Durchgang und danach noch einmal abgeraeumt. Der Klartext wird heute nirgends als HTML ausgefuehrt (iCal, Metadaten, Suche), soll aber von sich aus frei von Tags sein. Spitze Klammern im Text ("Kinder <10 Jahre", "a < b") bleiben stehen. Die Test-Hilfsfunktion folgt denselben Regeln; neue Tests fuer die Angriffsmuster.
fix(sicherheit): Klartext-Helfer lässt garantiert keine Tags stehen
Ein `<button>` nimmt nur Phrasing-Inhalt; `<p>` gehoert nicht dazu. Die Setup-Karten der Griffe rendern den Hinweistext an fuenf Stellen als `<p>` im Knopf — ungueltiges Markup, das Browser zwar aufraeumen, das aber beim Hydrieren warnt und die Baumstruktur unnoetig unklar laesst. In Noten-Lesen war dasselbe Muster bereits korrigiert, hier blieb es stehen. Fuenfmal `<span>` mit `block`, damit der Hinweis weiter eine eigene Zeile bekommt. Die drei `<p>` der Gruppenbeschriftungen stehen ausserhalb der Knoepfe und bleiben. Nachgemessen: `<p>` in `<button>` von 5 auf 0; alle zehn Hinweise in hell und nacht `display: block`, eigene Zeile, sichtbar (17px einzeilig, 33px zweizeilig). Also rein strukturell, optisch folgenlos.
Spiele: Notensatz-Bibliothek im Programmheft und ungueltiges Markup
Auf mittwald lassen sich im laufenden Container keine Skripte ausfuehren. Das Skript gegen harte Zeilenumbrueche laeuft deshalb wie die anderen Backfills im Startbefehl (deploy/stack.yaml), direkt nach den Migrationen -- die ohnehin schon bei jedem Start automatisch eingespielt werden. Damit das bei jedem Neustart gefahrlos ist, liegt die Regel jetzt getestet in src/lib/description-linebreaks.ts: Der Editor speichert Zeilenumbrueche als Markdown-Umbruch mit zwei Leerzeichen am Zeilenende, solche Zeilen fasst sie nie an, Listen und Absaetze auch nicht, und ein zweiter Lauf aendert nichts mehr. Das Skript schreibt jetzt standardmaessig (wie die anderen Backfills), --dry-run zeigt nur. Die neue Datei steht in der Kopierliste des Dockerfiles; ohne sie waere das Skript beim Start still gescheitert. Nachgewiesen im lokal gebauten Laufzeit-Image: Skript laeuft, erreicht die Datenbank, meldet auf dem schon geglaetteten Bestand 0 Aenderungen.
chore(deploy): Umbrüche in Beschreibungen beim Start glätten
Die Wartungsseite rendert auf dem Server, und der laeuft in UTC. Ohne feste Zeitzone stand dort 21:59, wo im Dashboard 23:59 eingetragen war (im Winter 22:59). Jetzt Europe/Berlin, und mit Wochentag: "Donnerstag, 17. September 2026 um 23:59".
fix(wartung): Voraussichtliches Ende in deutscher Ortszeit
Die App laeuft in Produktion in einem Container mit Zeitzone UTC. Ueberall, wo ohne feste Zeitzone formatiert wurde, standen Datum und Uhrzeit in UTC: zwei Stunden zu frueh (im Winter eine), und ein Termin kurz nach Mitternacht am Vortag. Auch Client-Komponenten waren betroffen, weil sie zuerst auf dem Server gerendert werden (danach Hydration-Abweichung), und E-Mails, die immer auf dem Server entstehen. Neuer Helfer src/lib/berlin-time.ts: feste Formate in Europe/Berlin (Ausgabe Zeichen fuer Zeichen wie vorher), Kalenderbestandteile in Berliner Zeit als Ersatz fuer getDate() und Co., berlinDate fuer Berliner Uhrzeiten auch an den Umstellungstagen. Getestet unter Sommer- und Winterzeit, an der Mitternachtsgrenze und in Prozessen unter UTC, Los Angeles und Kiritimati. Umgestellt: oeffentliche Seiten, Kalender, Anmeldeformular, Dashboard, zehn Mailvorlagen samt Kursmail-Platzhaltern und Newsletter, Exporte und Dateinamen. Wo es um einen deutschen Kalendertag geht, rechnet auch die Logik in Berlin: Kalenderzellen, heute, Monatsgruppen, vergangen/kommend, Altersgrenze der Preiskategorie (Server und Formular waren sich bei einem Kurs ab 00:00 uneinig), Monats- und Jahresgrenzen in Abfragen, Statistik. Dabei behoben: Termin bearbeiten belegte das Datum aus dem UTC-Tag vor, ein Termin zwischen 00:00 und 02:00 waere auf den Vortag gerutscht. Eingabefelder rechnen weiter in der Browserzeit; das ist dort richtig. Gemessen, UTC-Server gegen Berlin-Server: im Server-HTML vorher 29 von 108 Zeitangaben abweichend, nachher 0; im Dashboard vorher 83 von 245, nachher 0; Mailvorlagen vorher 10 von 12 falsch, nachher 0; keine Hydration-Warnungen mehr.
fix(zeit): Alle Datums- und Zeitangaben in deutscher Ortszeit
Die Frist endete mit setHours(23, 59, 59, 999) in der Zeit des Rechners. Der Server läuft in UTC; dort war das Ende 01:59 bzw. 00:59 deutscher Zeit am Folgetag, Anmeldungen gingen also bis zu zwei Stunden zu spät durch. Um Mitternacht gespeicherte Fristen (22:00 UTC am Vortag) liefen sogar schon am Vorabend ab, fast einen Tag zu früh. Jetzt endet die Frist mit dem deutschen Kalendertag. Die fortlaufende Rechnungsnummer nahm das Jahr aus getFullYear() in UTC: Rechnungen vom 1. Januar zwischen 00:00 und 01:00 Uhr bekamen noch das alte Jahr. invoiceYear() nimmt das Berliner Jahr. Die Tests bauen ihre Zeitpunkte über berlinDate und laufen damit in jeder Zeitzone gleich (geprüft von UTC−11 bis UTC+14).
fix: Anmeldefrist und Rechnungsjahr in deutscher Zeit
Viele Kommentare erzählten Verlauf, Messwerte und Abwägungen nach, statt den einen nicht offensichtlichen Punkt zu nennen. Entfernt sind Wiederholungen des Codes, Abschnittsmarken, Banner, auskommentierter Code, leere JSDoc und Verweise auf frühere Stände, PRs und Personen. Lange Erklärungen sind auf ihren Grund gekürzt. Erhalten bleiben Browser-Eigenheiten, die Regel zu UTC und deutscher Zeit, Sicherheits-, Datenschutz- und Geschäftsregeln sowie alle Direktiven (eslint-disable, @ts-expect-error, prettier-ignore usw.). Nur Kommentare geändert: Für jede Datei ist der Syntaxbaum gleich (TS/JS), bzw. der Inhalt ohne Kommentarzeilen (CSS, Prisma, YAML, Shell, Dockerfile). Kommentarzeilen 12.123 -> 6.077, Blöcke ab 6 Zeilen 495 -> 15. Zwei falsche Kommentare sind weg: users.delete ist kein Soft-Delete, und die JSDoc über sendConfirmationEmail gehörte zu einer anderen Funktion.
chore: Kommentare kürzen und überflüssige entfernen
Die Mails zur aufgeteilten Anmeldung und zum Nachrück-Angebot (samt „Angebot abgelaufen“ und Erinnerung ans Kursteam) bauten noch ihr eigenes Gerüst im alten Design: weiße Schrift auf Orange, runde Kästen, orange Knöpfe. Sie nutzen jetzt EmailLayout mit Regel, Knopf und Ersatzadresse wie alle anderen Mails und gehen zusätzlich als Nur-Text-Fassung raus. Der Warteliste-Kasten im Dashboard sprach salopp („Automatisch rückt niemand nach – das löst du hier aus“). Die Texte im Kasten, in der Rückfrage, im Ergebnis und in den Haltegründen sind jetzt sachlich und ohne direkte Anrede. Das Ergebnis nennt „die Anmeldung von …“, damit klar ist, dass nicht die Anmeldende selbst nachgerückt ist.
fix: Warteliste-Mails im neuen Design, sachliche Texte im Team-Kasten
nichtLehdev
force-pushed
the
release/1.0.0
branch
from
September 18, 2026 11:59
1a8a319 to
94a6dd7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sammel-PR für den Meilenstein 1.0.0: 145 Commits, 386 Dateien.
Enthält inzwischen auch die Spiele (#346, #348), den Abschlusstest der Anmeldung samt Korrekturen (#337, #341, #342, #343) und die Umstellung auf Nachrücken von Hand (#341).
Inhalt
Neugestaltung (#319) — Die gesamte Plattform, öffentlich wie Dashboard, steht im Programmheft-Design: Papier und Tinte, Haarlinien statt Karten, Druckorange als einziger Akzent, keine Radien und Schatten. Inhalte, Texte und Seitenaufbau bleiben, soweit nicht ausdrücklich anders entschieden.
Anmeldungen (#314, #316, #317 — schließt #305 und #315)
Inhalte und Medien
Kleinere Korrekturen aus dem Abend: Gruppierung der Startseiten-Termine, Filterbereich der Terminseite ohne Kategorie und neu geordnet, Suchfeld ohne Fokuskasten, Trennlinien zwischen Beiträgen, Rabattmarke an stornierten Anmeldungen, ausgewählter Tag im Nachtdruck, Anfragen bis 100 MB.
Abhängigkeiten: next 16.3.4, react-Gruppe, dev-tools-Gruppe, zod 4.6.2, @types/node 26.5.1 (#300–#304).
Vor dem Ausrollen zu erledigen
Automatisch beim Start des App-Containers (
deploy/stack.yaml), nichts von Hand:registration_groups,waitlist_promotion_offers,download_file_type_image(alle additiv) —prisma migrate deployläuft bei jedem Start.Von Hand in mStudio:
node /app/scripts/trigger-waitlist-offers.mjs. Seit feat(warteliste): Nachrücken nur noch von Hand, Wartende haben Vorrang #341 schließt er nur noch abgelaufene Angebote und erinnert das Kursteam; nachgerückt wird per Knopf im Dashboard.experimental.proxyClientMaxBodySize(fix(import): Anfragen bis 100 MB statt 10 MB #329).Inhaltlich offen: Förderverein (#308) — Seite aufgeräumt, finale Inhalte fehlen.
Geprüft
Auf dem Release-Stand jeweils lokal, weil die CI nur für
mainunddevelopläuft: eslint,tsc --noEmit, jest (780 Tests) undnext build— alles grün. Dazu Messungen und Screenshots in den einzelnen PRs (Chromium und WebKit, hell und dunkel, 1440 und 390).