fix(helfer): Schichten-Liste im Helfer-Detail zuverlässig laden - #258
Conversation
Die Schichten-Sektion im Helfer-Detail blieb hängen oder crashte das Template, statt zu laden (gemeldet für Helfer-Event dYT4m48vvJeHL0w2HrKL): - forkJoin([]) bei leerer Mitgliederliste emittierte nie und liess die Schichten-Sektion unbegrenzt im Ladezustand - Schichten sofort mit Platzhalter rendern statt auf einen Profil-Read pro Clubmitglied zu warten (gleiches Muster wie die Listen-Seiten) - Timeout pro Profil-Read, damit ein einzelner hängender Firestore-Read die Liste nicht unbegrenzt blockiert - changedAt nullsicher sortieren (legacy Attendee-Dokumente ohne Feld) - Fehler-Fallbacks template-sicher machen (status: [] statt null, plus children: []), damit schicht.status.length nicht mehr wirft - this.user optional lesen, da schichten$ vor event$ emittieren kann Spec: AuthService-Spy um getAuthenticatedUser$ ergänzt (die Suite war komplett rot, seit die Page darauf umgestellt wurde) und Regressionstests für die Ladepfade ergänzt. Fixes #257 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
ReviewSolid, well-targeted fix — each of the five root causes named in the PR description is addressed at the right layer, and the reasoning is easy to follow from the code + inline comments. A few notes, none blocking. Correctness
Minor suggestions (non-blocking)
Security / performanceNo security concerns (no new user input paths, standard Firestore reads scoped by club/event id). Performance impact is neutral-to-positive: this trades "wait forever" for "wait up to 10s, or render immediately with a placeholder," which is strictly better for perceived responsiveness. Overall: good root-cause analysis, matches an existing non-blocking pattern already used elsewhere in the Helfer pages, and the test additions meaningfully cover the regressions being fixed. |
…Test ergänzen Review-Feedback auf #258: die dreifach duplizierte template-sichere Platzhalter-Form in toPlaceholderSchicht() zusammenführen, damit die Kopien nicht auseinanderlaufen, und einen fakeAsync-Test ergänzen, der den timeout(10000)-Pfad wirklich auslöst (langsamer Profil-Read fällt auf den Unknown-Platzhalter zurück statt zu blockieren). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
ReviewSolid, well-scoped fix for the "Helferliste hängt" bug. The five root causes listed in the PR description all check out against the diff, and the new spec cases ( Potential bug / regression to double-check
Before this change, that resubscription would keep the previously-rendered (fully resolved) array on screen via the async pipe until the new one was ready — a stale-but-correct display. With Minor / nits
Test coverageGood targeted coverage of the new branches. One gap: no test asserts that a change to the attendees subcollection (as opposed to the schichten collection or member list) does not re-trigger the eager placeholder — that's the scenario underlying the regression concern above, and a test would make the intended behavior explicit either way. Security / performanceNo security concerns (no new user input handling, no rule changes implied). Performance-wise, the 🤖 Generated with Claude Code |
Review-Feedback auf #258: getClubHelferEventSchichtenRef ist ein Live- Listener — bei jeder weiteren Emission (z.B. Admin benennt eine Schicht um) lief der äussere switchMap neu an und das unbedingte startWith hätte die ganze Liste für alle offenen Viewer kurz auf den "unbeantwortet"- Platzhalter zurückblitzen lassen. Der Platzhalter greift jetzt nur bei der ersten Schichten-Emission (switchMap-Index); danach behält die async pipe den zuletzt aufgelösten Stand, bis der neue Join fertig ist. Test ergänzt: Live-Updates der Attendees- und Schichten-Collection emittieren keine Platzhalter mehr, nur aufgelöste Daten. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
ReviewSolid, well-targeted bug fix. The root-cause analysis in the description matches what's in the diff, and the new regression tests specifically cover each failure mode described (empty Code quality / correctness
Potential remaining issues (pre-existing, not introduced by this PR, but worth flagging)
SecurityNo concerns — no new user input handling, and Firestore access remains inside the service layer per Test coverageGood coverage of the specific bugs fixed: empty member list, missing Style / conventionsMatches Overall: a targeted, well-tested fix for a real production bug. Nothing here blocks merging. |
Review-Feedback auf #258: - schichten$ mit shareReplay({bufferSize: 1, refCount: true}) teilen — das Template konsumiert den Stream über zwei async pipes (Mitglieder- und Admin-Edit-Ansicht), wodurch die komplette Mitglieder-/Profil- Lesekaskade bisher doppelt lief - toPlaceholderSchicht mit dem bestehenden Schicht-Modell typisieren - Test ergänzt: bei langsamer Mitgliederliste erscheint zuerst der Platzhalter, danach die aufgelösten Daten Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
ReviewSolid fix — this correctly diagnoses and addresses all five root causes described in the issue ( Code quality
Potential bugs
Performance
Security
Test coverage
Overall: a well-targeted, well-tested fix for the reported hang/crash. The one thing I'd want a second look at before merge is the interactive-placeholder capacity-check window — worth a quick gut check on whether it's acceptable as-is. |
Review-Feedback auf #258: Mit dem Eager-Platzhalter waren die Schicht- Zeilen sofort klickbar, während attendeeListTrue noch leer war — ein Tap in diesem Fenster hätte die Kapazitätsprüfung (attendeeListTrue.length >= countNeeded) umgangen und eine volle Schicht überbuchen können. Platzhalter tragen jetzt pending: true und die Toggle-Handler ignorieren Taps darauf, bis die echten Zähler aufgelöst sind. Ausserdem: shareReplay-Kommentar präzisiert (die beiden async pipes liegen heute in sich ausschliessenden @if-Zweigen) und toten getUser$-Spy im Spec entfernt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
Code ReviewReviewed the fix for the Helfer-Schichten loading hang ( Strengths
Potential gap:
|
…schützen Review-Feedback auf #258: Die Admin-Aktion "Mitglieder hinzufügen" war über dasselbe Sliding-Item erreichbar, während die Schicht noch als Platzhalter (pending) gerendert war — der "verfügbare Mitglieder"-Filter hätte dann alle Clubmitglieder angeboten, auch bereits eingetragene. Gleiche pending-Guard wie in den Toggle-Handlern, Test erweitert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
ReviewSolid fix for the hang — the One correctness gap I'd like to see addressed before merge:
|
Review-Feedback auf #258: Die beiden catchError-Fallbacks lieferten pending: false mit leerer attendeeListTrue — bei einem echten Firestore- Fehler (genau die Fehlerklasse, gegen die dieser PR härtet) wären die Zeilen tappbar geblieben und die Kapazitätsprüfung hätte mit 0 Einträgen immer durchgelassen; addMembersToSchicht hätte alle Clubmitglieder als verfügbar angeboten. Ein Platzhalter hat per Definition keine aufgelösten Attendee-Daten, daher setzt toPlaceholderSchicht jetzt immer pending: true (Parameter entfernt). Regressionstest für Taps auf Fehler-Fallback-Zeilen ergänzt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
ReviewI read through the diff for Strengths
Potential issue:
|
…ur einmal bauen Review-Feedback auf #258: Die beiden async pipes liegen in sich ausschliessenden @if-Zweigen — beim Edit-Toggle fiel der Subscriber- Zähler kurz auf 0, wodurch das refCount-shareReplay die komplette Lesekaskade abriss und neu startete (inkl. Platzhalter-Flackern und erneuter Profil-Reads). Eine komponenten-eigene Subscription pinnt den Stream jetzt für die Lebensdauer der Seite; ngOnDestroy gibt sie frei, damit die Firestore-Listener mit dem Modal abgebaut werden. Zusätzlich baut loadData die Streams nur noch einmal (ngOnInit und ionViewWillEnter riefen es beide auf und starteten die Kaskade doppelt — gleiche Guard wie auf der Helfer-Listenseite). Test ergänzt: Re-Subscription und View-Re-Entry starten die Kaskade nicht neu; ngOnDestroy schliesst die gepinnte Subscription. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM
ReviewSolid fix — the root-cause analysis in the PR description matches what's in the diff, and each of the five failure modes has a corresponding code change plus a regression test. A few notes below, nothing blocking. Code quality
Potential issues
Performance
SecurityNothing concerning — no new user input paths, no template Test coverageGood depth here — One gap: there's no test asserting the admin-overbooking path ( Nice work tracking down a genuinely gnarly combination of RxJS completion/timing bugs. |
PR #258 emittiert die Schichten sofort als Platzhalter (pending: true), das Template zeigte sie aber wie fertige Zeilen: Status-Icon "unbeantwortet", Badge "0 / N" und keine Teilnehmer. Für Nutzer sah das aus, als wären keine Anmeldungen vorhanden, statt dass die Daten noch laden. - Solange eine Schicht pending ist, zeigen Status-Icon, Badge und zwei Teilnehmer-Zeilen ein animiertes Skeleton. - Die Fehler-Fallbacks sind ebenfalls pending, dürfen aber nicht endlos als Skeleton stehen: toPlaceholderSchicht() bekommt ein loadFailed- Flag, das Template zeigt dafür einen Hinweis (de, fr, it, en) statt des Skeletons. - Vier Tests sichern die Flags für Platzhalter, beide Fehlerpfade und aufgelöste Schichten. Baut auf PR #258 auf (Branch claude/issue-pr-fix-loading-59o6nf). Refs #257 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fixes #257
Problem
Die Helferliste «hängt immer wieder»: Im Helfer-Detail-Modal erscheinen nur die Event-Details, die Schichten-Liste lädt nie, und es kommt wiederholt eine Fehlermeldung (gemeldet für Helfer-Event
dYT4m48vvJeHL0w2HrKL, mehrheitlich im Browser genutzt).Ursachen
Die Pipeline
getHelferEventSchichtenWithAttendees()konnte auf mehreren Wegen hängen bleiben oder das Template crashen:forkJoin([])completed ohne Emission, wenn die Clubmitglieder-Liste leer ist →schichten$emittiert nie, die Sektion bleibt für immer leer.forkJoinwartet ohne Timeout auf alle → bei grossen Clubs/instabiler Verbindung bleibt die Liste beliebig lange leer.changedAt.toDate()wirft bei Attendee-Dokumenten ohnechangedAt.catchError-Fallbacks liefertenstatus: nullohnechildren— das Template liest aberschicht.status.length, wodurch jede Change Detection erneut wirft (die wiederkehrende Meldung) und die Ansicht eingefroren bleibt.schichten$kann emittieren, bevorevent$this.usergesetzt hat (this.user.uid→ TypeError → Fallback → Punkt 4).Änderungen
startWith-Platzhalter, nur beim initialen Laden — Live-Updates flackern nicht auf den Platzhalter zurück); die Profil-Joins füllen sie nach — gleiches non-blocking Muster wie auf den Listen-Seiten (29278ef).confirmSchichten()wartet weiterhin auf die echten Daten.forkJoin-Guard: Leere Mitgliederliste →of([])statt nie emittierendemforkJoin([]).timeout(10000)pro Profil-Read; bei Timeout/Fehler greift der «Unknown»-Platzhalter statt dass die ganze Liste blockiert.changedAt?.toDate?.()?.getTime() ?? 0.toPlaceholderSchicht()(status: [],children: []stattstatus: null), damitschicht.status.lengthnie mehr wirft.pending-Guard: Platzhalter- und Fehler-Fallback-Zeilen sind nicht interaktiv — Taps können die Kapazitätsprüfung nicht mehr mit leeren Attendee-Listen umgehen (toggleSchicht,toggleSchichtItem,addMembersToSchicht).schichten$wird per shareReplay geteilt und über eine komponenten-eigene Subscription für die Seiten-Lebensdauer gepinnt (Freigabe inngOnDestroy) — der Edit-Toggle zwischen den beiden@if-Zweigen startet die Lesekaskade nicht mehr neu.loadData()baut die Streams nur noch einmal (ngOnInit + ionViewWillEnter starteten sie doppelt).this.user?.uidmitfilter(Boolean)gegen die Race mitevent$.Tests
AuthService-Spy kanntegetAuthenticatedUser$nicht — alle 24 Tests der Suite waren rot (gleiches Problem in 13 weiteren Specs → test: AuthService-Spies in 13 Spec-Dateien veraltet – Suiten schlagen seit der getAuthenticatedUser$-Umstellung fehl #260). Spy ergänzt.changedAt, Fehler-Fallbacks (template-sicher und nicht interaktiv), Eager-Platzhalter (sofort, kein Replay bei Live-Updates, langsame Mitgliederliste), 10s-Timeout-Pfad (fakeAsync), Taps auf pending-Zeilen, keine Kaskaden-Neustarts bei Re-Subscription/View-Re-Entry.ng test(helfer-detail-Suite): 33/33 grün;ng build: erfolgreich (AOT/strictTemplates).Follow-ups: #259 (Profil-Lookups bündeln/cachen statt N Reads pro Clubmitglied), #260 (veraltete AuthService-Spies in 13 weiteren Specs).
🤖 Generated with Claude Code
https://claude.ai/code/session_01FkgikK428UwuRY3RJ6FMUM