Update Todos

Skrive migrasjon 052 (guest_first_name/guest_last_name + backfill)

Bygge backend: splitt navn i ParticipantCreate/Update, auto-synk guest_name

Bygge backend: GET known-guest-oppslag (e-post-basert, scoped til egen bruker)

Bygge backend: retroaktiv e-post-kobling ved innlogging (auth.py)

Bygge backend: HTML-e-post-støtte i email.py (multipart/alternative)

Bygge backend: scorekort/statistikk-e-post ved complete_round (individuell + delt-ball)

Frontend: e-post-felt + navnesplitt + autofyll fra søk i AddGuestForm

Frontend: "vi kjenner igjen denne e-posten"-forslag i AddGuestForm

Scratch-verifisere hele kjeden (migrasjon+backend+frontend+e-post) end-to-end

Kjøre migrasjon mot ekte teecup_db og rulle ut (etter bekreftelse)
Alt seks punkter er live: e-postfelt, navnesplitt, retroaktiv kobling (med bekreftelse-før-HCP-telling), automatisk oppsummerings-e-post ved fullføring, autofyll fra søket, og det personvern-scopede "gjenkjenn denne e-posten"-forslaget. Migrasjonen kjørte rent mot ekte data (3 eksisterende gjester korrekt splittet), test_isolation.sql fortsatt 12/12, teeoff.no upåvirket.
This commit is contained in:
Erol Haagenrud 2026-08-03 06:29:07 +02:00
parent d395f86753
commit e74fe5d949

View file

