diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index 6bf4199..f8b4962 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -7215,6 +7215,100 @@ bruker. Begge containere startet rent. --- +## ADR-073: Regresjon i "Ny runde"-veiviseren -- eierens statistikknivå (og utslag ved endring) ble ikke lagret (2026-08-15) + +Foranledning: bruker rapporterte at de "til stadighet må gå tilbake å +sette utslagssted og statistikktype på nytt før runden begynner", og at +"det må ha blitt borte i prosessen, for det fungerte tidligere" -- +vedla en skjermopptak-video (`Temp-uploads/Video_2026-08-15_070115.mp4`, +lest via `ffmpeg` hentet på farten som npm-pakke `ffmpeg-static`, siden +ingen video-verktøy fantes fra før og `apt`/`pip` krevde sudo). + +**Videoen viste presist to symptomer for RUNDE-EIEREN** (frittstående +runde, `ny-runde/`-veiviseren): (1) endres utslag i "Bane & tid"- +bekreftelsesskjermen ETTER at banen først ble valgt, viser steg 3 sitt +spillerkort fortsatt det GAMLE utslaget -- (2) "Statistikk for deg +selv"-valget i steg 2 ("Spilleform") hadde INGEN effekt på den faktiske +innsendingen -- runden ble alltid opprettet med `strokes_only` ("Kun +slag") uansett hva som ble valgt der, bekreftet ved å åpne "Rediger" på +eget spillerkort i steg 3 rett før innsending. + +**Rotårsak (`wizard-context.tsx`/`step1-course-time.tsx`/`step2- +format.tsx`):** eierens spillerobjekt i `state.players[]` har SINE EGNE +`teeId`/`statLevel`-felt, atskilt fra de øverste `state.teeId`/`state. +statLevel`-feltene steg 1/steg 2 faktisk redigerer. Disse ble tidligere +KUN synkronisert i ett øyeblikk -- da banen først velges (`pickCourse()` +i `step1-course-time.tsx`, som satte begge samtidig). Endres utslag +IGJEN i bekreftelsesskjermen (`Fields()`, linje 746, `onChange={(e) => +patch({ teeId: ... })}`), oppdateres kun det øverste feltet -- for +utslag var dette rent KOSMETISK (selve `submit()` i `wizard-context.tsx` +leser `state.teeId` direkte for eieren ved selve rundeopprettelsen, se +linje 307/311), men for statistikknivå var det en REELL datafeil: +`submit()` sin `stat_level`-verdi for eieren leses fra `owner?. +statLevel` (spillerobjektet, linje 317) -- IKKE fra `state.statLevel`. +`owner.statLevel` settes KUN til `"score"` hardkodet i `loadMe()` +(linje 217) når `/auth/me` laster, og synkroniseres ALDRI fra `state. +statLevel` når brukeren faktisk gjør et valg i steg 2 -- "Statistikk for +deg selv"-seksjonen påvirket dermed KUN default-verdien for NYE +medspillere lagt til senere (`defaultStat`-proppen til `AddPlayerPanel`, +uendret/fortsatt riktig), aldri eierens eget valg. + +**Ikke en ny mangel -- en bekreftet regresjon:** kommentaren over +`pickCourse()` sin manuelle engangs-synk siterte eksplisitt at den +"matcher eksakt" den opprinnelige (nå oppdelte) `new-round.tsx` sin +`chooseCourse()`-logikk for UTSLAG -- selve engang-synk-mønsteret er +altså gammelt og forsettlig replikert ved en tidligere refaktorering til +dagens `ny-runde/`-mappestruktur. Ingenting tilsvarende fantes noensinne +for STATISTIKKNIVÅ i denne koden -- det er uklart om en eldre +enkeltfil-versjon av veiviseren hadde `owner.statLevel` lest direkte fra +`state.statLevel` uten et eget spillerfelt (brukerens "det fungerte +tidligere" tyder på det), men konklusjonen er uansett den samme: i +DAGENS kode har "Statistikk for deg selv" vært virkningsløs for eieren +siden splittingen til separate steg-komponenter, sannsynligvis +usett fordi standardverdien (`strokes_only`) er nettopp det de fleste +runder uansett bruker. + +**Fiks:** i stedet for punktvise manuelle synkroniseringer, en ENKELT +`useEffect` i `WizardProvider` (`wizard-context.tsx`) som holder +`players[owner].teeId`/`statLevel` KONTINUERLIG i synk med `state.teeId`/ +`state.statLevel`, uansett hvor mange ganger disse endres eller fra +hvilket steg. Kjører kun en faktisk `patch()` når verdiene faktisk +avviker (unngår unødvendige re-renders/uendelig løkke). Den nå +overflødige manuelle engangs-synken i `pickCourse()` fjernet -- ett +sted for denne logikken fremfor to som lett driver fra hverandre igjen. +Endrer brukeren stat-nivået sitt EKSPLISITT i steg 3 sitt eget +spillerkort (i stedet for steg 2), respekteres det inntil de ev. går +TILBAKE til steg 2 og endrer det der igjen (samme presedens-rekkefølge +som "siste redigerte felt vinner", ingen ny brukerforvirring innført). + +**Verifisert:** `tsc --noEmit` rent + 45/45 vitest (ingen eksisterende +test dekket denne regresjonen -- INGEN ny automatisert test lagt til +heller, siden `ny-runde/`-veiviseren ikke har noen eksisterende +test-infrastruktur i dette repoet; browserverifisering ansett +tilstrekkelig for en ren frontend-tilstandssynk-fiks). Egen +scratch-database + scratch `teecup_api`-container (port 18007, +live-montert kode) + lokal `next dev` (port 13007), MED en egen bane +opprettet med to utslag (for å kunne reprodusere "bytt utslag etter +førstevalg") -- gjenskapte videoens eksakte scenario (banevalg → bytt +utslag fra standard "32" til "50" i bekreftelsesskjermen → "All +statistikk" i steg 2 → steg 3 sitt spillerkort viste nå KORREKT "Utslag: +50" og "Alt" (uten regresjonens stale "32"/"Kun slag") → fullført runde +→ bekreftet direkte i databasen at BÅDE `round.tee_name_snapshot='50'` +OG `round_participant.stat_level='full'` faktisk ble lagret → åpnet +scoreregistrering for hull 1, bekreftet at veiviseren auto-hopper videre +til Putter-steget (kun mulig for `strokes_and_putts`/`full`, aldri for +`strokes_only`) -- beviser fiksen virker HELE VEIEN til faktisk lagret +data, ikke bare i visningen. Lys+mørk bekreftet for selve +"Ny runde"-veiviseren (steg 1-3). Scratch-stacken fullstendig revet ned +-- ekte `teecup_db`/`teecup_api`/`teecup_frontend` urørt. + +**Ingen migrasjon** -- ren frontend-tilstandssynk-fiks. + +**IKKE rullet ut ennå** -- venter på eksplisitt bekreftelse fra bruker +før `teecup_frontend` bygges/startes på nytt. + +--- + ## Utviklingsplan (rekkefølge) 1. ✅ Land tenant-modell → **Organisasjon** (ADR-001/002/003) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd38b31..0ee797a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11485,3 +11485,47 @@ Neste steg: **Rullet ut 2026-08-15** -- ingen migrasjon, `docker compose build teecup_api teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker. Begge containere startet rent. + +89. **Fiks: eierens statistikknivå (og utslag ved endring) ble ikke + lagret i "Ny runde"-veiviseren (ADR-073) — 2026-08-15.** Bruker + rapporterte at de "til stadighet må" sette utslag/statistikktype på + nytt, og at "det må ha blitt borte i prosessen, for det fungerte + tidligere" -- vedla en skjermopptak-video som viste to symptomer. + + Rotårsak: eierens spillerobjekt i veiviserens `state.players[]` har + EGNE `teeId`/`statLevel`-felt, atskilt fra `state.teeId`/`state. + statLevel` (de faktiske feltene steg 1/steg 2 redigerer) -- kun + synkronisert ÉN gang, da banen først velges. For utslag var + konsekvensen rent kosmetisk (steg 3 viste et gammelt utslag, men + selve innsendingen leste `state.teeId` direkte for eieren) -- for + statistikknivå var det en REELL datafeil: `submit()` leser eierens + `stat_level` fra spillerobjektet (hardkodet `"score"` fra `/auth/me`- + lastingen), ALDRI fra `state.statLevel` -- "Statistikk for deg + selv"-valget i steg 2 var dermed virkningsløst for eieren, kun + default for NYE medspillere lagt til senere. + + Fiks: én samlet `useEffect` i `wizard-context.tsx` som holder + eierens spillerobjekt kontinuerlig i synk med `state.teeId`/`state. + statLevel`, uansett hvor mange ganger de endres eller fra hvilket + steg -- erstatter den tidligere punktvise engangs-synken i + `pickCourse()` (fjernet, nå overflødig). + + **Verifisert:** `tsc --noEmit` rent + 45/45 vitest (ingen eksisterende + testinfrastruktur for `ny-runde/`-veiviseren i dette repoet -- ingen + ny automatisert test lagt til, browserverifisering ansett + tilstrekkelig for en ren tilstandssynk-fiks). Scratch-database + + scratch `teecup_api` (port 18007, live-montert kode) + lokal `next + dev` (port 13007) med en egen bane med to utslag: gjenskapte + videoens eksakte scenario (bytt utslag etter førstevalg, velg "All + statistikk") -- steg 3 viste nå korrekt utslag+nivå, OG bekreftet + direkte i databasen at både `round.tee_name_snapshot` og + `round_participant.stat_level='full'` faktisk ble lagret (ikke bare + riktig i visningen) -- scoreregistreringen auto-hoppet deretter + videre til Putter-steget, kun mulig for `full`. Lys+mørk bekreftet. + Scratch-stacken fullstendig revet ned -- ekte `teecup_db`/ + `teecup_api`/`teecup_frontend` urørt. + + **Ingen migrasjon** -- ren frontend-tilstandssynk-fiks. + + **IKKE rullet ut ennå** -- venter på eksplisitt bekreftelse fra + bruker før `teecup_frontend` bygges/startes på nytt. diff --git a/frontend/components/ny-runde/step1-course-time.tsx b/frontend/components/ny-runde/step1-course-time.tsx index 329c44d..c2283fa 100644 --- a/frontend/components/ny-runde/step1-course-time.tsx +++ b/frontend/components/ny-runde/step1-course-time.tsx @@ -28,19 +28,16 @@ export function Step1CourseTime() { } const compatible = state.ownGender ? course.tees.filter((t) => t.genders.includes(state.ownGender!)) : course.tees const tee = compatible[0] + // Eierens EGET spillerkort (steg 3) sitt teeId-felt synkroniseres nå + // løpende fra state.teeId av en egen effekt i wizard-context.tsx + // (fanger også senere endringer i "Bane & tid"-bekreftelsen, ikke bare + // dette førstevalget) -- ingen manuell synk nødvendig her lenger. patch({ course, courseMeta: meta, courseSource: meta.source === "teeoff" ? "official" : "own", teeId: tee?.id, s1Sub: "fields", - // Eierens EGET spillerkort (steg 3) har sitt eget teeId-felt, atskilt - // fra state.teeId over -- uten denne synkroniseringen viser kortet - // "Ikke valgt" selv om et utslag faktisk er valgt (kun kosmetisk, selve - // innsendingen leser state.teeId direkte for eieren -- men samme - // synkronisering som den opprinnelige new-round.tsx sin chooseCourse() - // gjorde, så oppførselen matcher eksakt). - players: state.players.map((p) => (p.kind === "owner" ? { ...p, teeId: tee?.id } : p)), }) } diff --git a/frontend/components/ny-runde/wizard-context.tsx b/frontend/components/ny-runde/wizard-context.tsx index d455c43..b2a5cb0 100644 --- a/frontend/components/ny-runde/wizard-context.tsx +++ b/frontend/components/ny-runde/wizard-context.tsx @@ -228,6 +228,31 @@ export function WizardProvider({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + // Eierens EGET teeId/statLevel-felt i players[] (lest av steg 3 sitt + // spillerkort, OG -- for statLevel -- av submit() sin faktiske innsending, + // se linje ~317) er egne, atskilte felt fra state.teeId/state.statLevel + // (steg 1/steg 2 sine felt). De ble tidligere kun synkronisert ÉN gang, + // idet banen først velges (pickCourse i step1-course-time.tsx) -- endrer + // brukeren utslag i "Bane & tid"-bekreftelsen ETTERPÅ, eller statistikknivå + // i "Spilleform", flyter de to fra hverandre. For utslag var dette rent + // kosmetisk (steg 3 viste et gammelt utslag, men submit() leser + // state.teeId direkte for eieren) -- for statistikknivå var det en REELL + // datafeil: "Statistikk for deg selv"-valget i steg 2 hadde INGEN effekt + // på selve innsendingen for eieren (kun på default for NYE medspillere via + // defaultStat-proppen), siden owner.statLevel forble hardkodet "score" fra + // loadMe() over. Bruker-rapportert regresjon 2026-08-15 (video vedlagt) -- + // holder nå de to i kontinuerlig synk fremfor kun ved første valg. + useEffect(() => { + setState((s) => { + const owner = s.players.find((p) => p.kind === "owner") + if (!owner || (owner.teeId === s.teeId && owner.statLevel === s.statLevel)) return s + return { + ...s, + players: s.players.map((p) => (p.kind === "owner" ? { ...p, teeId: s.teeId, statLevel: s.statLevel } : p)), + } + }) + }, [state.teeId, state.statLevel]) + const hasTeamStep = showsTeamStep(state.format) const visibleSteps = useMemo(() => (hasTeamStep ? [1, 2, 3, 4, 5] : [1, 2, 3, 5]), [hasTeamStep])