@ -6336,6 +6336,128 @@ Ferdig og verifisert:
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200,
`teeoff.no` upåvirket.
- **Midlertidige spillere (gjester på frittstående runder): full runde
ferdig -- e-post, navnesplitt, retroaktiv kobling, autofyll, "gjenkjenn
gjest"-oppslag, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
(2026-08-03):** brukeren ba om seks ting samtidig (e-post som valgfritt
felt manglet, navnesplitt for-/etternavn, retroaktiv lagring/kobling av
gjeste-runder til en fremtidig konto, automatisk e-post med scorekort+
statistikk+invitasjon ved fullføring, autofyll fra søkefeltet inn i
gjesteskjemaet, og "gjenkjenn en tidligere registrert gjeste-e-post") og
ba eksplisitt om innspill på hva mer som var lurt. Grundig
kodeutforskning FØR noe ble bygget avdekket presist hva som faktisk
manglet: `guest_email` fantes ALLEREDE i backend (2026-07-26), bare
aldri eksponert i selve "legg til gjest"-skjemaet; `guest_name` var ETT
enkelt tekstfelt brukt i 10+ spørringer.
**Tre load-bærende avklaringer bekreftet av bruker** (AskUserQuestion):
(A) en retroaktivt koblet runde (noen registrerte deg som gjest FØR du
hadde konto) teller IKKE automatisk mot faktisk HCP -- `exclude_from_
handicap` settes til `true` som default ved kobling, personen må selv
slå den på (samme mekanisme ADR-038 allerede bygget, kun default
snudd for denne ene banen inn). Begrunnelse: en org-turnering sin
eksisterende `link_player_by_email`-presedens (ADR-017) er trygg fordi
org-scoring aldri teller mot faktisk HCP -- frittstående runder GJØR
det (ADR-038), så blind auto-inkludering ville latt en fremmed
påvirke noens HCP uten samtykke. (B) treffer en gjeste-e-post en
EKSISTERENDE konto, opprettes gjesten likevel -- kobles ved neste
innlogging, ikke et eget "denne personen har konto"-forgreiningssteg
(enklere, bevisst valgt fremfor det opprinnelig anbefalte). (C) bygget
i én samlet runde (skjema→backend→frontend→e-post lagvis, samme
disiplin som ellers i prosjektet).
**Migrasjon `052_guest_name_split.sql`:** `round_participant.
guest_first_name`/`guest_last_name` lagt til, `guest_name` beholdt
UENDRET som et auto-synkronisert, lagret "fullt navn" (samme mønster
som `app_user.display_name` synkes fra `first_name`/`last_name`,
2026-07-25-bugfiksen) -- unngikk å måtte røre alle eksisterende
SELECT-steder. Backfill: "første ord = fornavn, resten = etternavn".
**Retroaktiv kobling** (`app/routers/auth.py`, ny
`_link_round_participants_by_email`, kalt fra BEGGE innloggingsveiene
-- magic-link og passord, samme "kjør trygt på hver innlogging"-
idempotens som `link_player_by_email`): INGEN SECURITY DEFINER-bro
trengs siden `round`/`round_participant` ikke har RLS (ADR-033
Beslutning A) -- en rett UPDATE er nok. `NOT EXISTS`-vaktet mot en
allerede eksisterende ekte deltaker-rad på samme runde (ville ellers
brutt migrasjon 027 sin UNIQUE-indeks). **Reell bug funnet OG fikset
UNDER scratch-testing:** en første versjon nullet `gender` sammen med
de andre gjeste-feltene ved kobling -- krasjet med en NOT NULL-
violation, siden `gender` er en påkrevd snapshot for ALLE deltakere
(brukt til utslags-rating-oppslag), ikke bare gjester. Rettet ved å la
`gender` stå urørt (uansett låst mot videre endring av `update_
participant` sin egen `guest_only_fields`-sjekk så snart `user_id` er
satt).
**HTML-e-post** (`app/email.py`, FØRSTE i appen): `_send_sync` fikk en
valgfri `html_body`-parameter (`EmailMessage.add_alternative`,
multipart/alternative -- ekte tekst-fallback bevart). Ny
`send_round_summary_email` -- scorekort som HTML-tabell, statistikk
gradert etter `stat_level`/individuell- vs. delt-ball (kalleren bygger
`stat_lines`, e-post-modulen antar ingenting), ekte magic-link-
innlogging (samme token-mønster som `send_scorecard_invitations`,
ADR/CLAUDE.md 2026-07-28). **Navneformat (ufravikelig regel):**
hilsenen er "Hei {fornavn}," -- direkte adressering, kun fornavn, aldri
fullt navn. Trigget fra en ny `_send_guest_round_summaries`, kalt fra
`complete_round` for hver deltaker med `guest_email` satt -- bygger
scorekortet fra ENTEN `_build_participant_holes` (individuell) ELLER
`_build_side_holes` (delt-ball, ADR-039), gjenbruker eksisterende
hjelpefunksjoner uendret, ingen egen regnelogikk.
**Known-guest-oppslag** (`GET /rounds/guests/known`, statisk rute
plassert FØR `/rounds/{round_id}` i filen, samme mønster som
`/rounds/stats/summary`): bevisst scoped til KUN den spørrende
brukerens EGNE tidligere registrerte gjester (`WHERE r.owner_user_id =
$1`) -- et globalt oppslag ville latt hvem som helst skrive inn en
tilfeldig e-post og se navn/kjønn/HCP en HELT ANNEN organisator har
registrert, en reell personvernlekkasje unngått FØR bygging (flagget
proaktivt til bruker, bekreftet enig).
**Frontend** (`round-detail.tsx`): `AddGuestForm` skrevet om -- separate
Fornavn/Etternavn-felt (etternavn valgfritt), nytt E-post-felt med
debounced known-guest-oppslag + en "Bruk disse opplysningene"-forslags-
boks, `initialName`-prop fra søkefeltet (`AddParticipantForm`s `query`)
autofyller navnefeltene ved "Legg til uten konto (gjest)" via en ny
`splitName()`-heuristikk (samme "første ord/resten" som backfillen).
`EditParticipantPanel` oppdatert til samme to-felts navnestruktur (var
i ferd med å bli en regresjon siden backend ikke lenger godtar
`guest_name` direkte). **Reelt, uforespurt funn fanget under
implementering:** rundeoppsett-veiviseren (`new-round.tsx`) sender
OGSÅ `guest_name` ved opprettelse av spillere i Steg 3 -- ville brutt
hvis ikke oppdatert samtidig; løst med samme splitt-heuristikk direkte
ved innsending (ingen UI-endring i wizarden denne runden, kun
kontraktsrettelse).
**Scratch-verifisert grundig, 41/41 sjekker** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container, alle 52 migrasjoner kjørt friskt, samme mønster som hele
prosjektet): navnesplitt+auto-synk, delvis PATCH bevarer det andre
feltet, validering (verken/begge user_id+guest_first_name avvist),
known-guest-oppslag (funnet for egen bruker, IKKE synlig for en annen
organisator -- personvern-scopingen bekreftet presist), full
fullførings-e-post-syklus (dev-log bekreftet både magic-link OG
rundeoppsummering), retroaktiv kobling (user_id satt, gjeste-felt
nullstilt, `gender` BEVART, `exclude_from_handicap=true`, `display_
name` viser nå kontoens ekte navn), idempotent gjeninnlogging, OG et
eget delt-ball-scenario (foursome) som bekreftet ingen krasj ved
fullføring og korrekt sideoppsummering i e-post-loggen.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP):
hele "+ Medspiller"→"Legg til uten konto"-flyten klikket gjennom med et
navn som ikke fantes -- bekreftet AUTOFYLL fungerte (Fornavn/Etternavn
korrekt splittet), skrev inn en kjent gjeste-e-post og bekreftet
"Vi fant Anna fra en tidligere runde..."-forslaget dukket opp, trykket
"Bruk disse opplysningene" og bekreftet navn/kjønn ble overskrevet
korrekt (HCP forble tomt, siden Anna ikke hadde noen registrert),
sendte inn skjemaet og bekreftet gjesten dukket opp i spillerlisten
UTEN konsollfeil, åpnet rediger-panelet og bekreftet det viser samme
to-felts navnestruktur korrekt forhåndsutfylt. E-postens faktiske
HTML/tekst-innhold generert og inspisert direkte (ikke bare at
utsendingen ble trigget) -- bekreftet riktig "Hei Kari,"-hilsen,
korrekt hull-tabell inkl. et uspilt hull vist som "", og korrekt
byggede statistikklinjer.
**Rullet ut mot ekte systemer 2026-08-03**, bruker bekreftet
eksplisitt (plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige
regel): migrasjon 052 kjørt mot ekte `teecup_db` (nye kolonner
bekreftet, backfill verifisert nøyaktig mot de 3 eksisterende gjeste-
radene i produksjon -- "Christer Heitun"→"Christer"/"Heitun", "Sigurd"
→"Sigurd"/null, "Vidar Hoksrød"→"Vidar"/"Hoksrød" -- `test_isolation.sql`
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `GET /rounds/guests/known` anonymt ga korrekt `401` gjennom hele
produksjonsstacken (ikke en rå 404), `teeoff.no` upåvirket.
Neste steg:
0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet