` + eksplisitt
klikk-/Enter-håndtering.
- **Organisator-overstyring i `team_authz.py`, LIVE (2026-07-18):** reist av
brukeren rett før blind draw-skjermen skulle designes: `app/team_authz.py`
sin `user_may_act_for_team` krevde tidligere en `team_roster`-rad med
lenket bruker-konto — ingen vei for organisatoren til å låse/legge til
deltakere/føre score hvis ingen spiller hadde logget inn ennå (vanlig
tidlig i en turnering). Utvidet til også å godta org-eier/admin
(`organization_membership.role IN ('owner','admin')`), på ETHVERT lag,
ingen unntak for at organisatoren selv er rostret på motstanderlaget.
**Det unntaket ble bevisst vurdert og avvist** (brukeren spurte selv om
det, jeg anbefalte det opprinnelig, men vi kom sammen frem til at det ville
skapt en verre låsning: er organisatoren spillende på Lag A og
Lag B heller ikke har noen innlogget spiller, ville Lag B blitt helt
låst ute) — TeeCup er et tillitsbasert klubb-/vennegjeng-verktøy, ikke en
sikkerhetsgrense mot en fiendtlig organisator som uansett allerede ser
begge lags fulle troppe-liste (blind draw skjuler kun selve
kamp-paringen). Alle 5 kallsteder oppdatert (matches.py sin
add_participant/lock_lineup, scoring.py sin submit_hole_score/
submit_hole_result). **Verifisert grundig i scratch:** org-eier uten
roster kan nå låse BEGGE lag + føre score, en vanlig 'member'-rolle
fortsatt blokkert (uendret), en rostret spiller fungerer uendret
uavhengig av org-rolle. `test_isolation.sql` 12/12. Ingen migrasjon (ren
Python-endring) — kun `teecup_api` redeployet, `teeoff.no` upåvirket.
- **Tee-endepunkter bygget og LIVE (2026-07-18), rett før blind draw-skjermen:**
fant et hull som blokkerte selve blind draw-flyten: `match_participant.
tee_id` er påkrevd, men det fantes INGEN `GET`-vei for å liste en banes
tee-er (selv offisielt importerte baner har tee-rader, men ingenting
eksponerte dem) OG egendefinerte («custom») baner har ALDRI hatt noen
vei til å FÅ tee-er i det hele tatt — dette er ikke noe ADR-019 innførte,
det var et hull som fantes fra før custom-baner ble lagt til i første
omgang, bare usynlig til nå. Konsekvens før fiksen: en turnering satt opp
på en manuelt navngitt bane kunne ALDRI få en ekte deltaker lagt til på
noen match. **Presisering (brukeren spurte eksplisitt):** dette er IKKE
noe som må løses i teeoff.no sin kode — egendefinerte baner er per
definisjon baner teeoff ikke kjenner til, så dette er en ren
teecup-intern funksjon, uavhengig av ADR-019-integrasjonen.
Ny `GET/POST /orgs/{id}/courses/{id}/tees` i `app/routers/courses.py`.
`POST` avviser eksplisitt forsøk på offisielle baner (400
`VALIDATION_FAILED` — de får tee-ene sine fra teeoff-importen, ikke
manuelt). Kun full_18-rating dekket (samme begrunnelse som ADR-019
Beslutning D). **Bevisst UTENFOR omfang denne runden, egen senere sak:**
hull-/stroke-index-data for egendefinerte baner (`hole`-tabellen forblir
tom for custom-baner) — trengs for korrekt slagfordeling i SCORING-fasen
(ADR-008), ikke i blind draw, så det løses naturlig når scorekort-
skjermen bygges (samme "bygg i rekkefølgen ting brukes"-logikk).
**Verifisert grundig i scratch:** tom tee-liste på fersk custom-bane,
opprett+list tee på custom-bane, offisiell bane viser alle 8 importerte
tee-er, manuell tee-opprettelse avvist på offisiell bane, og — den
faktiske payoff-en — en deltaker lagt til en match på en custom-bane-økt
for FØRSTE gang noensinne (`POST .../matches/{id}/participants` lykkes
nå med en tee_id fra en nyopprettet custom-tee). `test_isolation.sql`
12/12. Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no`
upåvirket.
- **Blind draw-skjermen LIVE (2026-07-18):** syvende V0-skjerm,
`components/session-blind-draw.tsx`, ny rute
`/tournaments/[id]/sessions/[sessionId]`. To lag-kolonner, hver med sine
matcher ("flights"), legg til/fjern spiller+tee per plass (antall plasser
avhenger av format), lås-knapp med bekreftelse, avslørings-visning når
begge lag har låst. Program-skjermens økt-kort er nå klikkbare inn hit.
**Datamodell tilpasset fra V0s mock til API-ets faktiske sett-modell:**
V0 designet faste, indekserte "Slot"-arrays (kontrollert skjema-state);
API-et har verken PATCH på `match_participant` eller noen slot-indeks —
kun opprett/slett av en løs deltaker-mengde per side. Løst med en
"legg til spiller"-inline-form (samme mønster som spiller-/bane-type-ahead
ellers i appen) i stedet for faste dropdown-rader; funksjonelt likeverdig,
strukturelt riktigere for API-ets faktiske form.
**To reelle hull funnet og fikset FØR/UNDER integrering:**
1. Ingen `DELETE` fantes for `match_participant` — en kaptein kunne aldri
angre et valg før låsing uten å etterlate en foreldreløs rad. Ny
`DELETE /orgs/{id}/matches/{match_id}/participants/{participant_id}`
(samme ALREADY_LOCKED/NOT_ROSTERED_ON_TEAM-sjekker som opprett).
2. **"Skriv blindt"-hull, funnet under selve scratch-testingen (ikke bare
tenkt ut på forhånd):** forrige rundes org-admin-overstyring i
`team_authz.py` lot en organisator LEGGE TIL deltakere på et lag de
ikke selv er rostret på, men `app/blind_draw.py` sin `own_team_ids()`
sjekket KUN rostret spiller for SYNLIGHET — så organisatoren så aldri
sine egne tilføyelser igjen før begge lag hadde låst. Fikset ved å gi
`own_team_ids()` samme owner/admin-utvidelse som `user_may_act_for_
team()` — en org-admin ser nå BEGGE lag umiddelbart (konsistent med at
de uansett allerede har full tilgang, se forrige rundes resonnement),
mens en faktisk rostret kaptein fortsatt kun ser sitt eget lag før
reveal. Kun ett reelt kallsted (`matches.py` sin `list_matches`).
**Tredje, urelatert bug fanget under samme scratch-økt og fikset på
brukerens eksplisitte forespørsel:** `POST .../matches/{id}/participants`
krasjet med en rå 500 (`TypeError` i `handicap_engine.py` sin
`course_handicap_raw`) hvis spilleren manglet `handicap_index` OG
`use_handicap` var på (standard) — preeksisterende, ikke noe denne
runden introduserte. Fikset i `app/routers/matches.py` sin
`add_participant`: sjekker nå `handicap_index_snapshot IS NULL` EKSPLISITT
FØR innsetting når `use_handicap` er sann, avviser med en klar
`VALIDATION_FAILED` (400) i stedet for å krasje. Bekreftet at
`use_handicap=false` fortsatt tillater en spiller uten handicap
(scratch-spill), og at en spiller MED handicap fortsatt fungerer uendret.
**Verifisert grundig i scratch, flere runder:** DELETE-endepunktet
(fjern+idempotent 404 ved gjentak+409 etter lås), synlighetsfikset (org-
admin ser begge sider, rostret kaptein ser fortsatt kun eget lag),
null-handicap-fikset (avvist rent, scratch-modus upåvirket, normal
spiller upåvirket), full opprett-match→legg-til-deltaker→lås→avslør-
syklus ende-til-ende. `test_isolation.sql` 12/12 etter hver runde. Ekte
typesjekket produksjonsbuild.
**Rullet ut live**, bruker bekreftet eksplisitt: begge containere
bygget+redeployet (ingen migrasjon), `teeoff.no` upåvirket.
- **Backend-forarbeid for scorekort-skjermen, LIVE (2026-07-18):** to hull
funnet ved gjennomlesing av `scoring.py` FØR V0-prompten ble skrevet,
samme føre-var-mønster som resten av økten.
1. **Hull-/stroke-index-data for egendefinerte baner** (bevisst utsatt fra
tee-rundens status-notat) — `hole`-tabellen har ALDRI hatt noen vei inn
for custom-baner, som blokkerte `stroke`-scoringsmodus helt (`hole_
result`-modus trenger den ikke). Ny `GET/POST /orgs/{id}/courses/{id}/
holes` i `app/routers/courses.py` — engangs alle-18-på-en-gang
(avviser feil antall, avviser hvis banen allerede har hull, avviser på
offisielle baner som får hullene sine fra teeoff-import).
2. **`GET scorecard` viste kun UTLEDET vinn/tap/delt per hull, aldri de
faktiske tallene som var registrert** — umulig å bygge en skjerm som
viser gjeldende tilstand ved gjenlasting. Utvidet `Scorecard`-modellen
i `app/routers/scoring.py` med `stroke_entries`/`hole_result_entries`
(rå `hole_score`/`match_hole_result`-rader, nøyaktig ett av de to fylt
ut avhengig av øktens scoring_mode).
**Verifisert grundig i scratch:** hull-validering (feil antall avvist,
gjentatt oppsett avvist), og en FULL `stroke`-modus scoringsrunde med
ekte handicap-justert nettoberegning på en egendefinert bane for FØRSTE
gang noensinne (ulikt slagtall — 4 mot 5 — ga korrekt "halved" etter
handicap-utjevning), scorecard sin nye `stroke_entries` bekreftet å
returnere nøyaktig det som ble registrert. `test_isolation.sql` 12/12.
**Rullet ut live**, ingen migrasjon, kun `teecup_api` redeployet,
`teeoff.no` upåvirket.
- **Scorekort-skjermen LIVE (2026-07-18):** åttende V0-skjerm,
`components/session-scorecard.tsx`, ny rute `/tournaments/[id]/sessions/
[sessionId]/matches/[matchId]`. Ett hull i fokus om gangen (banebruk,
ikke et regneark) — store slag-steppere for `stroke`-modus, tre-valgs
vinner-knapper for `hole_result`-modus, hull-chip-navigasjon, kollapsbar
full oversikt, feiret "avgjort"-banner som låser alt til read-only.
Blind draw sin avslørte visning lenker nå til hver match sitt scorekort.
**Ingen nye backend-hull denne runden** — forrige rundes forarbeid
(hull-endepunkter + `stroke_entries`/`hole_result_entries` i scorecard)
dekket akkurat det skjermen trengte.
**Verifisert grundig i scratch, inkludert et fullt oppsett som speiler
NØYAKTIG frontend-ens egen last-sekvens** (sessions→teams→matches→
holes→scorecard, deretter en ekte `POST hole-scores` for begge spillere
på hull 1 og en refetch): status_text/derivert resultat oppdaterte seg
korrekt ("1 UP (A)", hull 1 → "a"), alle feltnavn stemte eksakt med
TypeScript-typene uten justering. `test_isolation.sql` 12/12, ekte
typesjekket build.
**Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket.
- **Leaderboard-backend LIVE (2026-07-18):** ny `GET /orgs/{id}/
tournaments/{id}/leaderboard` i `app/routers/tournaments.py` — summerer
poeng på tvers av ALLE økter/matcher i turneringen, både total og
per-økt-delsum.
**Reelt korrekthetshull funnet OG designet rundt FØR koden ble skrevet,
ikke oppdaget i ettertid:** `match.team_a_id`/`team_b_id` settes PER
MATCH ved opprettelse, ikke garantert konsistent på tvers av matcher —
en organisator kunne i prinsippet opprettet match 1 med team_a=Rød og
match 2 med team_a=Blå. En naiv summering av `points_side_a`/`points_
side_b` ville da blandet sammen poeng fra to ULIKE fysiske lag. Løst ved
å ALDRI summere på "a"/"b"-labelen — kun på det ekte lag-id-et
(`points_by_team: dict[team_id, float]`, både i totalen og per økt).
**Verifisert presist, ikke bare "kjørte uten feil":** bygget et scratch-
scenario med 3 matcher over 2 økter der match 2 sin team_a/team_b
BEVISST var byttet om i forhold til de to andre — satte poeng direkte
(Rød vinner alle tre, ett delt) og bekreftet at leaderboardet likevel ga
riktig total (Rød 2.5, Blå 0.5) og riktig per-økt-delsum, til tross for
swap-en. `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api`
redeployet, `teeoff.no` upåvirket. Frontend-skjermen gjenstår.
- **Leaderboard-skjermen LIVE (2026-07-18), samme dag:** niende og siste
V0-skjerm i "bygg i rekkefølgen ting brukes"-serien for kamp-play-flyten.
`components/tournament-leaderboard.tsx`, ny rute `/tournaments/[id]/
leaderboard`. Stort scoreboard-kort med de to lagenes totalpoeng
(lederen fremhevet), pluss en per-økt poeng-fordelingsliste med
fullført/ikke-startet-status. V0 la selv til et tredje faneelement
("Leaderboard") i BÅDE program- og roster-skjermens fanerad denne
runden — portert inn i de LIVE versjonene av begge (med riktig `org`-
parameter, som V0s eksport som vanlig manglet).
**Ingen nye backend-hull** — forrige rundes leaderboard-endepunkt dekket
akkurat det skjermen trengte.
**Verifisert i scratch:** full datamodell-runde (samme felt-for-felt
som backend-verifiseringen dagen før) OG et eget tomtilstand-scenario
(fersk turnering med 2 lag, 0 økter — `sessions: []`, `matches_total: 0`)
for å bekrefte at "ingen økter opprettet ennå"-meldingen vises riktig
i stedet for å krasje på et tomt array. `test_isolation.sql` 12/12, ekte
typesjekket build.
**Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket.
**Hele "bygg i rekkefølgen ting brukes"-serien for match-play-flyten er
dermed komplett:** oppsett (lag/roster) → program → blind draw →
scorekort → leaderboard.
- **Offisiell bane-import: idempotent + tydeligere navn, LIVE (2026-07-18),
rapportert av brukeren som faktisk brukte funksjonen på ekte
produksjonsdata:** to reelle problemer, begge funnet ved å lese (kun
lesing) de faktiske dataene for "De Gamle er Eldst" FØR noe ble antatt.
1. **`POST .../courses/official-import` var IKKE idempotent** — å
importere samme bane på nytt (helt vanlig: flere økter spilles ofte
på samme bane) ga en 409 `DUPLICATE`-feil (migrasjon 010 sin sperre)
i stedet for å bare gi tilbake den allerede importerte banen. Fikset:
sjekker nå `external_course_ref` FØR noe teeoff-kall gjøres — finnes
banen fra før, returneres den eksisterende raden direkte (også
raskere, og robust mot at teeoff er nede akkurat da).
2. **Lagret navn var kun selve banens navn ("Hovedbanen"), ikke hvilken
klubb** — ubrukelig til å skille baner fra hverandre, siden mange
klubber navngir hovedbanen sin identisk. Fikset: navnet kombineres nå
til "{anlegg} – {bane}" (f.eks. "Tjøme Golfklubb – Hovedbanen") ved
import.
**Reell konsekvens av hull #1 funnet i produksjonsdata:** brukeren hadde,
mens hen forsøkte å søke opp Tjøme via det ØVERSTE banefeltet (som søker
organisasjonens EGNE baner, ikke teeoff), ved et uhell trigget «Opprett
ny bane: «Tj»»-snarveien og fått en tom, søppel `custom`-bane hengende på
Foursome-økten -- en ekte forvekslingsfelle mellom de to adskilte
bane-søkeflatene (eget vs. teeoff), ikke en kodefeil i seg selv.
**Data ryddet opp i EKTE `teecup_db`, bruker bekreftet eksplisitt:**
Foursome-økten pekt om til den allerede importerte, ekte Tjøme-banen
(samme bane som Fourball-økten allerede brukte -- nøyaktig det
organisatoren egentlig ønsket), søppel-«Tj»-banen slettet (bekreftet
ingen matcher/tee-er/hull hang på den FØR sletting), og den ekte banens
navn oppdatert til "Tjøme Golfklubb – Hovedbanen". Verifisert i etterkant
at begge økter nå peker til samme, korrekt navngitte bane.
**Verifisert mot ekte teeoff_api i scratch FØR utrulling:** importerte
Borregaard på nytt to ganger — andre kallet ga nøyaktig samme course-id
(ikke en duplikat-rad), navnet kom ut som "Borregaard Golfklubb –
Hovedbanen". `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api`
redeployet, `teeoff.no` upåvirket.
- **`PATCH`/`DELETE` for økter, LIVE (2026-07-18), samme dag:** brukeren
spurte rett etter opprydningen om det i det hele tatt var mulig å
rette/slette en feiloppsatt økt — det var det ikke (kun `POST`/`GET`
fantes). Ny `PATCH /orgs/{id}/sessions/{id}` (`app/routers/
tournaments.py`) for enkle felt (navn, klokkeslett/intervall, starthull,
poeng, handicap-brytere) via vanlig `exclude_unset`-mønster, PLUSS en egen
gren for bane-bytte. Ny `DELETE /orgs/{id}/sessions/{id}` — kun tomme
økter (ingen matcher), avviser med 409 ellers (bruk PATCH til å korrigere
i stedet).
**Banebytte-scenarioet brukeren selv reiste** ("5 hull spilt, oppdager
feil bane — slagene er ekte, utregningen er trolig feil") krevde egen
design: `match_participant.tee_id` peker til en tee som HØRER til den
gamle banen. Løst med `_remap_course()` — finner en tee med samme
navn+kjønn på den nye banen for hver allerede tillagte deltaker, flytter
dem dit, og avviser HELE bane-byttet tydelig (400, ingenting skrevet,
bekreftet transaksjonell rollback) hvis den nye banen mangler en
tilsvarende tee. Etter et vellykket bytte: handicap regnes om for alle
berørte deltakere, og matchstatus/poeng regnes om for HVER match i
økten — **bevisst uavhengig av om matchen allerede er avgjort** (brukeren
bekreftet eksplisitt at en bane-korrigering skal kunne endre et allerede
cachet resultat). `recompute_and_cache_match_state` i `app/routers/
scoring.py` gjort delt (fjernet ledende understrek) for gjenbruk fra
tournaments.py.
**Reelt, urelatert funn underveis i scratch-testingen, IKKE fikset:**
`tee_rating` lages i dag ALLTID kun med `full_18`-omfang (både ved
teeoff-import og manuell tee-opprettelse) — en økt satt til `front_9`/
`back_9` i `stroke`-modus kan derfor ALDRI få handicap beregnet
(`compute_and_store_side_handicaps` sin `tee_rating`-join finner aldri
noen rad), og dermed aldri avgjøre noen hull. `hole_result`-modus
upåvirket. Flagget til bruker, bevisst latt urørt denne runden.
**Verifisert grundig i scratch:** enkelt feltbytte (kun navn), fullt
banebytte-scenario bygget nøyaktig som brukerens eksempel (10 hull spilt
under feil bane, matchen allerede avgjort 10&8, PATCH til riktig bane →
tee-er ombyttet korrekt, handicap endret fra 7/9 til 10/13 under den nye
banens rating, matchstatus regnet på nytt med UENDREDE rå slagtall),
avvist bane-bytte ved manglende tee-match (bekreftet full rollback,
også av det urelaterte navnefeltet i samme kall), DELETE avvist på økt
med match (409) og godtatt på tom økt (204). `test_isolation.sql` 12/12.
Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no` upåvirket.
Frontend (rediger-/slett-knapper i program-skjermen) ikke bygget ennå —
kun backend-kapasiteten denne runden.
- **Rediger/slett-UI for økter LIVE (2026-07-19):** V0-utvidelse av den
eksisterende Program-skjermen (ingen ny rute) — "..."-meny (samme
DropdownMenu-mønster som roster-skjermens per-spiller-handlinger) med
"Rediger"/"Slett" per øktkort. Rediger bytter kortet til et inline-skjema
(samme felt som PATCH støtter: navn, poeng, starthull, klokkeslett/
intervall, bane). Slett viser en bekreftelse; treffer den ekte 409-en
(økt har matcher), vises backend sin egen feiltekst i stedet for en
forhåndsberegnet klient-tilstand.
**Reell regresjon funnet OG UNNGÅTT i selve V0-eksporten, ikke i
etterkant:** denne rundens V0-prompt handlet kun om rediger/slett, men
eksporten hadde samtidig (utilsiktet) FJERNET bane-feltet fra "Legg til
økt"-skjemaet helt — nye økter ville stille blitt satt til en hardkodet
mock-bane. Fanget under diff-mot-live-treet FØR noe ble tatt inn (samme
rutine som alltid) — `CreateSessionCard` (med sitt ekte bane-søk/
teeoff-import) beholdt fullstendig urørt; kun de nye rediger/slett-
delene ble hentet inn.
**Bane-bytte i redigeringsskjemaet er bevisst ENKLERE enn opprett-
skjemaets bane-felt** — kun velg blant organisasjonens eksisterende
baner eller hent fra teeoff, INGEN "opprett ny bane: X"-snarvei her
(PATCH sin `course_id` må være en ekte, allerede eksisterende bane, ikke
en tekststreng) — unngår at samme "Tj"-forvekslingsfelle fra forrige
banebytte-hendelse kan gjenta seg via redigeringsveien.
**Verifisert:** ekte PATCH med nøyaktig skjemaets feltform, ekte DELETE
(204 tom økt, 409 med matcher — bekreftet at `{"detail":{"code",
"message"}}`-formen leses riktig av UI-et), `test_isolation.sql` 12/12,
ekte typesjekket build. Rullet ut live, ren frontend-endring, `teeoff.no`
upåvirket.
- **Invitasjonskode + ledende side + projisert stilling, BACKEND LIVE
(2026-07-19, ADR-020):** brukeren reiste tre relaterte hull rett etter at
"bygg i rekkefølgen ting brukes"-serien var ferdig: (1) ingen vei inn til
en turnering for en spiller som bare har fått muntlig beskjed, (2)
leaderboardet viser kun faktisk opptjente poeng, ikke hva stillingen ville
blitt om pågående matcher holder seg, (3) ingen fargekoding i matchlister
for hvem som leder. Full ADR-020 skrevet (4 delbeslutninger, se
ARCHITECTURE_DECISIONS.md) — nøkkelbeslutning bekreftet eksplisitt av
bruker FØR bygging: en invitasjonskode OVERSTYRER `tournament.visibility`
helt (koden ER selve invitasjonen, ikke en snarvei som fortsatt krever
eksisterende tilgang).
Ny migrasjon `011_join_code_and_leading_side.sql`: `tournament.join_code`
(6 tegn, alfabet uten 0/O/1/I, globalt unikt, backfylt for eksisterende
rader), `match.leading_side` (cachet fortegn av `MatchState.lead`, samme
mønster som `status_text`/`points_side_a/b`), fjerde
`SECURITY DEFINER`-bro `public_tournament_by_code()` (etter
`public_tournament_org` 007, `link_player_by_email` 008,
`public_org_by_slug` 009).
**Backend bygget:** `create_tournament` genererer koden (retry-løkke ved
kollisjon, astronomisk usannsynlig med 33^6 kombinasjoner). Ny
`GET /public/tournaments/by-code/{code}` (MÅ registreres FØR
`/{tournament_id}` i routeren, ellers tolkes "by-code" som en ugyldig
UUID). `GET /public/tournaments/{id}`, `GET .../sessions` og
`POST .../register` godtar alle en valgfri `code`-parameter som — når den
matcher — hopper `_check_visibility()` helt over.
`recompute_and_cache_match_state` (scoring.py) cacher nå `leading_side`
ved HVER hull-innsending, ikke bare ved avgjørelse. Leaderboard-
endepunktet fikk `projected_points`/`projected_points_by_team`:
avgjorte matcher bidrar likt til faktisk og projisert, ikke-avgjorte gir
hele `points_per_match` til `leading_side` (delt 0,5/0,5 ved "AS"/ikke
startet) — speiler hvordan Ryder Cup-TV-dekning viser "hvis det sluttet
nå".
**Reell hendelse underveis, håndtert transparent (ikke skjult):** en
feilformulert `docker exec teeoff_db env | grep -i POSTGRES`-kommando
(ment å liste variabelNAVN) fanget opp `POSTGRES_PASSWORD` sin VERDI også,
siden selve nøkkelnavnet matchet søkemønsteret — eksponerte `teeoff_db`
sitt superbruker-passord (`teeoff_admin`) i verktøyresultatet. Alvorligere
enn de to tidligere passord-hendelsene i prosjektet siden dette er
superbrukeren for HELE den delte Postgres-klyngen (teeoff OG teecup), ikke
en enkelt tjeneste-credential. Flagget til bruker umiddelbart, som valgte
å rotere. Rotert trygt UTEN å noensinne re-eksponere gammel ELLER ny verdi
i noe synlig kommandoresultat: `ALTER ROLE` kjørt via lokal
Unix-socket-`trust`-auth (bekreftet ved å lese `pg_hba.conf`, ingen
hemmelighet involvert i den sjekken) — krevde altså IKKE det gamle
passordet i det hele tatt. Nytt passord generert med `openssl rand -hex
32` (hex, ikke base64 — unngår SAMME klasse URL-enkodings-felle som
`TEECUP_DATABASE_URL`-hendelsen tidligere, siden verdien også ligger i en
`postgres://`-DSN). `/opt/teeoff/.env` sine TO forekomster
(`POSTGRES_PASSWORD` og `DATABASE_URL`) oppdatert med `sed`-mønstre som
ALDRI leser/skriver ut den gamle verdien. `teeoff_api`/`teeoff_worker`
service-nøklene i `docker-compose.prod.yml` viste seg å hete `api`/
`worker` (ikke `teeoff_api`/`teeoff_worker` — det er kun
`container_name`), samme "service-nøkkel ≠ container-navn"-fallgruve som
nettverksalias-hendelsen fra containeriseringsrunden. `docker compose up
-d --force-recreate api worker` gjenskapte OGSÅ `teeoff_db` selv (ikke
eksplisitt navngitt) — Compose oppdager konfigurasjonsendring
(`${POSTGRES_PASSWORD}` i `db`-tjenestens egen `environment:`) og
gjenskaper uansett hvilke tjenester som ble navngitt. Verifisert grundig
ETTERPÅ: `teeoff_db`-loggen viste "Skipping initialization" (datavolum
urørt, ikke reinitialisert) + ren oppstart, `teeoff_api`/`teeoff_worker`
ren oppstart uten en eneste feil-/auth-/passord-linje i hele loggen,
`teeoff.no` OG `teecup.teeoff.no/health` begge `200` etterpå.
**Scratch-verifisert grundig** (fersk `teecup_scratch` 001→011,
isolert `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs
API-container): `test_isolation.sql` 12/12 (måtte først rettes — tre
RÅ `INSERT INTO tournament`-steder i selve testfilen predaterte
`join_code` og traff den nye NOT NULL-constrainten, rettet med
dummy-koder). Full ende-til-ende-runde: to turneringer opprettet, ulike
koder bekreftet; `by-code`-oppslag bekreftet for kjent OG ukjent kode
(404); anonym lesing av en `org`-synlig turnering BLOKKERT uten kode
(403 NOT_VISIBLE), TILLATT med riktig kode (inkl. case-insensitivt),
FORTSATT blokkert med feil kode; samme mønster bekreftet for
`POST .../register`. Full hull-for-hull-simulering av en singel-match
(hole_result-modus): `leading_side`/`status_text` fulgte hverandre
eksakt gjennom "1 UP (A)" → "AS" (leading_side=null) → avgjort "9&7
(A)", leaderboardets `projected_points` traff nøyaktig 1.0/0.0 mens A
ledet, 0.5/0.5 ved "AS", og ble likt `points`/`projected_points` (begge
1.0/0.0) etter avgjørelse.
**Rullet ut mot ekte `teecup_db` 2026-07-19**, bruker bekreftet
eksplisitt: migrasjon 011 kjørt (eneste eksisterende turnering fikk
automatisk generert kode), `test_isolation.sql` fortsatt 12/12, kun
`teecup_api` redeployet (ingen frontend-endring i denne del-runden),
`teeoff.no` upåvirket.
**Frontend fullført samme dag, egen del-runde:** `login-form.tsx` fikk et
eget kode-modus (`JoinByCode`) — «Har du en invitasjonskode?»-lenke bytter
ut e-post-skjemaet, slår opp `/public/tournaments/by-code/{code}` og
navigerer til `/t/{id}?code=...` med Next sin `useRouter`. `code`
query-param tres gjennom hele veien: `app/t/[id]/page.tsx` leser
`searchParams`, `public-tournament.tsx` sender den med på BÅDE
info-/sessions-lesingen og selve `POST .../register` (ikke bare det
første oppslaget som tok deg dit).
`tournament-detail.tsx` viser koden i en egen kopier-chip i headeren —
ingen enkelt-turnering-`GET` fantes, så komponenten henter i stedet hele
org-ens turneringsliste (som allerede bærer `join_code`) og finner egen
rad, i stedet for å legge til et nytt endepunkt kun for dette.
`tournament-leaderboard.tsx` fikk en ny `SegmentedBar`-komponent — ETT
fargesegmentert rektangel per bar (ikke tall side om side), proporsjonalt
med hvert lags poeng, 50/50 nøytralt ved 0-0. To slike bares rett under
headeren: "Stilling nå" (faktisk) og "Projisert (hvis pågående matcher
holder seg)" — den EKSISTERENDE store tall-scoreboarden beholdt uendret
lenger ned som detaljvisning.
`session-blind-draw.tsx` sin `RevealedView`: matchkortet får nå en farget
toppkant (leaderens `team.color`) og en `status_text`-chip (fylt farge
ved avgjort match m/ konfetti-ikon, lys tone ved pågående) i stedet for
kun klokkeslettet; `RevealSide` for ledende side får en svak fargetonet
bakgrunn. `leading_side`/`status_text`/`points_side_a/b` lagt til
`ApiMatch`-typen (var der allerede i API-et, bare ikke konsumert
frontend-siden før nå).
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
som deployes, ikke dev-server) kompilerte rent, alle 10 ruter listet.
Rullet ut (kun `teecup_frontend`, ingen backend-endring i denne delen),
`teecup.teeoff.no/dashboard` og `/` → 200, `teeoff.no` upåvirket. Ekte
smoke-test i produksjon: `by-code`-oppslag for "De Gamle er Eldst" sin
faktiske kode ga riktig turnering-id, `/t/{id}?code=...` ga 200.
**ADR-020 er dermed helt ferdig** (backend + frontend, alle fire
del-ønsker: kode-basert oppdagelse, kode-felt på login, projisert
stilling, fargekoding av matcher) — bortsett fra kode-regenerering, som
er bevisst utsatt (se FEATURE_BACKLOG.md).
**Reell driftshendelse underveis** (mellom backend- og frontend-delen,
under scratch-oppsett): en feilformulert `grep -i POSTGRES`-kommando
eksponerte `teeoff_db` sitt superbruker-passord ved et uhell. Flagget
umiddelbart, brukeren valgte å rotere — se detaljene under
backend-avsnittet over for hele hendelsen og hvordan roteringen ble
gjennomført uten å noensinne re-eksponere gammel eller ny verdi.
- **Sesjons-bug diagnostisert og FIKSET, LIVE (2026-07-19):** brukeren
rapporterte at hen måtte be om ny magic-link-kode ved HVERT besøk til
`teecup.teeoff.no`, til tross for ADR-009s 30-dagers sesjonscookie. Bad
brukeren sjekke den EKTE cookien i nettleseren fremfor å gjette — kom
tilbake korrekt satt i alle henseender (`Expires` 30 dager frem,
`Secure`/`HttpOnly`/`SameSite=Lax`). Rot-årsaken var derfor IKKE cookien
eller backend-en: `frontend/app/page.tsx` (rot-siden) viste ALLTID
innloggingsskjemaet uten noensinne å sjekke om en gyldig sesjon allerede
fantes. `Dashboard`-komponenten sjekker `/auth/me` og sender til `/` ved
MANGLENDE sesjon, men ingen kode gjorde det motsatte — en bruker som
besøkte roten direkte (i stedet for å navigere til `/dashboard`) så
derfor alltid innloggingsskjemaet uansett sesjonsstatus.
**Fikset:** `page.tsx` gjort om til en async server-komponent som leser
sesjonscookien via `next/headers`, kaller `/auth/me` server-til-server
direkte mot `TEECUP_API_ORIGIN` (samme mønster som `generateMetadata` i
`app/t/[id]/page.tsx` — IKKE gjennom `next.config.mjs` sin `rewrites()`,
som kun gjelder nettleser-trafikk), og sender en allerede innlogget
bruker videre til `/dashboard` med `redirect()` FØR innloggingsskjemaet
når rendres.
**Verifisert presist mot den ekte, live stacken, med brukerens EGEN
ekte sesjonscookie** (ikke en syntetisk test): `curl` uten cookie mot
`https://teecup.teeoff.no/` ga `200` (skjemaet vises, riktig for en
anonym besøkende); samme kall MED den ekte cookien ga `307` til
`/dashboard` (riktig — sender en allerede innlogget bruker rett videre).
Ekte typesjekket produksjonsbuild kjørt FØR utrulling (build-outputet
viste selv at `/` nå er `ƒ` dynamisk i stedet for `○` statisk — bekrefter
at server-sjekken faktisk ble tatt i bruk). Rullet ut live, kun
`teecup_frontend`, ingen migrasjon, `teeoff.no` upåvirket.
**Samme runde:** brukeren stilte to oppfølgingsspørsmål om
autentisering/autorisasjon — «er flere-organisasjoner-eierskap tenkt
gjennom» (bekreftet: ja, ADR-002 fra dag én, ingen kodeendring nødvendig)
og et ønske om passord (valgfritt tillegg)+2FA. Fullt design skrevet som
**ADR-021** (passord/2FA) og **ADR-022** (dele/invitere/frasi seg
eierskap + superadmin) — se ARCHITECTURE_DECISIONS.md.
- **ADR-021 (passord/2FA) + ADR-022 (org-eierskap) BYGGET OG LIVE
(2026-07-19), samme dag:** brukeren ba om begge sammen («Bygg det»,
bekreftet eksplisitt at det gjaldt begge ADR-ene i samme runde). Ny
migrasjon `012_password_2fa_and_org_invitations.sql`: `app_user.
password_hash`/`two_factor_method`/`totp_secret`/`is_super_admin`, ny
tabell `two_factor_code` (samme hash-og-utløp-mønster som
`magic_link_token`), ny tabell `organization_invitation` (RLS
org-isolert, INGEN egen klikkbar aksept-lenke — godtas automatisk ved
neste innlogging med matchende e-post), femte
`SECURITY DEFINER`-bro `accept_pending_invitations_by_email()` (etter
`public_tournament_org` 007, `link_player_by_email` 008,
`public_org_by_slug` 009, `public_tournament_by_code` 011).
**Sesjons-STADIER innført i `app/auth.py`:** en sesjonscookie er ikke
nødvendigvis en full sesjon lenger — `create_session_token()` tar nå en
`stage`-parameter (`full`/`pending_2fa`/`must_enroll_2fa`), lagt inn som
et JWT-claim. `get_current_user` avviser eksplisitt alt annet enn `full`
ELLER en ELDRE token uten stage-claim i det hele tatt (utstedt før denne
runden — behandlet som `full` for bakoverkompatibilitet, ingen
eksisterende bruker logget brått ut). To nye avhengigheter:
`get_pending_user` (kun for 2FA-verifiseringsendepunktene) og
`get_current_or_enrolling_user` (godtar BÅDE en full sesjon — frivillig
2FA-oppsett fra kontoinnstillinger — OG `must_enroll_2fa` — tvunget
oppsett rett etter innlogging — samme oppsett-logikk dekker begge
veiene). `get_current_user_optional` fikk samme stage-sjekk for
konsistens.
**Passord (Argon2id, ikke bcrypt):** bevisst valg for å unngå bcrypt sin
stille 72-byte-trunkering, siden brukeren eksplisitt ba om korrekt
håndtering av spesialtegn/mellomrom. `POST /auth/set-password`/
`/remove-password`/`/login-password` — sistnevnte svarer med IDENTISK
401 uansett om e-posten finnes, mangler passord, eller passordet er
feil (samme anti-enumerering som magic-link).
**2FA (TOTP via `pyotp` ELLER e-post-engangskode via eksisterende SMTP,
brukerens eget valg):** `POST /auth/2fa/setup/start` genererer en
TOTP-secret UTEN å lagre den (rundturer til klienten, som ekkoer den
tilbake i `/setup/confirm` — unngår en halvferdig 2FA-tilstand i
databasen hvis brukeren forlater oppsettet). QR-kode generert
server-side (`qrcode`-biblioteket + eksisterende Pillow-avhengighet,
ingen ny ekstern tjeneste). `POST /auth/2fa/verify` fullfører en
PÅGÅENDE innlogging.
**Tvungen 2FA for org-eier/admin (ADR-021 Beslutning D):**
`user_requires_2fa_enrollment()` sjekket ved HVER innlogging (ikke bare
første gang, siden en bruker kan bli eier av en NY org etter at kontoen
allerede eksisterer uten 2FA) — verifisert eksplisitt: en fersk
org-eier uten 2FA ble korrekt blokkert fra all normal tilgang (401) og
tvunget inn i oppsett-flyten før noe annet ble tilgjengelig.
**Organisasjonseierskap (ADR-022):** `POST/GET/DELETE
/orgs/{id}/invitations` (owner→enhver rolle, admin→KUN member — ellers
en privilegie-eskaleringsvei), `PATCH/DELETE /orgs/{id}/memberships/
{id}` (owner kan endre/fjerne hvem som helst; en bruker kan ALLTID
SENKE egen rolle selv — aldri heve den, ville vært selv-forfremmelse —
og alltid forlate selv), «siste eier»-vern (409 `LAST_OWNER`, `FOR
UPDATE`-lås mot race på alle eier-rader, samme TOCTOU-mønster som
ADR-011s to-lags-grense). Superadmin (`app_user.is_super_admin`, KUN
manuelt DB-tildelt — bevisst INGEN API-vei til å gi seg selv eller
andre flagget) får en parallell autorisasjonssti
(`get_superadmin_user`) som kan sette medlemskap på ENHVER org,
uavhengig av eget medlemskap — bevisst avgrenset til nøyaktig dette,
ikke generell tilgang til andres turnering-/spillerdata.
**Tre reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde
produksjon:**
1. `verify_magic_link` sendte en rå asyncpg-`UUID` (ikke streng) videre
til sesjonsutstedelse — `jwt.encode()` sin JSON-serialisering
krasjet rått (500) på selve innloggingen. Fant umiddelbart ved første
reelle innloggingstest. Rettet med en eksplisitt `str()`.
2. OG 3. Både `2fa/setup/confirm` og `2fa/verify` kalte først den delte
`_issue_login_result()`-hjelpefunksjonen (ment for PRIMÆR
autentisering) EN GANG TIL etter at 2FA nettopp var bekreftet — som
så (korrekt, men feil kontekst) at `two_factor_method` nå var satt
og krevde EN NY runde med 2FA for akkurat den samme innloggingen,
en uendelig løkke. Fant ved å faktisk fullføre hele innloggings-
syklusen med ekte genererte TOTP-koder (`pyotp` i test-scriptet),
ikke bare ved å lese koden. Rettet ved at begge endepunktene nå
utsteder en full sesjon DIREKTE etter vellykket 2FA-bekreftelse,
ikke via gjenbruk av den generelle sjekken.
**Frontend:** `login-form.tsx` fikk en tredje modus (passord, ved siden
av magic-link og ADR-020s invitasjonskode-modus). Ny delt
`two-factor-flow.tsx` (`TwoFactorVerifyForm`/`TwoFactorSetupForm`),
brukt av BÅDE `login-form.tsx` og `verify-form.tsx` siden begge
primær-autentiseringsveiene kan returnere samme 2FA-mellomtilstand. Ny
`/account`-skjerm (sett/fjern passord, aktiver/deaktiver 2FA) og ny
`/orgs/[id]/members`-skjerm (invitere, endre rolle, fjerne/forlate),
begge lenket fra dashbordets header/org-visning. `next.config.mjs` sin
`rewrites()` utvidet med `/superadmin/:path*` (ADR-016s konsekvens for
enhver ny API-prefiks, selv om ingen frontend-UI faktisk bruker den
ennå).
**Verifisert grundig mot fersk `teecup_scratch`** (001→012,
`test_isolation.sql` 12/12): full magic-link-bakoverkompatibilitet
(eksisterende flyt uendret for brukere uten 2FA), passord med ekte
spesialtegn/mellomrom/æøå satt og brukt til pålogging, full TOTP-runde
(oppsett→bekreft→logg ut→logg inn→krev 2FA→verifiser med ekte
`pyotp`-generert kode), full e-post-2FA-runde (samme mønster, feil kode
avvist, kode ikke gjenbrukbar), tvungen 2FA-registrering for ny
org-eier, invitasjon→auto-aksept ved førstegangsinnlogging,
rolle-eskalering-forsøk avvist på tre distinkte måter (medlem kan ikke
invitere, admin kan ikke gi eierskap, bruker kan ikke forfremme seg
selv), siste-eier-vern (både PATCH og DELETE), duplikat-invitasjon
avvist, superadmin-sti fungerer/avvises riktig i begge retninger. Ekte
typesjekket produksjonsbuild av frontend (alle nye ruter listet).
**Rullet ut mot ekte systemer 2026-07-19**, bruker bekreftet eksplisitt:
migrasjon 012 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt
12/12, begge containere (`teecup_api`, `teecup_frontend`) redeployet og
bekreftet ren oppstart, `/health` og `/dashboard` fortsatt 200 (eksisterende
sesjoner uendret av stage-bakoverkompatibiliteten), `/auth/login-password`
bekreftet nåbart over ekte https, `teeoff.no` upåvirket.
**ADR-021 og ADR-022 er dermed begge helt ferdig** — backend + frontend.
Bevisst utenfor omfang: dedikert superadmin-UI (brukes via API av en
betrodd operatør), SMS som 2FA-metode.
- **Program-skjerm: tydeligere klikk-hint på øktkort (2026-07-19):** brukeren
påpekte at ingenting i grensesnittet indikerte at et øktkort er klikkbart
inn til blind draw-skjermen. Lagt til en synlig "Sett opp flights og lås
oppstilling →"-rad nederst i hvert kort (`components/tournament-program.tsx`).
Ren frontend-endring, ingen backend-rørt.
- **Brukerroller: kaptein som reell autorisasjon, deltaker-avgrenset scoring
(2026-07-19, ADR-023):** direkte oppfølging av det lenge åpne
"Brukerroller"-punktet i FEATURE_BACKLOG.md. Fire beslutninger avklart
eksplisitt med bruker (AskUserQuestion) før bygging — alle anbefalte valg.
**Bygget:** `app/team_authz.py` skrevet om — `user_is_team_captain`
(erstatter `user_may_act_for_team`) krever `is_captain=true` på
`team_roster` (eller org-eier/admin) for å legge til/fjerne deltakere og
låse et lag (`matches.py`); ny `user_is_match_participant` krever en ekte
`match_participant`-rad for brukeren i AKKURAT den matchen (valgfritt
side-spesifikk via `team_side`) for å føre/korrigere score (`scoring.py`)
— uavhengig av kapteinmerket. Nye feilkoder `NOT_TEAM_CAPTAIN` og
`NOT_MATCH_PARTICIPANT` (erstatter `NOT_ROSTERED_ON_TEAM` på disse fem
stedene). `app/routers/tournaments.py` sin `PATCH`/`POST .../roster`
håndhever nå "kun én kaptein per lag" (fjerner automatisk forrige
kapteins merke i samme transaksjon).
**Reelt funn FØR utrulling, ikke antatt:** sjekket (kun lesing, superbruker
mot ekte `teecup_db`) om noen eksisterende lag ville blitt låst ute av en
ren kaptein-only-regel — "De Unge" i "De Gamle er Eldst" har i dag 0 av 2
roster-rader merket kaptein. Designet derfor en bevisst fallback i
`user_is_team_captain`: har laget INGEN utpekt kaptein ennå, godtas enhver
rostret spiller i stedet for å låse laget helt ute. Ingen lag hadde flere
kapteiner, så "kun én kaptein"-håndhevelsen krevde ingen data-opprydning.
**Reell bug funnet OG fikset UNDER scratch-testing:** `user_is_match_
participant` sin SQL sammenlignet `mp.team_side` (enum-kolonne) direkte
mot en tekst-parameter uten cast når `team_side=None` (hole_result-modus)
— ga en rå 500 (`UndefinedFunctionError: operator does not exist: team_side
= text`). Rettet med et eksplisitt `mp.team_side::text = $3`.
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container):
et 15-punkts Python/httpx-testskript som simulerte fire innloggede
brukere (organisator + tre rostrede spillere på to lag) gjennom hele
syklusen — lag uten kaptein tillater enhver rostret (fallback bekreftet),
kaptein utpekt fjerner andre rostredes rettighet, "kun én kaptein" bekreftet
(ny kaptein avsetter automatisk forrige), org-admin fungerer uendret
uavhengig av kaptein, og scoring (`hole_result`-modus) bekreftet begrenset
til faktiske matchdeltakere (en kaptein som IKKE selv spiller matchen ble
korrekt avvist med `NOT_MATCH_PARTICIPANT`, mens en faktisk deltaker og
org-admin begge fikk føre score). Måtte også oppdage og legge til et
forutsetning-steg underveis: `get_authorized_org` krever
`organization_membership` for ALLE org-scopede endepunkter uansett — en
rostret spiller må derfor også være invitert som org-medlem (minimum
'member', ADR-022s invitasjonsflyt) for i det hele tatt å nå
team_authz-vurderingen; ikke en bug, men en forutsetning testskriptet
først manglet. `test_isolation.sql` 12/12 uendret (ingen skjemaendring).
Ekte typesjekket produksjonsbuild av frontend (øktkort-hintet fra samme
runde) kjørt og bekreftet, alle 11 ruter listet.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
begge containere boot-et rent (`Application startup complete`, Next.js
`Ready`), `/health` og `/dashboard` → 200, `teeoff.no` upåvirket.
- **Walkover/konsesjon LIVE (2026-07-19, ADR-024):** direkte oppfølging av
Brukerroller-runden samme dag — brukeren ba eksplisitt om å ta fatt på
dette som naturlig neste steg. Løser det lenge kjente hullet: en side som
aldri stiller nok spillere fikk aldri beregnet handicap og matchen kunne
derfor aldri avgjøres — hang uendelig.
Fire beslutninger avklart eksplisitt med bruker (AskUserQuestion) før
bygging — tre anbefalte valg, ett (omfang: match+turnering-nivå samtidig,
ikke bare match) valgt utover anbefalingen.
**Bygget:** ny `apply_concession`-hjelpefunksjon i `app/routers/
scoring.py` (skriver til de samme fire kolonnene som
`recompute_and_cache_match_state` -- `status_text`/`points_side_a/b`/
`leading_side` -- men direkte, ikke utledet fra hull). Ny
`POST /orgs/{id}/matches/{id}/concede` (kun kaptein for det TAPENDE laget,
speiler ekte golf-etikette -- du gir bort DITT tap, krever ikke seier på
motstanderens vegne -- eller org-admin, gjenbruker `user_is_team_captain`
fra ADR-023 uendret). Ny `POST /orgs/{id}/tournaments/{id}/concede`
(`app/routers/tournaments.py`) som gir opp ALLE ikke-avgjorte matcher
laget har i turneringen i én operasjon -- v1s to-lags-grense (ADR-011)
gjør dette trivielt (bare én motstander uansett), `FOR UPDATE`-låser alle
berørte match-rader (samme race-vern som ellers). Kan erklæres uansett
hvor mange hull som allerede er registrert (match-play teller kun
seier/tap/delt for poeng, ikke marginen) -- allerede registrerte hull i
`hole_score`/`match_hole_result` forblir urørt, kun matchens
avgjørelses-felt endres.
**Frontend:** `session-scorecard.tsx` fikk en kollapsbar
"Gi opp matchen (walkover)"-seksjon (to knapper, én per lag, med
bekreftelsessteg) synlig når matchen ikke er avgjort.
`tournament-detail.tsx` sin `TeamPanel` fikk en tilsvarende "Gi opp resten
av turneringen for laget"-knapp nederst i hvert lagkort. Ingen
klientside-forhåndsfiltrering på kapteinstatus noe sted -- begge knappene
vises alltid, en 403 fra backend vises bare som vanlig feiltekst (samme
mønster som resten av appen).
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs
API-container): to separate 15-punkts Python/httpx-testløp. Match-nivå:
vinnende lags kaptein NEKTES å konsedere på vegne av det tapende laget
(kan ikke kreve seier på andres vegne), tapende lags kaptein FÅR, allerede
avgjort match avvist 409, fremmed team_id avvist 400, konsesjon ETTER at
ett hull allerede er registrert bekreftet å fungere OG bekreftet at det
registrerte hull-resultatet forblir synlig i scorekortet etterpå (ikke
overskrevet), org-admin FÅR konsedere direkte uavhengig av kapteinmerke.
Turnering-nivå: feil lags kaptein nektes, riktig kaptein FÅR gi opp
resten (kun de faktisk ikke-avgjorte matchene telles -- allerede avgjorte
matcher fra match-nivå-testene i samme løp ble korrekt hoppet over),
gjentatt kall er trygt (0 nye, idempotent i praksis), ukjent team_id gir
404. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). Ekte
typesjekket produksjonsbuild av frontend kjørt og bekreftet, alle 11
ruter listet.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
begge containere boot-et rent, `/health` og `/dashboard` → 200,
`teeoff.no` upåvirket.
- **Turnering-status via API, SCRATCH-VERIFISERT (2026-07-19):** brukeren
valgte dette som neste steg etter walkover/konsesjon-runden (fikk velge
mellom denne lille opprydningen og å starte Kommunikasjon-runden). `status`
(draft/active/completed/archived) har ligget i skjemaet siden migrasjon
001, men INGEN endepunkt kunne endre det — kun `INSERT`-defaulten `'draft'`
fra `create_tournament`.
**Bygget:** lagt til i `TournamentUpdate` (`app/routers/tournaments.py`),
settes via det eksisterende generiske `PATCH /orgs/{id}/tournaments/{id}`
(`exclude_unset`-mønsteret, ekte PATCH-semantikk uendret).
**Reelt funn UNDER scratch-testing, ikke antatt riktig på forhånd:**
`status` er -- ulikt `visibility` (som er ren `text`+`CHECK`) -- en EKTE
Postgres ENUM-type (`tournament_status`). Den generiske
`set_clauses`-byggeren (`f"{key} = ${i}"`) hadde derfor trengt et
eksplisitt cast for at asyncpg sin ukjent-typede parameter skulle løses
riktig mot en enum-kolonne -- lagt til en spesialsjekk (`::tournament_status`
kun for `status`-nøkkelen) FØR jeg antok mekanismen "bare fungerer" fordi
den gjør det for de andre feltene.
Bevisst INGEN tilstandsmaskin/overgangsregler bygget (kan f.eks. gå fra
`completed` tilbake til `draft` fritt) -- samme tillitsnivå som resten av
appen, ikke etterspurt.
**Frontend:** `tournament-status-badge.tsx` fikk en ny redigerbar
`TournamentStatusPicker` (dropdown over de fire verdiene, optimistisk
UI-oppdatering med rollback ved feil), koblet inn i `tournament-detail.tsx`
sin header ved siden av invitasjonskode-chipen. Den eksisterende
skrivebeskyttede `TournamentStatusBadge` (dashbordets kortliste) urørt.
**Verifisert i scratch:** ny turnering får riktig default `draft`, PATCH
til `active` bekreftet enum-castet faktisk løser problemet, PATCH med
status+et annet felt samtidig fungerer, PATCH UTEN status-felt lar
verdien stå urørt (regresjon på eksisterende PATCH-semantikk), ugyldig
status-verdi avvist med 422 (Pydantic-mønster), `GET`-listen viser samme
verdi etterpå. `test_isolation.sql` 12/12 uendret (ingen skjemaendring).
Ekte typesjekket produksjonsbuild kjørt og bekreftet.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
begge containere boot-et rent, `/health` og `/dashboard` → 200,
`teeoff.no` upåvirket.
- **Kommunikasjon LIVE (2026-07-19, ADR-025) — det største enkeltløftet i
prosjektet så langt:** lag-intern chat («det hemmelige rommet») + offentlig
runde-feed («Banter Board»), begge med bilder og ekte WebSocket-sanntid,
bygget i samme runde. Fire hovedbeslutninger avklart eksplisitt med bruker
(AskUserQuestion) før bygging — brukeren valgte den mest ambisiøse
kombinasjonen på alle fire (begge deler nå, WebSockets fremfor polling,
ekte privat chat, bilder fra start).
**Datamodell:** ny migrasjon `013_messaging.sql` — delt `message`-tabell
med `scope`-diskriminator (`team`/`tournament_feed`) i stedet for to
separate tabeller, RLS org_isolation som ellers. `author_display_name`
FRYSES ved skrivetidspunkt (samme prinsipp som handicap-snapshot,
ADR-007) — spillerens `player.display_name` i org-en hvis den finnes,
ellers e-postens lokaldel (dekker org-ansatte uten egen spillerprofil).
**Lag-chat er BEVISST ekte privat** — ny `user_is_rostered_on_team` i
`app/team_authz.py`, med VILJE uten org-admin-fallback, ulikt de to andre
funksjonene i samme fil (`user_is_team_captain`/`user_is_match_participant`,
ADR-023, som begge har et slikt unntak). Første sted i hele appen der
org-eier/admin er strukturelt utestengt fra noe.
**Offentlig feed:** LESING gjenbruker `registration.py` sitt eksisterende
trenivå-visibility-mønster (ADR-018) helt uendret, inkl. anonym tilgang.
POSTING er strengere enn lesing — krever ekte innlogging OG org-
medlemskap/faktisk deltakelse, selv på en `public`-synlig turnering (en
helt urelatert innlogget bruker skal ikke kunne poste på en fremmed
offentlig side). Moderering: forfatteren selv ELLER org-eier/admin kan
slette et feed-innlegg (motsatt av lag-chatten, som ikke har noen
ekstern moderator).
**`registration.py` sine fire interne hjelpefunksjoner gjort delt**
(fjernet ledende understrek — samme "gjort delt for gjenbruk"-mønster som
tidligere runder): `resolve_org`, `is_participant`, `code_matches`,
`check_visibility`. `team_authz.py` sin `_is_org_admin` likeens →
`is_org_admin`. Ingen atferdsendring, kun navn, for at `messaging.py`
skulle kunne gjenbruke dem uendret i stedet for å duplisere logikk.
**WebSockets, ikke polling:** in-memory tilkoblingsregister PER PROSESS i
`app/routers/messaging.py` — trygt med dagens ene `teecup_api`-container,
men deles IKKE på tvers av flere prosesser/containere (samme klasse
begrensning som den allerede aksepterte in-memory-cachen, se ARCHITECTURE_
DECISIONS.md "Åpne spørsmål"). Ny `get_current_user_from_websocket` i
`app/auth.py` — WS-ruter kan ikke bruke `get_current_user`/
`get_authorized_org` direkte via `Depends()` (de er `Request`-typet, ingen
ekte HTTP Request finnes i en WS-scope), derfor en bevisst minimal, egen
kopi av samme cookie-dekode-/oppslagslogikk.
**Reell infrastrukturoppdagelse FØR noe ble forsøkt, ikke i etterkant:**
Next.js sin `rewrites()` proxyer ikke WebSocket-oppgraderinger pålitelig i
"standalone"-modus — løst likt som MinIO-media-ruten (ADR-018): en egen
Caddy-rute (`handle /ws/* { reverse_proxy teecup_api:8000 }`) rett til
API-et, forbi Next.js/`teecup_frontend` helt. Caddyfile ligger i det
SEPARATE `/opt/teeoff`-repoet — samme stale-bind-mount-inode-oppførsel som
ALLE tidligere Caddyfile-runder (graceful reload plukker ikke opp
endringen), løst likt: full `docker restart teeoff_caddy`, brukeren
bekreftet eksplisitt på forhånd, noen sekunders nedetid for `teeoff.no`.
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container
med `websockets`-Python-biblioteket installert kun for testen): et
20-punkts asyncio/httpx/websockets-testskript som dekket BEGGE
meldingstyper ende-til-ende. Kritiske personvern-/sanntid-funn, alle
bekreftet med ekte tilkoblinger (ikke bare REST):
- Rostret spiller på lag A FÅR lese/skrive lag A sin chat; rostret spiller
på lag B NEKTES; **organisatoren (org-eier) NEKTES OGSÅ** — bekreftet
BÅDE over REST (`GET`) og over selve WebSocket-håndtrykket (avvist med
lukkekode 4403 før `accept()` i det hele tatt kalles).
- Sanntid bekreftet reelt: A1 koblet til lag A sin chat-socket, A2 sendte
en melding over vanlig REST, A1 mottok den umiddelbart over den åpne
WebSocket-tilkoblingen (ikke bare at REST-svaret så riktig ut).
- Bildeopplasting i chat bekreftet (ekte AVIF-konvertert `image_url`
returnert).
- Offentlig feed: anonym NEKTES å poste (401), en tilfeldig INNLOGGET men
uvedkommende bruker NEKTES (403 `NOT_A_PARTICIPANT`), org-medlem FÅR,
en faktisk deltaker (rostret, IKKE org-medlem) FÅR — beviser
`is_participant`-veien fungerer uavhengig av `is_member`-veien. Anonym
WebSocket-tilkobling til en `public`-synlig turnerings feed FÅR lov og
mottar sanntidsoppdateringer.
- Moderering bekreftet: forfatter sletter eget innlegg, org-admin sletter
ANDRES innlegg (feeden), uvedkommende NEKTES å slette andres innlegg.
`test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket
produksjonsbuild av frontend kjørt og bekreftet, inkl. den nye
`/tournaments/[id]/teams/[teamId]/chat`-ruten.
**Frontend:** ny `components/team-chat.tsx` (meldingsliste med egen/andres-
styling, bildeopplasting, sanntid via nettleserens native `WebSocket`,
slett-egen-melding), lenket fra en ny chat-ikon-knapp i `tournament-
detail.tsx` sin `TeamPanel`. Ny seksjon `TournamentFeed` i
`components/public-tournament.tsx` (nederst på den offentlige
turneringssiden) — viser 401/403-svar fra posting som forklarende
inline-tekst ("logg inn for å poste" / "du må være medlem/deltaker") i
stedet for en generisk feilmelding.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt for alle tre
stegene (migrasjon, containere, Caddy-restart): migrasjon 013 kjørt mot
ekte `teecup_db`, `test_isolation.sql` fortsatt 12/12, begge containere
boot-et rent, Caddy validert (`caddy validate` — "Valid configuration")
FØR restart, restarten ren (ingen feil i loggen). Verifisert grundig
etterpå: `/health`/`dashboard` → 200, `teeoff.no` → 200, et ekte
`wss://`-håndtrykk over produksjons-https bekreftet å nå helt frem til
applikasjonslaget (testet mot en ukjent turnering-id — ingen ekte data
berørt — ga korrekt 404 fra selve WS-ruten, ikke en Caddy/Next.js-feil),
og et ren-HTTP-kall mot en `/ws/*`-sti bekreftet å returnere FastAPI sin
egen JSON-404 (`{"detail":"Not Found"}`) og ikke Next.js sin HTML-404 —
beviser Caddy-ruten faktisk treffer `teecup_api`, ikke `teecup_frontend`.
- **Tilskuer-rolle LIVE (2026-07-19, ADR-026):** brukeren valgte dette som
neste steg rett etter Kommunikasjon-runden — "tilskuer" var bevisst
utsatt til feed-synligheten (ADR-025) fantes, og nå gjorde den det.
**Kjernebeslutning:** ingen ny rolle/tabell — "tilskuer" er ganske enkelt
enhver som kan SE en turnering per `tournament.visibility` (ADR-018),
utvidet til også å dekke LIVE-data (leaderboard, matcher, scorekort), ikke
bare info-siden/programtidene som før.
**Bygget:** `GET /orgs/.../leaderboard`, `.../sessions/{id}/matches` og
`.../matches/{id}/scorecard` fantes allerede (organisator-/spiller-siden),
men krevde org-medlemskap — en ren spectator kunne aldri se dem. Løst ved
å ekstrahere den delte kjernelogikken til gjenbrukbare funksjoner
(`fetch_leaderboard` i tournaments.py, `fetch_matches` i matches.py,
`fetch_scorecard` i scoring.py — samme "gjort delt"-mønster som tidligere
runder), og la tre nye offentlige endepunkter i `registration.py` kalle
dem etter egen visibility-sjekk. `own_team_ids()` (`blind_draw.py`) gjort
null-sikker (`user_id: str | None`) -- en anonym leser har per definisjon
ingen egne lag, korrekt oppførsel er tom mengde (ser kun avslørte
matcher), ikke en feil.
**To nye sikkerhetssjekker funnet under DESIGN, ikke i etterkant, samme
disiplin som tidligere ADR-018 Beslutning B-lærdommen:**
1. `session_id`/`match_id` i URL-en må eksplisitt verifiseres å høre til
NØYAKTIG `tournament_id` i samme URL — `org_connection()` setter kun
TENANT-grensen (RLS), ikke at stiens id-er faktisk henger sammen. Uten
dette kunne noen med tilgang til én offentlig turnering i en
organisasjon lest en HVILKEN SOM HELST økt/match i samme organisasjon
(inkl. en privat en) ved å gjette/prøve id-er.
2. Scorekortet krever eksplisitt at BEGGE lag har låst oppstillingen
(blind draw, ADR-013) — leaderboard/matchliste arver reveal-skjuling
automatisk via `own_team_ids()`, men scorekortet har ingen tilsvarende
innebygd sjekk.
**Bruker valgte omfang utover anbefalingen:** BÅDE leaderboard+matchliste
OG fullt hull-for-hull-scorekort per match i samme runde (anbefalingen var
kun de to første).
**Frontend:** ny `/t/[id]/live`-side (`components/public-live.tsx`) —
fargesegmentert stillingsbar (faktisk + projisert, samme visuelle idé som
den org-autentiserte leaderboard-skjermen, men egen enklere implementasjon
siden komponentene har ulik autentiseringskontekst), utvidbar øktliste →
matchliste → hull-for-hull-scorekort (fargede hull-chips per lag). Lenket
fra hovedsiden (`public-tournament.tsx`) med en ny "Følg live"-knapp.
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container):
16 automatiserte sjekker, inkl. en PRESIS test av den nye tenant-vs-sti-
sjekken (ikke bare en ukjent id, men en EKTE ANNEN turnering i SAMME org
— bekreftet at match/økt fra turnering A fortsatt ikke kan leses via
turnering B sin offentlige URL), full blind-draw-skjuling FØR/ETTER
reveal for en anonym leser, kode-overstyring (ADR-020) fungerer uendret
for de nye endepunktene, scorekort eksplisitt nektet før reveal. Ekte
typesjekket produksjonsbuild av frontend, inkl. den nye `/t/[id]/live`-
ruten.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
begge containere boot-et rent, `/health`/`dashboard` → 200, `teeoff.no`
upåvirket.
- **"Følg live"-siden koblet til sanntid (2026-07-19, ADR-027):** brukeren
fulgte anbefalingen fra forrige runde — `/t/[id]/live` (bygget i
tilskuer-runden samme dag) krevde omlasting for nye resultater.
**Bygget:** ny, RUTEFRI modul `app/realtime.py` (in-memory
tilkoblingsregister + `broadcast_live_update(tournament_id)`) -- ligger
bevisst BAK alle routere i importgrafen for å unngå en sirkulær import
(`registration.py` importerer allerede fra `scoring.py`/`tournaments.py`/
`matches.py`, og `messaging.py` importerer fra `registration.py`; en
kringkastingsfunksjon i noen av routerne ville derfor bitt seg selv i
halen). Kringkastingen er lagt INN I `recompute_and_cache_match_state` og
`apply_concession` selv (sistnevnte fikk en ny påkrevd `tournament_id`-
parameter) -- kallerne (hole-score/hole-result-innsending, walkover på
både match- og turnering-nivå) trenger ikke huske å gjøre noe selv. Nytt
WS-endepunkt `/ws/public/tournaments/{id}/live` i `messaging.py`
(gjenbruker `/ws/*`-Caddy-ruten fra ADR-025 uendret -- ingen ny
infrastruktur). Sender bevisst kun et "noe endret seg, hent på nytt"-
signal, ikke selve dataene -- unngår å duplisere leaderboardets
projeksjons-regnestykke/blind draw-filtrering i kringkastings-payloaden.
**Frontend:** `components/public-live.tsx` åpner en WebSocket ved
montering; hver melding teller opp en `refreshKey` som utløser refetch av
leaderboardet ALLTID, og av en økts matcher/et scorekort KUN hvis det
faktisk er utvidet/åpent på skjermen akkurat da (ingen unødvendige kall
for lukket innhold). Liten pulserende prikk lagt til ved siden av
"Følg live"-teksten som visuell bekreftelse.
**Verifisert i scratch:** anonym avvist på live-WS for en `org`-synlig
turnering (samme visibility-sjekk som REST), satt til `public`, deretter
bekreftet FAKTISK sanntidsmottak (ikke bare at REST-svaret så riktig ut)
for BÅDE et vanlig hull-resultat OG en walkover-konsesjon -- en åpen
WebSocket-tilkobling mottok kringkastings-signalet i begge tilfeller.
Leaderboard fortsatt korrekt lesbar over REST etterpå (regresjon). Ekte
typesjekket produksjonsbuild kjørt og bekreftet.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
migrasjon, ingen ny Caddy-endring, `docker compose up -d --build
teecup_api teecup_frontend`, begge containere boot-et rent. Verifisert
grundig etterpå: `/health`/`dashboard` → 200, `teeoff.no` upåvirket, OG et
ekte `wss://`-håndtrykk mot den NYE `/live`-ruten over produksjons-https
(ukjent turnering-id, ingen ekte data berørt) ga korrekt 404 fra selve
applikasjonslaget.
- **Fire brukerrapporterte UI-/UX-hull, DIAGNOSTISERT OG NOTERT, IKKE fikset
(2026-07-19):** brukeren rapporterte fire ting fra faktisk bruk av
`teecup.teeoff.no` rett før PWA-runden startet. Root cause funnet ved
kodegjennomgang for tre av fire (ikke gjettet). Full detalj i
FEATURE_BACKLOG.md sin nye seksjon "Rapporterte UI-/UX-hull (2026-07-19)".
Kort:
1. Dashboard-turneringskortet viser "Ingen datoer satt" alltid — leser
`tournament.start_date`/`end_date` (eget felt, ADR-015), som INGEN
UI-skjema noensinne skriver til. Skal enten få et faktisk skjemafelt,
eller kortet bør heller utlede datoen fra øktenes `scheduled_at`.
2. **Reell, bekreftet rewrite-bug:** `/orgs/{id}/members` gir en rå
FastAPI-404 (`{"detail":"Not Found"}`) i stedet for medlemssiden.
`next.config.mjs` sin `rewrites()` returnerer en plain array (implisitt
"afterFiles") — DYNAMISKE Next.js-sider sjekkes ETTER rewrites, så
`/orgs/:path*`-proxy-regelen (ADR-016) fanger kallet FØR
`app/orgs/[id]/members/page.tsx` noensinne nås. Dette er den FØRSTE
frontend-siden som er nestet direkte under et allerede proxyet prefiks
— ingen tidligere skjerm har truffet dette. Selve siden/komponenten er
riktig bygget, kun ruten dit er blokkert.
3. Ingen UI-vei til å opprette en ANDRE organisasjon når man allerede har
én — `CreateOrganizationState` i `dashboard.tsx` vises kun ved null
org-er. Backend støtter det fullt ut allerede (ADR-021).
4. Ingen sammendrag/indikator noe sted for "alle runder har fått dato" —
må sjekkes manuelt per øktkort på program-skjermen.
**Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens
eksplisitte instruks, PWA-runden prioriteres først.
- **PWA: installasjon + full offline scoreregistrering, BYGGET, IKKE ENNÅ
RULLET UT (2026-07-19, ADR-028):** bruker valgte det mest ambisiøse
omfanget (installerbar app OG offline scoreregistrering, ikke bare
installasjon), og valgte å generere enkle ikoner nå fremfor å vente på
ekte design (med eksplisitt beskjed om at de er midlertidige).
**Ikoner:** ingen `PIL`/`rsvg-convert`/`imagemagick` tilgjengelig i miljøet
— løst med et scratch npm-prosjekt (`sharp@0.33.5`, node18-kompatibel
versjon; nyeste `sharp` krever node ≥20 og feilet først) som genererte et
enkelt grønt golf-flagg-ikonsett (`public/icons/icon-192.png`,
`icon-512.png`, `icon-maskable-512.png`) + erstattet den gamle
`public/apple-icon.png` (var v0.app sin generiske plassholderlogo, ikke
TeeCup-merkevare i det hele tatt).
**Bygget:** `app/manifest.ts` (Next.js sin innebygde manifest-generator,
ikke en statisk `manifest.json`), `appleWebApp`-metadata i `layout.tsx`
(iOS leser ikke manifest.json for hjemskjerm-oppførsel),
`components/sw-register.tsx` (stille no-op uten SW-støtte),
håndskrevet `public/sw.js` (ingen next-pwa/workbox-avhengighet — nettverk
først/cache-fallback for navigasjon + `/orgs/*`-GET-er, BEVISST ikke
stale-while-revalidate, se ADR-028 for hvorfor), `public/offline.html`.
**Offline scoreregistrering:** ny `lib/offline-queue.ts` (IndexedDB-kø,
ren klientkode — ikke i SW-en), koblet inn i `session-scorecard.tsx` sin
`submitStroke`/`submitHoleResult`: sjekker `navigator.onLine` først, køer
kun ved en EKTE nettverksfeil (ikke ved et avvist HTTP-svar som "matchen
er avgjort" — det vises fortsatt som vanlig feiltekst). Lokalt overlay
(`pendingStrokes`/`pendingResults`) viser køede verdier umiddelbart,
merket "Lagret lokalt · venter på synk". Auto-synk ved `window`s
`online`-event PLUSS en manuell "Synkroniser nå"-knapp (bevisst IKKE
Background Sync API — iOS Safari støtter den ikke). Et definitivt avvist
synk-forsøk (f.eks. matchen ble avgjort på en annen enhet mens denne var
offline) fjernes fra køen og vises som feilmelding, henger aldri for
alltid.
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som
deployes) kjørt og bekreftet — alle 16 ruter listet inkl.
`/manifest.webmanifest`. Kort container-boot + `curl` bekreftet manifest/
service worker/ikoner/offline.html alle svarer riktig (200, riktig
innhold).
**IKKE gjort denne runden, viktig å være ærlig om:** ingen faktisk
nettleser-basert offline-test (Chrome DevTools sin Offline-bryter,
faktisk "Legg til på hjemskjerm") — intet nettleserverktøy tilgjengelig i
denne økten. Kun kodegjennomgang + build-verifisering. **Brukeren bør selv
teste scorekort-siden med DevTools Offline-modus før tillit i skarp
bruk.**
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: `docker
compose up -d --build teecup_frontend`. Compose gjenskapte OGSÅ
`teecup_api` som en bivirkning (avhengighets-oppløsning i `up --build`)
— ingen backend-kode rørt, ren uendret gjenoppbygging, begge containere
boot-et rent (`Application startup complete` / Next.js `Ready`).
Verifisert: `/health`/`dashboard` → 200, `/manifest.webmanifest`/`sw.js`/
`icons/icon-512.png` alle 200 over ekte https, `teeoff.no` upåvirket.
- **Fire nye hull rapportert fra faktisk testing av blind draw + scorekort
(foursome), DIAGNOSTISERT OG NOTERT, IKKE fikset (2026-07-19):** rett
etter PWA-runden. Full detalj i FEATURE_BACKLOG.md sin nye seksjon
"Rapporterte hull, blind draw + scorekort (2026-07-19)". Kort:
1. **Bekreftet root cause:** valgt spiller forsvinner ikke fra
nedtrekkslisten i `AddSlotForm` (`session-blind-draw.tsx`) — den
merkes kun `disabled` på `
`-nivå, som HTML fortsatt viser
(bare gråtonet). Skal FILTRERES bort, ikke deaktiveres.
2. **Bekreftet, større enn antatt:** tee-valg (Dame/Herre) bør følges av
spillerens registrerte kjønn automatisk. Krever en backend-utvidelse
først — `RosterEntry`/`list_roster` (`tournaments.py`) mangler
`player.gender` helt i responsen, selv om feltet finnes i skjemaet og
alt eksponeres via `GET /orgs/{id}/players`. Åpne spørsmål om låst vs.
forhåndsutfylt valg, og fallback ved ukjent kjønn/manglende
matchende tee, før bygging.
3. Ren frontend-UX: tallvelger (1–9 + utvidbar "10 eller flere") i stedet
for pluss/minus-steppere for slagregistrering. Ingen backend-endring.
4. **HCP "ikke hensyntatt" i foursome-test — IKKE bekreftet som bug.** Ett
definitivt, bekreftet hull uavhengig av alt annet: det beregnede
`course_handicap`/`playing_handicap` eksponeres ALDRI noe sted i
API-et eller UI-et (sjekket `matches.py`, `scoring.py`, begge
frontend-skjermene) — usynlig selv når beregningen er korrekt. I
TILLEGG en kjent, tidligere dokumentert begrensning som kan ha slått
inn: foursome/greensome/scramble sin side-handicap beregnes kun når
BEGGE deltakere har en matchende `tee_rating`, og `tee_rating`-rader
lages i dag ALLTID kun med `full_18`-omfang — en `front_9`/`back_9`-
testøkt ville derfor ALDRI fått handicap beregnet i det hele tatt.
**Trenger avklaring:** var testøktens `hole_config` `full_18`, og
hadde begge sider alle sine deltakere+tee lagt til før scoring?
**Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens
eksplisitte instruks.
- **Tre av fire hull FIKSET OG SCRATCH-VERIFISERT (2026-07-19), samme dag:**
bruker ba eksplisitt om punkt 1 og 3, og presiserte punkt 2 (se under)
samt bekreftet den nøyaktige handicap-formelen for punkt 4.
1. **Spillerliste-fiks:** `AddSlotForm` (`session-blind-draw.tsx`)
filtrerer nå allerede-valgte roster-rader helt bort i stedet for å
bare `disabled`-merke ` `-en (som HTML uansett viser gråtonet).
2. **Tee-valg — OMDEFINERT etter brukerens presisering, IKKE bygget
ennå:** min opprinnelige antakelse ("lås tee til spillerens kjønn")
var feil i premisset — en golfbane har ikke fysisk kjønnsdelte
utslag, kun eventuelt kjønnsdelt RATING av samme utslag (noen klubber
sloper bevisst ikke ett utslag for ett kjønn). Bekreftet mot ekte
Tjøme-data: hvert fysisk utslag ligger i dag som TO `tee`-rader med
samme navn (én per kjønn) — nøyaktig konflateringen brukeren pekte
på. Riktig fiks er å flytte `gender` fra `tee` til `tee_rating`
(skjemaendring + datamigrering + import-/handicap-/remap-kode).
Betydelig større enn antatt — se FEATURE_BACKLOG.md for full
analyse og de tre åpne designspørsmålene som trengs FØR bygging.
3. **Tallvelger:** ny `StrokePicker`-komponent
(`session-scorecard.tsx`) — 1-9 direkte, "10+"-knapp åpner 10-19,
med en "tilbake"-lenke. Erstatter ±-stepperen og all dens døde kode
helt.
4. **HCP-bug, BEKREFTET og FIKSET:** brukeren beskrev selv riktig
formel (kombinert hcp/2 justert for prosent, laveste side til 0
mottatte slag ved matchplay-hcp, resten fordelt fra stroke index 1) —
lest direkte mot `handicap_engine.py` og bekreftet at koden allerede
implementerer NØYAKTIG dette. Bugen lå ikke i formelen, men i at den
ALDRI kjørte for front_9/back_9-økter: `compute_and_store_side_
handicaps` (`app/handicap.py`) joinet `tee_rating` på øktens
`hole_config` som rating-scope, men slike rader lages i praksis kun
med `scope='full_18'` (matcher ADR-008 sin allerede etablerte
design — full_18-ratingen skal alltid brukes, front/back-9-
fordelingen skjer senere ved selve slagtildelingen). Bekreftet
direkte mot EKTE `teecup_db` (read-only): brukerens rapporterte
testøkt (`front_9`+`foursome`) hadde `course_handicap`/
`playing_handicap = NULL` på alle fire deltakere. Påvirket ALLE
formater på front_9/back_9-økter, ikke bare foursome. Fikset: scope
hardkodet til `full_18`, `hole_config`-parameteren fjernet helt fra
funksjonen og alle tre kallstedene (var død etter fiksen).
**Scratch-verifisert presist:** samme scenario gjenskapt (foursome+
front_9, hcp 10/20 mot 5/15) — course/playing handicap kom ut nøyaktig
som beregnet for hånd (10/21 vs. 5/16, kombinert 16/10), og et hull
med IDENTISK bruttoscore (5-5) på begge sider ga et IKKE-delt
resultat ("a" vant) — direkte bevis på at hcp nå faktisk brukes.
**Ny, urelatert bug funnet under samme scratch-test, IKKE fikset:**
stroke-modus-innsending på en bane uten registrerte hull krasjer rått
(500 `IndexError` i `allocate_over_played_holes`) i stedet for en ren
`VALIDATION_FAILED` — samme klasse feil som en tidligere fikset
manglende-handicap-krasj. Notert i FEATURE_BACKLOG.md, ikke bygget.
**Scratch-infrastruktur:** isolert `teecup_scratch`-database +
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container (`python:3.12-slim`, `app/` og `handicap_engine.py` montert
read-only), alt ryddet opp etter verifisering. Typesjekket
produksjonsbuild kjørt for frontend-fiksene (1+3), alle 16 ruter listet.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt, se eget
punkt lenger ned for punkt 2 (som ble bygget og rullet ut sammen med
disse tre i én utrulling).
- **Punkt 2 (tee/kjønn) BYGGET OG SCRATCH-VERIFISERT (2026-07-19, ADR-029),
samme dag, rett etter designavklaringen:** bruker bekreftet begge
anbefalte alternativer (helautomatisk tee-valg, "feil høyt" ved
manglende kjønn/rating). Ny migrasjon `014_tee_gender_to_rating.sql`:
flytter `gender` fra `tee` til `tee_rating` (unikhet
`(tee_id, scope)` → `(tee_id, scope, gender)`), slår sammen
eksisterende kjønns-par-tee-rader til én fysisk tee-rad per (bane,
navn) — velger laveste id som "beholder", flytter `tee_rating`- og
`match_participant.tee_id`-referanser dit, sletter duplikatene.
**Reell bug funnet OG fikset UNDER selve migrasjonsskrivingen** (ikke i
produksjon): første versjon prøvde å droppe den GAMLE
`(tee_id, scope)`-unikheten ETTER sammenslåingen i stedet for FØR —
kolliderte da midlertidig med beholder-tee-ens egen eksisterende rad
for samme scope. Rettet ved å bytte rekkefølge (drop gammel unikhet FØR
sammenslåing, legg til ny kjønnsbevisst unikhet ETTER).
**Kodeendringer:** `app/handicap.py` sin `compute_and_store_side_
handicaps` joiner nå også på `player.gender` (i tillegg til forrige
rundes full_18-fiks). `app/routers/matches.py` sin `add_participant`
validerer FØR innsetting: spiller har registrert kjønn, OG valgt utslag
har en matchende rating — begge avvist med klar `VALIDATION_FAILED`.
`app/routers/tournaments.py` sin `_remap_course` (bane-bytte) matcher nå
på tee-navn OG bekrefter matchende kjønnsrating for hver berørte
spiller. `app/routers/courses.py`: `TeeCreate` redesignet fra ett flatt
kjønn+rating-sett til en `ratings`-liste (1-2 elementer, distinkte
kjønn); ADR-019 sin `import_official_course` lager nå ÉN tee-rad per
fysisk teeoff-utslag (før: to, én per kjønn) med inntil to
`tee_rating`-rader under. `session-blind-draw.tsx` forenklet — ingen
"H"/"D"-suffiks, ingen kjønnslogikk i det hele tatt lenger (serveren
løser det).
**Verifisert grundig, flere separate scratch-runder:**
1. Selve fletting-migrasjonen kjørt mot SYNTETISK data som gjenskaper
Tjøme-mønsteret nøyaktig (to par + én enslig utslag, pluss en
`match_participant`-rad som bevisst pekte til DUPLIKATEN, ikke
beholderen) — bekreftet: to rader ble til én, `tee_rating.gender`
riktig fylt inn for begge, `match_participant.tee_id` korrekt
reparert til beholderens id, det enslige utslaget urørt.
2. Full API-runde (18 automatiserte sjekker via et Python/urllib-
testskript — httpx var ikke tilgjengelig i vertsmiljøet, løst med
stdlib `http.cookiejar`/`urllib` i stedet): manuell tee-opprettelse
(to ratinger, kun én rating, duplikat kjønn avvist), `GET tees`
viser riktig sammenslått struktur, kvinne+dame-rating lykkes,
mann+kun-dame-rating avvist tydelig, spiller uten kjønn avvist
tydelig, full kjønnsblandet singel-match scoret korrekt.
3. Offisiell import kjørt mot EKTE `teeoff_api` (Borregaard Golfklubb,
samme mønster som ADR-019 sin opprinnelige verifisering) — bekreftet
4 fysiske utslag importert med begge kjønnsratinger hver, ikke 8
doble rader.
4. `_remap_course`: bane-bytte til en bane UTEN matchende kjønnsrating
avvist tydelig, bane-bytte til en bane MED matchende rating lykket.
Ekte typesjekket produksjonsbuild av frontend kjørt på nytt og
bekreftet etter blind draw-forenklingen.
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: migrasjon
014 kjørt mot ekte `teecup_db` FØRST (Tjømes 8 tee-rader slått sammen
til 4 — bekreftet 0 brutte `match_participant.tee_id`-referanser
etterpå med en direkte spørring), deretter `docker compose up -d
--build teecup_api teecup_frontend` sammen med punkt 1/3/4 i samme
utrulling. Begge containere boot-et rent, `/health`/`dashboard` → 200,
`teeoff.no` upåvirket.
- **Dashboard-dato-fiks (ADR-030) + rediger spiller, BYGGET OG SCRATCH-
VERIFISERT (2026-07-19/20):** brukeren viste et faktisk skjermbilde av
det tidligere dokumenterte datovisning-hullet (økt planlagt til "11.
juli" på Program-fanen, men dashbord-kortet viste fortsatt "Ingen
datoer satt") og ba samtidig om å kunne redigere spilleres HCP.
**Dato-fiks:** `list_tournaments` (`app/routers/tournaments.py`) utleder
nå datospennet fra øktenes `scheduled_at` (`COALESCE` med et evt.
eksplisitt satt `tournament.start_date`/`end_date`, som fortsatt vinner
om det noensinne settes — ingen UI gjør det i dag). Kun denne ene
spørringen endret, `_TOURNAMENT_COLUMNS` (brukt av opprett/PATCH sine
`RETURNING`-klausuler) urørt. Se ADR-030.
**Rediger spiller:** ny `PATCH /orgs/{id}/players/{id}`
(`app/routers/players.py`, vanlig `exclude_unset`-mønster, dekker alle
spillerfelt) + ny "Rediger spiller"-handling i rosterradens meny
(`tournament-detail.tsx`). **Bevisst grense, forklart i selve UI-et:**
endrer spillerpoolen, IKKE et lags allerede frosne
`handicap_index_snapshot` (ADR-007) — reproduserbarhet for allerede
opprettede lag er et bevisst, tidligere designvalg, ikke noe denne
fiksen skulle endre. Skjemaet sier dette rett ut i stedet for å late som
endringen slår inn overalt.
**Scratch-verifisert, 9 sjekker:** spiller-PATCH (hcp-endring, delvis
PATCH lar andre felt stå urørt, tomt PATCH avvist, ukjent id gir 404),
DEN KRITISKE sjekken (en spillers allerede frosne roster-snapshot for et
eksisterende lag forble UENDRET etter en påfølgende spiller-PATCH —
bekrefter ADR-007 fortsatt holder), dato-utledning (ingen økter → null,
to økter 11./12. juli → riktig utledet spenn, eksplisitt satt dato
vinner over utledet). Ekte typesjekket produksjonsbuild kjørt og
bekreftet.
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: `docker
compose up -d --build teecup_api teecup_frontend`, ingen migrasjon.
Begge containere boot-et rent, `/health`/`dashboard` → 200, `teeoff.no`
upåvirket. Verifisert mot EKTE data (ikke bare scratch): "De Gamle er
Eldst" viser nå korrekt 11. juli 2026 i stedet for "Ingen datoer satt".
- **To nye punkter reist 2026-07-20, GJENNOMTENKT OG FORESLÅTT, IKKE
bygget:** brukeren ba eksplisitt om at punkt 1 tenkes grundig gjennom
og legges frem som et forslag FØR bygging (ikke kode med en gang), og
markerte det eksplisitt som prioritet over punkt 2.
1. **Personlig landingsside for enhver registrert bruker (PRIORITERT).**
Bekreftet reelt hull ved kodegjennomgang: `app/page.tsx` sender
enhver innlogget bruker til `/dashboard`, som viser "opprett
organisasjon" så snart `organizations.length === 0` — også for en
bruker som KUN er spiller (koblet via `player.user_id`,
ADR-017 B), aldri organisator. Fullt forslag skrevet i
FEATURE_BACKLOG.md: ett samlet dashboard (ikke to atskilte ruter),
ny "Mine runder"-seksjon (tverr-org, krever en ny SECURITY
DEFINER-bro `player_organizations_for_user()` + utvidelse av
`/auth/me`, samme mønster som `public_tournament_org()` m.fl.),
organisasjonsseksjonen uendret under. Venter på brukerens
bekreftelse på retningen før bygging starter.
2. **Midlertidige spillere + automatisk etter-runde-e-post (lavere
prioritet, likevel dokumentert grundig).** Presisert ved
kodegjennomgang: det meste av "midlertidig spiller"-behovet
dekkes ALLEREDE av eksisterende `POST /orgs/{id}/players`
(krever aldri en konto). Det som faktisk mangler er en PROAKTIV
e-post-utsending etter runden (scorekort + innloggingslenke) —
foreslått som en eksplisitt organisator-knapp per økt (ikke en
automatisk bakgrunnsjobb, for å unngå uventede e-poster fra en
gjettet "runden er ferdig"-deteksjon). Tre åpne spørsmål notert i
FEATURE_BACKLOG.md (økt- vs. turnering-nivå, dobbel-utsending-
sperre, locale).
**Ingen kode skrevet for noen av de to ennå** — dette var bevisst en
tenke-og-foreslå-runde, ikke en byggerunde.
- **Punkt 1 BYGGET OG SCRATCH-VERIFISERT (2026-07-20), samme dag, rett etter
forslaget over:** bruker svarte "Gjør punkt 1" med et utvidet omfang —
inkluder også opprettelse/redigering/sletting av personlig informasjon
(profilbilde, fornavn, etternavn, fødselsdato, kjønn, HCP, hjemmeklubb).
**Ny migrasjon `015_user_profile.sql`:** `app_user` får de sju nye
profilfeltene — ETT sett PER KONTO, bevisst IKKE slått sammen med de
org-scopede `player`-radene (se ADR-031 Beslutning B for full
begrunnelse — to reelt atskilte konsepter). Ny
`player_organizations_for_user()`-bro (femte instans av samme
SECURITY DEFINER-mønster som `public_tournament_org()` m.fl.).
**Backend:** `/auth/me` utvidet med profilfeltene + `avatar_url` +
`my_tournaments` (turneringer brukeren er ROSTRET i, tverr-org, samme
N+1-org_connection()-mønster som organisasjonslisten). Ny
`PATCH /auth/profile` (vanlig exclude_unset), `POST`/
`DELETE /auth/profile/avatar` (samme ekte multipart→AVIF-mønster som
turnering-hero-bilder).
**Reelt sikkerhetshull funnet UNDER bygging, ikke antatt på forhånd:**
testet "Mine runder" mot en EKTE ren spiller (rostret, ingen
org-medlemskap) og oppdaget at `check_visibility()` (ADR-018) kun ga
deltaker-tilgang for `visibility='participants'` — IKKE for `'org'`
(DEFAULT for enhver ny turnering). En ren spiller ville altså vært
stengt ute fra sin EGEN, helt vanlige turnering — nøyaktig
brukergruppen "Mine runder" er bygget for. Fikset: deltaker-sjekken
gjelder nå begge ikke-offentlige tier, med eksplisitt begrunnelse om
at visibility styrer eksponering mot UTENFORSTÅENDE, aldri mot faktiske
deltakere. Verifisert presist at dette er en REN UTVIDELSE, ingen
innstramming: samme scratch-test bekreftet at en helt ubeslektet
FREMMED (innlogget, ikke deltaker) og en ANONYM leser fortsatt begge
avvises identisk som før (403 NOT_VISIBLE).
**Frontend:** `account-settings.tsx` fikk en ny "Personlig profil"-
seksjon (avatar-opplasting/fjerning, fornavn/etternavn/fødselsdato/
kjønn/HCP/hjemmeklubb-skjema). `dashboard.tsx` fikk en ny
`MyToursSection` («Mine runder», øverst, lenker til den offentlige
turnering-siden) + en mykere, sekundær utgave av "opprett organisasjon"-
tomtilstanden når brukeren allerede har spiller-data å vise.
**Bevisst UTENFOR omfang, klart flagget, IKKE en del av denne rundens
leveranse:** "Mine runder" lenker IKKE til lag-chat/scorekort ennå — de
krever fortsatt ekte organisasjonsmedlemskap (`get_authorized_org`), en
strengere, bredt brukt sperre som ikke ble endret denne runden (egen,
større og mer risikofylt endring, se ADR-031).
**Scratch-verifisert, 15 sjekker:** full profil-CRUD (alle felt satt,
delvis PATCH lar andre felt stå urørt, eksplisitt `null` sletter et
felt, tomt PATCH avvist, avatar lastet opp med ekte AVIF-URL og
slettet igjen, ugyldig filtype avvist), "Mine runder" for en EKTE ren
spiller uten org-medlemskap, OG den kritiske sikkerhetssjekken over.
Ekte typesjekket produksjonsbuild kjørt og bekreftet.
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: migrasjon
015 kjørt mot ekte `teecup_db` (bekreftet nye kolonner + funksjon
finnes), deretter `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`/
`/account` → 200, `teeoff.no` upåvirket.
- **Oppfølging samme dag: e-post + mobil, BYGGET OG SCRATCH-VERIFISERT
(ADR-032):** brukeren påpekte rett etter forrige runde at "identifikatoren"
(e-post) manglet i profilen, og etterspurte mobil med landsnummer.
Spurte samtidig hvorfor V0 ikke brukes til det visuelle her — svarte at
jeg ikke har V0 som et verktøy jeg selv kan kalle (all V0-bruk i
prosjektet har vært brukeren som designer i v0.app og sender meg
zip-eksporter), og at disse siste tilføyelsene er små, inkrementelle
skjemafelt i eksisterende komponenter (gjenbruker allerede etablerte
Tailwind/shadcn-mønstre) der en full V0-runde (design→eksport→diff→
sammenslåing) ville vært en unødvendig omvei.
**Mobil:** `mobile_country_code`+`mobile_number` (to separate felt, ikke
én sammensatt streng), lagt til i den EKSISTERENDE `PATCH /auth/profile`
— ren tilføyelse, ingen ny sikkerhetsvurdering nødvendig.
**E-post — bevisst IKKE en enkel PATCH:** e-post er innloggings-
identifikatoren (magic-link-mål) — en vanlig PATCH ville latt en
skrivefeil eller en kapret sesjon stjele kontoen for godt. Bygget som et
ekte to-stegs bekreftelsesløp i stedet, samme `token_hash`+
`expires_at`+`consumed_at`-mønster som `magic_link_token` (migrasjon
004): ny `email_change_token`-tabell (migrasjon `016_profile_
contact.sql`). `POST /auth/profile/email` (krever sesjon, sender
bekreftelseslenke til den NYE adressen -- ikke den gamle, beviser
eierskap av MÅLET). `POST /auth/profile/email/confirm` (ingen sesjon
påkrevd, samme mønster som selve magic-link-verifiseringen -- lenken
kan åpnes på en annen enhet enn den som ba om byttet). E-posten endres
ALDRI før lenken faktisk åpnes. Duplikat-sjekk kjøres TO GANGER (ved
forespørsel og rett før selve byttet, i tilfelle adressen ble tatt i
mellomtiden), pluss den eksisterende unike indeksen som siste
bakstopper. Ny `/verify-email`-side (samme mønster som `/verify`).
**Scratch-verifisert, 10 sjekker:** mobil satt via vanlig PATCH; vanlig
profil-PATCH rører aldri e-post; bytte til allerede brukt adresse
avvist (409); e-post uendret helt til bekreftelse; ugyldig kode avvist;
gyldig kode fullfører byttet; SAMME kode kan ikke gjenbrukes; en helt ny
innlogging med den GAMLE adressen oppretter en fersk, tom konto (beviser
byttet er reelt og fullstendig). Ekte typesjekket produksjonsbuild kjørt
og bekreftet (ny `/verify-email`-rute listet).
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt (spurte
samtidig og bekreftet at `teecup.teeoff.no/dashboard` nå er DEN samme
adressen for enhver innlogget bruker uansett rolle — nettopp poenget
med ADR-031/032): migrasjon 016 kjørt mot ekte `teecup_db` (bekreftet
nye kolonner + `email_change_token`-tabell finnes), deretter `docker
compose up -d --build teecup_api teecup_frontend`. Begge containere
boot-et rent, `/health`/`/dashboard`/`/account`/`/verify-email` → 200,
`teeoff.no` upåvirket.
- **Medlemsside-ruten FIKSET OG LIVE (2026-07-20):** brukeren ba eksplisitt
om å ta fatt på dette (det mest presserende av de fire UI-hullene notert
2026-07-19 — siden var helt utilgjengelig). Root cause var allerede
presist diagnostisert: `next.config.mjs` sin `rewrites()` (plain array,
implisitt "afterFiles") fanger `/orgs/:path*` FØR Next.js sine egne
DYNAMISKE sider sjekkes, så `app/orgs/[id]/members/page.tsx` ble aldri
nådd — kallet gikk til FastAPI i stedet, som ga en rå 404.
**Fikset:** siden flyttet til `app/organizations/[id]/members/page.tsx`
(utenfor `/orgs/*`-prefikset), eneste lenke (`dashboard.tsx`) oppdatert.
Lagt til en forklarende kommentar i `next.config.mjs` sin `rewrites()`
for å forhindre samme feil ved en fremtidig ny side.
**Verifisert med ekte produksjonsbuild + container-boot** (ikke bare
typesjekk): den nye ruten (`/organizations/{id}/members`) rendrer
faktisk `OrgMembers`-komponenten med riktig `organizationId`/`orgName`
i RSC-payloaden, IKKE en 404 eller innloggingssiden.
**Rullet ut live**, ren frontend-endring, ingen migrasjon, `teeoff.no`
upåvirket.
- **De to siste UI-/UX-hullene fra 2026-07-19 FIKSET OG LIVE (2026-07-21):**
alle fire punkter i den runden er dermed fikset.
1. **«Ny organisasjon»:** ny `NewOrganizationControl` i `dashboard.tsx`
(identisk inline-ekspanderende-form-mønster som `NewTournamentControl`),
lagt til i `OrganizationView` sin header ved siden av
«Medlemmer»/«Ny turnering» — synlig uansett hvor mange org-er brukeren
allerede har. Ingen backend-endring (`POST /orgs` hadde aldri en
grense).
2. **Dato-sammendrag:** ny `DateCoverageSummary` i `tournament-
program.tsx`, vist øverst i øktlisten når minst én økt finnes: «X av Y
runder har fått dato og klokkeslett» (uthevet når alle er satt). Ren
klientside-telling av allerede lastet `scheduled_at`, ingen
backend-endring.
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som
deployes) kjørt og bekreftet, alle 16 ruter listet. **Rullet ut live**,
bruker bekreftet eksplisitt: `docker compose up -d --build
teecup_frontend` (gjenskapte også `teecup_api` som compose sin vanlige
avhengighets-bivirkning, ingen backend-kode rørt). Begge containere
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **Dashboard/konto-runde (2026-07-21): to store punkter reist samtidig av
brukeren.** (1) "Alt relatert til dashboard/account/brukerkontoer" —
scopet sammen med brukeren via spørsmål: tom-tilstand-redesignet ble
PAUSERT (bruker ba om "juster retningen" og reiste et dypere spørsmål om
hvorvidt organisasjon fortsatt bør være "det som meldes først" — se eget
punkt i FEATURE_BACKLOG.md, min vurdering: nei, bør bli ett likestilt
valg blant flere). (2) Et helt nytt, stort forslag om frittstående
rundeføring + detaljert statistikk (putter/chip/bunkerslag/straffeslag/
førsteputt-lengde) UTEN turnering/organisasjon — grundig notert i
FEATURE_BACKLOG.md med en eksplisitt arkitektur-advarsel: dette
UTFORDRER tenant-invarianten (`organization_id` på alle domenetabeller)
direkte og trenger en egen ADR, ikke bygget denne runden.
**Deltaker-tilgang til lag-chat/scorekort — ✅ BYGGET OG LIVE
2026-07-21** (én av tre konkrete følgepunkter brukeren bekreftet i samme
runde, de to andre — sekundær e-post, HCP-historikk — tas fortløpende
etterpå): fjernet den blanke `get_authorized_org`-sperren fra ni
endepunkter på tvers av `messaging.py`/`scoring.py`/`matches.py`/
`tournaments.py`/`courses.py`, erstattet med de ALLEREDE eksisterende
domene-sjekkene (`user_is_rostered_on_team`/`user_is_match_participant`/
`user_is_team_captain`) som viste seg å støtte ikke-org-medlemmer helt
fint fra før — de var bare aldri nåbare. To nye delte hjelpefunksjoner i
`team_authz.py` (`is_org_member`, `user_is_tournament_participant` —
sistnevnte FLYTTET dit fra `registration.py` for å unngå sirkulær
import) dekker de endepunktene som IKKE hadde noen finkornet sjekk fra
før (ren fjerning der ville åpnet dem for enhver innlogget bruker).
`/auth/me` sin `my_tournaments` fikk `my_session_id`/`my_match_id`;
"Mine runder"-kortet fikk "Lag-chat"/"Scorekort"-lenker.
**Scratch-verifisert grundig, 43 sjekker** (isolert scratch-rolle+MinIO+
engangs API-container): rostret ikke-medlem fikk korrekt tilgang
overalt (inkl. faktisk sendt chat-melding og hull-resultat), fortsatt
avvist fra det ANDRE lagets chat, en helt fremmed bruker avvist overalt,
org-eier beholder alt UNNTATT lag-chat (uendret, med vilje), kryss-org-
isolasjon bekreftet. **Reelt funn UNDER selve scratch-testingen:** en
rostret-men-ikke-kaptein spiller ble først uventet GODTATT til walkover
— viste seg å være en allerede tiltenkt, dokumentert fallback
(`user_is_team_captain`: "ingen kaptein utpekt ennå = enhver rostret
spiller godtas"), ikke en bug — testen ble rettet (la til en faktisk
kaptein) og bekreftet deretter riktig avvisning. `test_isolation.sql`
12/12 uendret (ingen skjemaendring). Ekte typesjekket produksjonsbuild
kjørt og bekreftet.
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket.
- **Sekundær e-postadresse (del 1, det enkle tilfellet) — ✅ BYGGET OG LIVE
2026-07-21**, samme dag, rett etter deltaker-tilgang-runden. Ny
migrasjon `017_secondary_email.sql` (`secondary_email_token` +
`user_secondary_email`, samme token-hash-og-utløp-mønster som ADR-032s
`email_change_token`). Nye endepunkter `POST /auth/secondary-email`,
`POST /auth/secondary-email/confirm`, `DELETE /auth/secondary-email/{id}`.
**Kjernestykket:** `verify_magic_link`/`login_with_password` slår nå opp
`user_secondary_email` FØR sitt vanlige `app_user.email`-oppslag — en
innlogging på en verifisert sekundæradresse løses til EIERENS
eksisterende konto i stedet for å opprette en ny, separat en (nøyaktig
det hullet som gjorde funksjonen nødvendig i utgangspunktet). Lagt i
`/account` (ikke dashbordet som opprinnelig bedt om — bevisst avvik,
flagget eksplisitt: dette er kun del 1, dashbord-plassering er trolig
riktigere når/hvis del 2 (kontosammenslåing) bygges).
**Scratch-verifisert, 20 sjekker:** adresse ikke lagt til før bekreftet,
token ikke gjenbrukbart, dupliserte adresser (som andres primær- ELLER
sekundæradresse) avvist tydelig, innlogging via sekundæradresse (magic-
link OG passord) bekreftet å resolve til SAMME eksisterende konto,
fremmed kan ikke slette andres adresse, fjernet adresse oppretter en
genuint NY konto ved neste innlogging (beviser fjerning er reell).
`test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild kjørt og
bekreftet.
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon
017 kjørt mot ekte `teecup_db` (begge tabeller bekreftet, `test_
isolation.sql` fortsatt 12/12), deretter `docker compose up -d --build
teecup_api teecup_frontend`. Begge containere boot-et rent,
`/health`/`/dashboard`/`/account`/`/verify-email` → 200, `teeoff.no`
upåvirket. Del 2 (ekte kontosammenslåing) fortsatt IKKE designet, egen
fremtidig runde, se FEATURE_BACKLOG.md.
- **HCP-historikk over tid — ✅ BYGGET OG LIVE 2026-07-21**, samme dag,
siste av de tre bekreftede punktene fra dashboard/konto-runden. Ny
migrasjon `018_handicap_history.sql` (append-only `handicap_history` —
kun for personlig profil sin `app_user.handicap_index`, IKKE de org-
scopede `player`/`team_roster`-radene, som har sitt eget uendrede
reproduserbarhets-prinsipp fra ADR-007). `PATCH /auth/profile` logger nå
en ny rad KUN ved en FAKTISK endring til en tallverdi — leser gjeldende
verdi FØR overskriving for å unngå duplikater ved gjentatt lagring av
samme verdi, og logger bevisst IKKE ved nullstilling. Ny
`GET /auth/profile/handicap-history`. Frontend: «Vis HCP-historikk»-
lenke i `/account` sin profilseksjon.
**Scratch-verifisert, 18 sjekker:** ingen duplikat ved uendret
gjenlagring, korrekt logging ved reell endring, ingen logg ved
nullstilling, ny logg ved gjeninnsetting etter nullstilling,
kronologisk rekkefølge riktig, full isolasjon mellom to brukeres
historikk. `test_isolation.sql` 12/12. Ekte typesjekket
produksjonsbuild kjørt og bekreftet.
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon
018 kjørt mot ekte `teecup_db` (tabell bekreftet, `test_isolation.sql`
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent,
`/health`/`/dashboard`/`/account` → 200, `teeoff.no` upåvirket.
**Dermed er alle tre bekreftede punktene fra dashboard/konto-runden
(2026-07-21) ferdig bygget** (deltaker-tilgang, sekundær e-post del 1,
HCP-historikk).
- **Obligatorisk profil-fullføring ved innlogging LIVE (2026-07-22):**
svar på det pauserte "dashbordets tom-tilstand"-spørsmålet over —
brukeren avklarte at det ALLER første en innlogget bruker med en
ufullstendig profil skal se, er en fokusert «Fullfør profilen din»-
visning, ikke dashbordet. Ny migrasjon `019_profile_country_bio.sql`
(`app_user.country`, `app_user.bio` — samme nullable-kolonne-mønster
som resten av profilen, "obligatorisk" håndheves i app-laget).
`/auth/me` fikk et nytt beregnet felt `profile_complete` (sant når
fornavn/etternavn/fødselsdato/kjønn/HCP/hjemmeklubb/land ALLE er
utfylt — bilde og beskrivelse er bevisst unntatt, valgfrie).
**HCP-grensetilfelle avklart med bruker FØR bygging** (nybegynnere har
sjelden en offisiell HCP ennå): WHS-maksimum 54 brukes som
forhåndsutfylt standardverdi i skjemaet (ikke en DB-default), og
`ProfileUpdate.handicap_index` fikk en hard `le=54`-grense (kan aldri
registreres høyere) — løser grensetilfellet uten en egen "har ikke
HCP ennå"-avkrysning.
`AccountSettings` (`/account`) grener nå: er profilen ufullstendig,
vises KUN et nytt, fokusert `ProfileOnboarding`-skjema (de obligatoriske
feltene + valgfri beskrivelse, «Logg ut» tilgjengelig, INGEN tilgang
til resten av kontosidene) — er den komplett, vises den vanlige
innstillingssiden som før (nå med land+beskrivelse lagt til i det
vanlige profilskjemaet, for redigering i etterkant). `app/page.tsx`
(rot-siden) og `Dashboard`-komponenten sender en innlogget bruker til
`/account` i stedet for `/dashboard` når profilen er ufullstendig —
dekker alle innloggingsveier (magic-link/passord/2FA lander alle på
`/dashboard`, som selv gjør sjekken ved mount).
**Bevisst avgrenset:** gaten håndheves kun ved disse to naturlige
inngangspunktene, ikke ved dypere direktelenker til andre autentiserte
sider — samme skope-disiplin som tidligere runder.
**Scratch-verifisert, 16 backend-sjekker** (isolert scratch-rolle+
MinIO+engangs API-container): fersk konto starter `profile_complete:
false`, delvis utfylling forblir ufullstendig, HCP>54 avvist (422),
full utfylling gir `true`, beskrivelse er reelt valgfri, å nullstille
et obligatorisk felt i etterkant slår `profile_complete` tilbake til
`false`, full isolasjon mellom to kontoer. `test_isolation.sql` 12/12
uendret. Ekte typesjekket produksjonsbuild + et ekte HTTP-nivå-bevis
mot en kjørende produksjonscontainer (anonym mot `/` → 200 innloggings-
skjema, en ekte innlogget-men-ufullstendig sesjonscookie mot `/` →
`307 → /account`).
**Rullet ut live 2026-07-22**, bruker bekreftet eksplisitt: migrasjon
019 kjørt mot ekte `teecup_db` (kolonner bekreftet, `test_isolation.sql`
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`/
`/account`/`/` (anonym) → 200, `teeoff.no` upåvirket. **Merk:** BEGGE
brukerens egne kontoer (`erol.haagenrud@envide.no` — eier av «Tjøme
Gents» — og `hei@erol.no`) mangler i dag alle disse feltene og vil
derfor begge se profil-fullførings-skjemaet ved neste innlogging —
bekreftet tilsiktet, ikke en bug.
- **Sju punkter fra faktisk bruk av scorekort-skjermen, BYGGET OG LIVE
2026-07-24:** starthull-bug fikset (`currentHole` respekterte aldri
`round.start_hole` — `1` er truthy i JS, så `prev || start_hole` var en
no-op — forklarer trolig også det samtidig rapporterte GIR-avviket,
siden formelen selv var korrekt), kølle-bag på profilen (28 faste
typer, maks 14), nytt statistikkfelt «Anywayslag», valgfritt
statistikknivå per deltaker (default kun slag — ny kolonne
`round_participant.stat_level`), putt-avstand endret fra fritekst til
seks faste bøtter, «Hullet er spilt»-avkrysningen fjernet (overflødig
— spilt settes allerede automatisk ved slagtall). Ny migrasjon
`022_round_stats_and_bag.sql`. Numpad-layout/retningskors-ikoner for
tallvelgerne (brukerens punkt 6) er BEVISST holdt utenfor — egen
V0-prompt utarbeidet i stedet, ikke bygget selv. Full detalj i
ADR-033. 18 scratch-sjekker, `test_isolation.sql` 12/12, rullet ut mot
ekte `teecup_db`/`teecup_api`/`teecup_frontend`, `teeoff.no` upåvirket.
- **V0-prompten for numpad/retningskors/sveip-vurdering (punkt 6) BYGGET
OG LIVE, samme dag:** bruker kjørte prompten, sendte zip 13. V0 valgte
trykk-baserte Score/Statistikk-faner fremfor sveip (godt begrunnet —
unngår en tredje sveiperetning på en skjerm som allerede har to).
Flettet inn i EKSISTERENDE, allerede fungerende datalag (statLevel-
gating, kølle-bag, anywayslag, putt-bøtter, merge-før-PATCH,
starthull-fiks, `/my-rounds`-lenker) — ikke en ren erstatning, siden
V0 ikke kjente til den runden. Full detalj i ADR-033. Typesjekket
build kompilerte rent, rullet ut (kun `teecup_frontend`), `teeoff.no`
upåvirket.
- **Enda en runde brukerpunkter, ALLE BYGGET OG LIVE, samme dag:**
utslagstidspunkt + automatisk tidsbruk-visning (gjenbruker eksisterende
"Fullfør runde" som "Ferdig", ny `round.started_at`-kolonne, migrasjon
023), "Idx" → "Hcp", "par"-merking på slag-tastaturet, ni-hulls-
navigasjonsbug fikset (respekterte aldri `holes_planned`, hoppet feil
ved "Forrige"), tak på putter/chip/bunker/straffeslag/anywayslag (kan
ikke overstige antall slag), "Slett runde"-knapp (backend fantes,
manglet UI), og en ny `PATCH /rounds/{id}` for å rette bane/utslag/
antall hull MIDT i runden uten å røre allerede registrerte slag —
sperret etter fullføring. 22+8 scratch-sjekker, `test_isolation.sql`
12/12, ren build. Full detalj i ADR-033.
- **To til punkter, BYGGET OG LIVE samme dag:** starthull kan nå endres
uansett (ren metadata), utslagstid justeres når som helst, og
fullført-tidspunkt kan korrigeres i etterkant — men KUN på en allerede
fullført runde (løser "glemte å trykke Fullfør runde i flere timer").
Pluss en ny "Nærmest deg"-liste i bane-søket ved ny runde (Haversine-
avstand mot alle 174 teeoff-anlegg, geolokasjon i nettleseren, feiler
stille hvis avslått). 14 nye scratch-sjekker inkl. et ekte nearby-kall
mot teeoff. Full detalj i ADR-033.
- **Reell UX-bug fikset + "Så langt i runden"-oversikt bygget, samme
dag (2026-07-25), klar for utrulling:** brukeren rapporterte at "All
statistikk" ikke viste noe utover slag/putter -- bekreftet (kun
lesing) mot ekte `teecup_db` at `stat_level='full'` FAKTISK var
lagret riktig, så feilen var presentasjonen: alle detaljfeltene lå
bak en "Score"/"Statistikk"-fane fra forrige V0-runde som brukeren
aldri oppdaget. Fikset ved å fjerne faneløsningen helt -- alt vises nå
alltid samlet. Samtidig bygget en ny "Så langt"-oversikt
(`ScoreSoFar`-komponent: kompakt linje + utvidbar full-oversikt-
tabell med netto per hull), med et nytt backend-felt
`strokes_received` (`allocate_strokes_by_index()`, ingen ny
algoritme). 11 nye scratch-sjekker (inkl. et presist tall-eksempel på
slagfordelingen), ren build. Full detalj i ADR-033.
**Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt: kun
`teecup_api`+`teecup_frontend` redeployet, ingen migrasjon,
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **Reell produksjonsregresjon rapportert av bruker rett etter utrullingen
over, funnet og fikset umiddelbart samme dag (2026-07-25):**
"Ingenting er klikkbart i den avanserte statistikken." Root cause var
IKKE frontend (all onClick-kabling var korrekt) -- funnet ved å faktisk
gjenskape brukerens klikk-sekvens mot en fersk scratch-container:
`update_hole` (hull-PATCH) manglet fortsatt det nye påkrevde
`strokes_received`-feltet i responsen sin (lagt til i GET-endepunktet i
runden rett over, glemt i PATCH) -- ga en 500 (Pydantic-valideringsfeil)
på HVER hull-lagring, ikke bare de avanserte feltene. Frontend svelger
feilresponsen stille, så symptomet så ut som "ingenting skjer" for
ALT, ikke bare avansert statistikk (brukeren merket det trolig først
der siden Slag/Putter fra tidligere runder allerede hadde lagrede
verdier som så riktige ut). Fikset: `update_hole` beregner nå
`strokes_received` for hullet som oppdateres, samme algoritme som
`list_holes`. 14 scratch-sjekker som gjenskaper eksakt klikk-
rekkefølgen, alle bestått. **Rullet ut live 2026-07-25**, bruker
bekreftet eksplisitt: kun `teecup_api` redeployet, `/health`/
`/dashboard` → 200, `teeoff.no` upåvirket.
- **Rediger/Fullfør/Slett tonet ned + flyttet til toppen, auto-scroll ved
hull-bytte, LIVE samme dag (2026-07-25):** brukeren rapporterte at
"Fullfør runde"/"Slett runde" var for lette å trykke på ved et uhell
(lå rett under "Neste hull"). Flyttet alle tre handlingene til en
nedtonet rad øverst -- må nå aktivt scrolles til. Samtidig: "Neste
hull"/"Forrige" scroller nå automatisk opp til toppen av hull-panelet
(også ved direkte hull-valg), så det nye hullets Slag-felt alltid er
synlig med en gang. Ren frontend-endring, ingen backend/migrasjon.
Rullet ut, `teeoff.no` upåvirket.
- **"Så langt i runden" utvidet med netto/stableford-sum, putt-/kølle-
statistikk-totaler og grafisk fairway-/innspill-fordeling, LIVE samme
dag (2026-07-25):** etterspurt av bruker. Ny `StatPill`-rutenett
(Slag/Til par/Netto/Stableford/Putt/Chip/Bunker/Straffeslag/
Anywayslag), Stableford-kolonne i hull-tabellen, og to nye
`DistributionBar`-seksjoner (Fairwaytreff/Innspill, N-kategori-variant
av `SegmentedBar`-mønsteret fra tournament-leaderboard.tsx) +
gjennomsnittlig brutto score-til-par (to desimaler, fortegn) splittet
på treff-vs-bom for både fairway og innspill. Ingen backend-endring --
alt beregnes klientside fra data `GET .../holes` allerede returnerer.
Stableford beregnes alltid ut fra netto score når mulig (appen har
ingen egen "spilleform"-innstilling). Logikk verifisert manuelt mot et
regnet eksempel før utrulling. Ren frontend-endring, ingen migrasjon,
rullet ut, `teeoff.no` upåvirket.
- **"Avstand første putt" flyttet rett under "Putter", LIVE samme dag
(2026-07-25):** lå tidligere lenger ned i "full"-statistikk-blokken
(etter kølle/retning/chip-gruppen) -- flyttet til å bli første felt i
den blokken, rett under Putter-NumberPickeren (som ligger utenfor
"full"-gaten). Ren omrokkering, ingen ny logikk. Ekte typesjekket
build, rullet ut, `teeoff.no` upåvirket.
- **V0-prompt for en rikere rundestatistikk-skjerm skrevet, IKKE bygget
ennå (2026-07-25):** brukeren lastet opp en skjermopptaksvideo
(`screen-20260724-133013-1784892586987.mp4`, 26 sek, av en KONKURRENT-
apps statistikkskjerm) og ba om et V0-prompt inspirert av innholdet,
eksplisitt IKKE et plagiat. Video analysert bilde for bilde (ffmpeg i
en engangs Docker-container, ikke installert på verten). Innholdet
identifisert dekkes i stor grad av data vi ALLEREDE sporer
(fairwaytreff/innspill-retning/putts/chip/bunker/straffeslag/putt-
lengde-bøtte) -- ingen backend-endring skulle trengtes for de fleste
målene. Prompt skrevet til bruker i chatten (ikke lagret som egen
fil) -- beskriver mål/informasjonsarkitektur/tilgjengelighetskrav
abstrakt (donut med sentertall, gauge-stolper for avvik fra par,
retnings-diagram for bom-retning) UTEN å kopiere konkurrentens
eksakte fargevalg/layout/ordlyd, og ber eksplisitt om TeeCups egen
merkevareidentitet (grønn/oransje) + en egen visuell vri.
**Én bevisst forskjell fra videoen, valgt for å unngå plagiat OG fordi
det matcher vårt eget datamodell:** videoens puttlengde-bøtter
(<1m/1-2/2-4/4-8/+) er ANDRE enn TeeCups allerede lagrede seks bøtter
(<1m/<2m/<3m/<5m/<8m/8m+, ADR-033) -- promptet ber om TeeCups egne
bøtter, ikke videoens. "Lengste drive" (krever GPS/avstandsmåling vi
ikke har) bevisst utelatt fra promptet.
**Bygget og rullet ut live 2026-07-25, samme dag:** zip 14 mottatt.
Diffet mot live-treet FØR noe ble tatt inn (samme rutine som alltid) --
kun to reelt nye filer (`components/round-stats.tsx`,
`app/rounds/[id]/stats/page.tsx`), resten var V0s vanlige uvitende
reverts (bl.a. sin egen `/rounds/[id]`-ruteversjon fra FØR
`/my-rounds`-omdøpingen ADR-033 gjorde 2026-07-23 -- korrekt hoppet
over). Ruten lagt inn som `app/my-rounds/[id]/stats/page.tsx` i stedet
(samme kollisjon-unngåelse). `globals.css` sin nye `--chart-1..6`
data-viz-fargeskala slått sammen inn (V0 sine egne, mer omtrentlige
`--primary`/`--ring`/`--brand-orange`-verdier IKKE tatt inn -- beholdt
de presise OKLCH-verdiene fra ADR-016).
**Datalag skrevet fullstendig om fra mock:** henter `GET /rounds/{id}`
+ `GET .../participants/{id}/holes` (samme endepunkter round-detail.tsx
allerede bruker) -- ingen ny backend. Ny `computeStats()`-funksjon
regner ut ALT fra rå hull-data ved lesing (score-kategorier,
snitt-til-par totalt/per hulltype, fairway-fordeling + score-splitt,
GIR totalt/per hulltype/kryss-fairway + score-splitt, bom-retning på
green, putt-fordeling 1/2/3-putt + snitt per hulltype + med/uten GIR,
én-putt% per TeeCups egne seks puttlengde-bøtter + lengdefordeling
hit/miss, chip-fordeling, scrambling%, sand save%, bunker/straffeslag
per runde + score-splitt). Hver seksjon skjules helt når det ikke
finnes nok data (samme "vis kun det som faktisk finnes"-prinsipp som
"Så langt i runden"). Ny enkel spillervelger (pill-rad) lagt til når
runden har flere deltakere -- default til eieren.
**"Se full rundestatistikk"-lenke** lagt inn i `CompletedBanner` i
round-detail.tsx (V0s egen versjon hadde denne, men i sin ellers
fullstendig reverterte fil -- portert manuelt inn i vår LIVE versjon
i stedet for å ta hele filen).
**Matematikken verifisert FØR utrulling:** `computeStats()`-logikken
portert til et frittstående Node-script og kjørt mot et
hånd-etterregnet 6-hulls syntetisk datasett (blandet par 3/4/5,
fairwaytreff/-bom, GIR-treff/-bom, bunkerslag) -- alle 15+ utledede
tall stemte eksakt med manuell utregning. Ekte typesjekket
produksjonsbuild kjørt og bekreftet (`/my-rounds/[id]/stats` listet
som ny rute).
**Rullet ut live 2026-07-25**, ren frontend-endring, ingen migrasjon,
`docker compose up -d --build teecup_frontend`, `/health`/`/my-rounds`
→ 200, `teeoff.no` upåvirket.
- **Rundeliste + scorekort-redesign, LIVE samme dag (2026-07-25):**
brukeren delte en ny skjermopptaksvideo av "Egne runder"-listen (som
manglet ALL score-informasjon) og av scorekort-registreringen (rapportert
som "veldig dårlig designet" -- for mange ulike knapp-typer stablet
oppå hverandre) + "Runde fullført"-siden (rapportert som "veldig mye
dobbel informasjon"). Reflektert over problemstillingen (kort, per
brukerens ønske) før to V0-prompter ble skrevet.
**Fikset direkte, uten V0:** "Runde fullført"-duplikatet -- `ScoreSoFar`
("Så langt i runden") skjules nå helt når runden er fullført, siden
den nye rundestatistikk-siden dekker akkurat det samme, langt
grundigere. Ny backend-beregning i `_load_round_out`
(`app/routers/rounds.py`): `owner_holes_played`/`owner_total_score`/
`owner_score_to_par`, en enkel aggregatspørring mot `round_hole` for
eierens egen deltaker-rad -- verifisert med 10 scratch-sjekker
(inkl. at en gjests score IKKE påvirker eierens aggregat).
**Zip 15 og 16 mottatt** (samme v0.app-prosjekt, kontinuerlig --
`round-card.tsx`/`own-rounds.tsx` identiske i begge, zip 16s
`round-detail.tsx` var den nyeste med selve scorekort-redesignet; zip
15 brukt kun for å bekrefte at zip 16 var det riktige, endelige
eksportet).
`round-card.tsx` fikk en ny `ScoreTile` -- prominent resultat+til-par
for fullførte runder, hull-fremdrift-bar ("6/18 hull spilt") for
runder som pågår, pluss en HCP-differensial-chip når runden telte.
Datalag i `own-rounds.tsx` skrevet om fra mock til ekte fetch, kobler
de nye `owner_*`-feltene fra backend + eierens `score_differential`
(kun vist når `counts_for_handicap`).
`round-detail.tsx` fikk en ny kollapsbar "Flere detaljer"-seksjon
(lukket som default) som nå rommer kølle/utslag-retning/innspill-
retning/chip-bunker-straffeslag/anywayslag -- Slag/Putter/Avstand
første putt forblir alltid synlig over. Datalag/statLevel-gating/
bucket-basert puttlengde/maxValue-capping (alt bygget tidligere denne
økten) bevisst IKKE revertert til V0s eldre mock-baseline, kun selve
kollaps-mekanismen og plasseringen ble hentet derfra. Lagt til en kort
kode-kommentar (ikke noe bygget UI ennå) som reserverer plass ved
siden av hull-headeren til en fremtidig avstandsmåling-indikator,
bekreftet av bruker at dette kommer senere.
Ekte typesjekket produksjonsbuild kjørt og bekreftet (alle 19 ruter).
**Rullet ut live 2026-07-25**, ren frontend-endring (+ den lille
backend-tilføyelsen over), ingen migrasjon, `/health`/`/my-rounds` →
200, `teeoff.no` upåvirket.
- **Reelt hull funnet og fikset SAMME dag, rapportert av bruker rett
etter forrige punkt ("Hvor er scorekortet?"):** da "Så langt i runden"
ble skjult for fullførte runder (se over), forsvant OGSÅ den eneste
plassen den rå hull-for-hull-tabellen (Hull/Par/Score/Netto/Sum) fantes
-- `round-stats.tsx` (den nye dedikerte statistikk-siden) hadde KUN
utledet/aggregert statistikk (donuter, stolper), ingen tabell med de
faktiske tallene per hull. Fikset ved å legge til en ny "Scorekort"-
seksjon FØRST på statistikk-siden (samme tabell-mønster som "Så
langt" hadde -- Hull/Par/Score/Netto/Stableford/Sum), åpen som
default. La til `start_hole` i `round-stats.tsx` sin `ApiRound`-type
og `strokes_received` i `ApiHole`-typen (sistnevnte kom allerede fra
backend, bare ikke lest av denne siden ennå) for å kunne vise hullene
i rundens FAKTISKE rekkefølge (samme sirkulære start_hole-logikk som
round-detail.tsx) i stedet for bare rå hullnummer 1-18. Ekte
typesjekket build kjørt og bekreftet. Rullet ut, ren frontend-endring,
ingen backend/migrasjon, `teeoff.no` upåvirket.
- **Ny scorekort-presentasjonsregel + V0-prompt skrevet, IKKE bygget
ennå (2026-07-25):** brukeren delte et referansebilde av et
tradisjonelt horisontalt golf-scorekort og formulerte en generell
regel: hull listet HORISONTALT (som kolonner) → sum TIL HØYRE; hull
listet VERTIKALT (som rader) → sum UNDER. Dagens "Scorekort"-tabell
(bygget rett over samme dag) er vertikal med sum som løpende KOLONNE
-- ikke i tråd med regelen. V0-prompt skrevet (horisontalt scorekort,
hull 1-9/10-18 som kolonner, Ut/Inn-sum til høyre for hver halvdel,
Hcp/Par/Score/Netto/Stableford-rader, håndterer både 9- og 18-hulls
runder med vilkårlig start_hole), bevisst IKKE et plagiat av
referansebildet (egne farger, egen "Hcp"-term i stedet for bildets
"Slope", ingen kopiert spiller-header). Full detalj i
ARCHITECTURE_DECISIONS.md. **Presisert samme dag, før noe ble sendt:**
brukeren spurte om et mykt "scroll hvis nødvendig"-unntak fanget opp
målet om ALDRI å måtte scrolle -- svart nei (reell breddekonflikt med
"lesbar uten briller"-kravet, ikke bare ordlyd) og skrevet om til et
hardt "ingen scroll"-krav som eksplisitt forteller V0 HVORDAN det
oppnås (kompakte fete høykontrast-siffer i selve rutenettet, smale
forkortede rad-labels i en trang gutter -- "lesbar uten briller"
avgrenset til labels/knapper, ikke enkeltsifre). Full detalj i
ARCHITECTURE_DECISIONS.md.
**Zip 17 mottatt og BYGGET/LIVE samme dag:** en ekte HTML `` med
`` faste kolonnebredder -- ingen scroll-container i det hele
tatt, løst med kompakte celler i stedet. Score-cellene bruker FORM
(sirkel=under par, firkant=over par) + fylt/ufylt (2+ slag av) for å
aldri stole på farge alene, pluss en egen symbolforklaring. Egen,
NY dedikert side `/my-rounds/[id]/scorecard`
(`components/round-scorecard.tsx`) -- ikke slått sammen med
`round-stats.tsx`, siden V0 designet den med egen side-chrome (header,
rundesammendrag). Datalag skrevet om fra mock til ekte fetch; V0s egen
`strokesReceived()`-formel (generisk modulo) BEVISST forkastet til
fordel for backend sin allerede beregnede `strokes_received` (samme
`allocate_strokes_by_index()` som resten av appen -- unngår to ulike
HCP-slagfordelings-implementasjoner). Samme sirkulære
start_hole-rekkefølge som resten av rundeskjermene. Den gamle
vertikale "Scorekort"-tabellen i `round-stats.tsx` FJERNET (erstattet
med en lenke til den nye siden) -- `round-stats.tsx` er nå rendyrket
aggregert statistikk, det rå scorekortet bor kun ett sted. V0 la selv
til en "Se scorekort"-knapp i `CompletedBanner` (round-detail.tsx) ved
siden av den eksisterende "Se full rundestatistikk" -- tatt inn.
**Samtidig, rapportert av bruker:** Anywayslag manglet helt fra
rundestatistikk-siden sin "Chip, bunker og straffeslag"-seksjon.
**Feilrettet rett etterpå, samme dag:** min første fiks slo feilaktig
sammen Anywayslag-tallet i DEN eksisterende seksjonen og omdøpte hele
seksjonen til "Annet" -- brukeren påpekte at "Chip, bunker og
straffeslag" skulle beholde navn+innhold uendret, og at "Annet" skulle
være en EGEN, ny seksjon RETT ETTER med kun anywayslag-tall (total per
runde + andel hull med anywayslag). Rettet umiddelbart. Notert at et
fritekst-notatfelt trolig havner i "Annet" senere. Ekte typesjekket
build kjørt og bekreftet (ny rute `/my-rounds/[id]/scorecard` listet).
Rullet ut (to runder), ren frontend-endring, ingen backend/migrasjon,
`teeoff.no` upåvirket.
- **Rundestatistikk-seksjonene starter nå kollapset, LIVE samme dag
(2026-07-25):** brukeren ba om at "trekkspillet" (StatCard-seksjonene
på `/my-rounds/[id]/stats`) skal vises sammenslått -- må klikkes for å
se innholdet. `StatCard` sin `defaultOpen` endret fra `true` til
`false` (ingen kallsted overstyrte den, så én linje dekket alle
seksjonene). Ekte typesjekket build, rullet ut, `teeoff.no` upåvirket.
- **Notert i FEATURE_BACKLOG.md, IKKE bygget:** brukeren ba om et notat
om et femte fremtidig turneringsformat, "Flaggturnering" (utover de
fire fra 2026-07-19-runden) -- krever en visning av GJENSTÅENDE slag
for spilleren, oppdatert etter hvert hull, pluss en fremtidig idé om
å bruke GPS (når/hvis integrert) til å markere hvor langt spilleren
faktisk kom. Lagt til i samme seksjon som de fire andre formatene.
- **Tre punkter fra brukeren, ALLE BYGGET, SCRATCH-VERIFISERT OG LIVE
2026-07-25:** (1) enkeltbane-anlegg (f.eks. Tjøme Golfklubb) dropper nå
det overflødige banenavnet ved import/runde-opprettelse — kun "Tjøme
Golfklubb", ikke "Tjøme Golfklubb – Hovedbanen" (flerbane-anlegg som
Ålesund beholder uendret "Anlegg – Bane"-navn). (2) Runder kan nå
navngis (`round.name`, migrasjon `024_round_name.sql`, valgfritt,
redigerbart i etterkant) — vises på tvers av rundeliste/rundeside/
scorekort/statistikk, faller tilbake til banenavn når ikke satt. (3)
Profilens "Land"-felt er nå en nedtrekksliste (kun "Norge" foreløpig,
klargjort for flere), plassert FØR "Hjemmeklubb", som selv ble
omgjort til en søkbar liste mot teeoffs klubbregister (gjenbruker
eksisterende `/rounds/official-search`, ingen ny backend-kode). Full
detalj i ARCHITECTURE_DECISIONS.md. Bruker bekreftet eksplisitt:
migrasjon 024 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt
12/12, begge containere redeployet, `/health`/`/dashboard`/`/my-rounds`/
`/account` → 200, `teeoff.no` upåvirket.
- **Refleksjonsrunde 2026-07-25: hvorfor organisasjon? + venner/deling —
DESIGNET, IKKE bygget.** Brukeren spurte hvorfor organisasjon i det
hele tatt trengs, gitt at ADR-033 nå gjør en enkelt bruker fullt
selvstendig. Veide A (bruker-eide turneringer, som runder) mot B
(organisasjon beholdt, men opprettelsen gjøres usynlig/automatisk) —
A avvist (ville krevd duplisering av HELE turnering-apparatet som er
bygget rundt RLS/`organization_id`, og er ikke reversibelt i noen
retning), B valgt (rører verken skjema eller de ni eksisterende
routerne, kun frontend-orkestrering — `POST /orgs` krever allerede kun
`name`). Skrevet som **ADR-035** (organisasjon-B-beslutningen + et
konkret 7-blokks dashbord-forslag: Hurtighandlinger/Kommende runder/
Kommende turneringer/Statistikk/Spilte baner/Venner/Organisasjoner —
sistnevnte nedtonet, kun synlig ved reelt flere org-er) og **ADR-036**
(nytt vennekonsept — gjensidig forespørsel/aksept, privat
kategorisering i faste grupper som Make/Nær familie/Golfvenner/osv.,
ny rundevisibilitet `public`/`private`/`friends` med eksplisitt
gruppevalg, og et tiered personsøk — venner→samme klubb→samme
land→globalt, navnerekkefølge-uavhengig — delt mellom "finn venn" og
en fremtidig "legg til ekte medspiller"-utvidelse av
`round_participant.user_id`, som allerede lå ubrukt i skjemaet som en
eksplisitt notert "v1-avgrensning" i ADR-033). V0-prompt for det nye
dashbordet skrevet (i FEATURE_BACKLOG.md), IKKE sendt til V0 ennå.
**Ingen kode skrevet** — bevisst en design-/dokumentasjonsrunde, ikke
en byggerunde, på brukerens eksplisitte instruks. Full detalj i
ARCHITECTURE_DECISIONS.md (ADR-035/036) og FEATURE_BACKLOG.md (åpne
spørsmål, foreslått 3-fase byggerekkefølge for venner-delen).
- **Oppfølging samme dag:** bruker bekreftet at en lagt-til ekte
medspiller SKAL se runden i sin egen "Egne runder"-liste (ADR-036 fase
3, tidligere bevisst uavklart). Presiserte samtidig en ny teknisk
konsekvens i ARCHITECTURE_DECISIONS.md: `RoundOut` sine
`owner_*`-statistikkfelt må bli viewer-relative, ikke alltid eierens,
og et NYTT åpent spørsmål dukket opp — skal medspilleren også kunne
SKRIVE egne hull-tall, ikke bare lese? Ikke avgjort. Fortsatt ingen
kode skrevet.
- **Dashbord-redesign (ADR-035) BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):**
zip 18 mottatt og integrert — kun `dashboard.tsx` var reelt nytt (samme
full-reeksport-mønster som alltid), pluss en genuin MERGE (ikke revert)
i `tournament-card.tsx`: V0s nye `organizer`-merkelapp lagt til SIDE OM
SIDE med den eksisterende `orgId`-baserte lenkelogikken (offentlig vs.
innlogget kontekst) som V0 ikke kjente til. Datalag skrevet fra bunnen:
"Kommende turneringer" slår sammen deltaker- (`me.my_tournaments`) og
arrangør-turneringer (hentet per org, alle organisasjoner brukeren er
medlem i) til ÉN tidssortert liste, filtrert til `draft`/`active`-status.
"Statistikk"/"Spilte baner" er REN klientside-utledning fra eksisterende
`GET /rounds` + `GET /auth/profile/handicap-history` — ingen nye
endepunkter trengt. "Ny turnering"-hurtighandlingen implementerer selve
ADR-035-mekanismen: oppretter/gjenbruker organisasjon USYNLIG først
(kun ved faktisk innsending, ikke når skjemaet åpnes — unngår en
foreldreløs org ved avbrutt skjema), deretter turneringen, deretter
navigerer rett inn i den. "Bli med med kode" gjenbruker samme
`by-code`-oppslag som `login-form.tsx` sin `JoinByCode`, nå som en
dashbord-lokal variant. "Venner"-seksjonen vises med en ærlig
tom-tilstand (ADR-036 er ikke bygget ennå) — CTA-en er bevisst uten
href/onClick, ikke en lenke til noe som ikke finnes. "Spilte baner"
fikk sine listeelementer gjort om til ren visning (ikke lenker) siden
API-et ikke eksponerer noen stabil bane-id å lenke til per runde.
Ekte typesjekket produksjonsbuild kompilerte rent, alle 21 ruter
listet. **Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt:
`docker compose up -d --build teecup_frontend` (gjenskapte også
`teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200,
`teeoff.no` upåvirket.
- **Oppfølging samme runde, avklart med bruker:** medspillere skal kunne
registrere score for HELE flighten (ikke bare egen rad) når ADR-036
fase 3 bygges — oppdatert i ARCHITECTURE_DECISIONS.md/FEATURE_BACKLOG.md.
To nye notater lagt i FEATURE_BACKLOG.md (ingen design/bygging ennå,
kun fanget opp): (1) turneringsoppsett mangler en vei til å FLYTTE en
rostret spiller til et annet lag (kun fjerning finnes i dag), (2) et
reelt modelleringsspørsmål om flere flighter i én frittstående runde
(f.eks. "min flight + vennenes flight bak oss samme dag") — to
prinsipielt ulike retninger skissert, ingen valgt.
- **ADR-036 fase 1 (venner-kjernen) BYGGET OG SCRATCH-VERIFISERT, IKKE
ENNÅ RULLET UT (2026-07-25):** ny migrasjon `025_friends.sql`
(`friendship` + `friend_categorization`, ingen RLS — samme
`plain_connection()`-mønster som runder) og nytt `app/routers/
friends.py`: `GET /people/search` (tiered venn→klubb→land→globalt,
navnerekkefølge-uavhengig token-matching, min. 2 tegn før noe
returneres), `POST /friends`/`POST /friends/{id}/accept`/
`DELETE /friends/{id}` (forespørsel/aksept/avslå-kanseller-avvenn — én
DELETE dekker alle tre), `GET /friends`, `PUT /friends/{friend_user_id}/
categories` (erstatter hele settet, krever akseptert vennskap). Router
registrert i `main.py`, `/people`+`/friends`-rewrites lagt til i
`next.config.mjs` (ny frontend-rute vil hete `/my-friends`, IKKE
`/friends` — unngår samme kollisjonsfelle som `/rounds` fra før).
**Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API-
container, 5 syntetiske brukere): 31 sjekker, inkl. presist bevist
tiered rangering for 4 distinkte brukere samtidig, og at
kategorisering er EKTE PRIVAT (bekreftet: B ser aldri kategoriene A
satte B i). `test_isolation.sql` fortsatt 12/12. Ekte typesjekket
produksjonsbuild kompilerte rent. **Rullet ut live 2026-07-25**, bruker
bekreftet eksplisitt: migrasjon 025 kjørt mot ekte `teecup_db` (begge
tabeller bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
`docker compose up -d --build teecup_api teecup_frontend`. Begge
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket. Verifisert presist at ruten faktisk når FastAPI (ikke bare
at Next.js svarte): anonymt `GET /people/search`/`GET /friends` over
ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. Frontend
bevisst IKKE hånd-kodet denne gangen (V0-prompt skrevet i
FEATURE_BACKLOG.md i stedet, matcher etablert mønster/tidligere
korrigering) — venter på at brukeren kjører den i v0.app.
- **ADR-036 fase 1 FRONTEND BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):**
zip 19 mottatt og integrert — kun `components/friends.tsx` og
`app/friends/page.tsx` var reelt nye fra V0 (samme full-reeksport-
mønster som alltid, resten forventede reverts av allerede tilpassede
filer, hoppet over). **V0s egen rute (`/friends`) BEVISST IKKE brukt**
— flyttet til `/my-friends`, siden `/friends` nå er API-prefikset
(samme kollisjonsklasse som `/rounds` → `/my-rounds` tidligere, unngått
fra start denne gangen i stedet for oppdaget i produksjon).
Datalag skrevet fullstendig om fra V0s mock til ekte fetch mot
`/people/search`+`/friends`-endepunktene — TS-typene speiler Pydantic-
modellene i `app/routers/friends.py` felt-for-felt. Kategori-koder
(`spouse`, `golf_friends`, osv.) mappet mot V0s norske visningsnavn via
en delt `CATEGORY_OPTIONS`-liste i SAMME rekkefølge som backend sin
`Category`-type. Søkefeltet håndhever samme 2-tegns-minimum som
backend (viser en forklarende tekst i stedet for å bare returnere
tomt). "Send forespørsel"/"Godta"/"Avslå"/"Kanseller"/"Fjern venn"
trigger alle en full refetch av `GET /friends` etterpå (samme
refetch-etter-mutasjon-mønster som resten av appen) — kun kategori-
avkrysning er lokalt optimistisk (matcher PUT-endepunktets
erstatt-hele-settet-kontrakt).
**Dashbordets "Venner"-blokk koblet til ekte data i samme runde:**
root-komponenten henter nå `GET /friends`, viser ekte avatar-initialer
+ antall venner + ventende forespørsler (samme visuelle design som
opprinnelig i zip 18, som ble bevisst forenklet til en inert tom-
tilstand forrige runde siden backend ikke fantes ennå) — begge
knappene ("Se venner"/"Søk etter venner") lenker nå til `/my-friends`.
Ekte typesjekket produksjonsbuild kompilerte rent, `/my-friends` listet
blant 22 ruter. **Rullet ut live 2026-07-25**, bruker bekreftet
eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte
også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt).
Begge containere boot-et rent, `/health`/`/dashboard`/`/my-friends` →
200, `teeoff.no` upåvirket. **ADR-036 fase 1 (venner-kjernen) er
dermed helt ferdig, backend + frontend, live.**
- **Notat 2026-07-25: scramble-statistikk (utslag brukt per spiller) —
IKKE bygget, kun fanget opp.** Brukeren ba om at scramble-turneringer
skal føre statistikk over hvor mange ganger hver spillers utslag ble
valgt av laget. Dette er reelt NY datamodell — dagens `hole_score`/
`match_hole_result` for scramble er en delt rad per side, ingen
kobling til HVILKEN spiller sitt utslag ble brukt. Trolig samme
problemstilling for greensome. Krysset mot det allerede eksisterende
"Scramble-grensesnitt"-punktet i ARCHITECTURE_DECISIONS.md sin "Åpne
spørsmål"-seksjon, full detalj i FEATURE_BACKLOG.md.
- **Bug fikset: `display_name` synkroniserte aldri med for-/etternavn
(2026-07-25), rapportert av bruker med skjermbilde av dashbordet.**
`app_user.display_name` settes i dag KUN fra e-postens lokaldel ved
kontoopprettelse (`verify_magic_link`) — ADR-031s profil-fullføring la
til atskilte `first_name`/`last_name`-felt, men rørte aldri
`display_name`. Konsekvens: en bruker med fullstendig utfylt profil
viste fortsatt e-post-avledet plassholdernavn overalt `display_name`
brukes (org-medlemslister, invitasjons-e-post, dashbord-hilsen — ikke
bare dashbordet). **Fikset** i `app/routers/auth.py` sin
`update_profile`: synkroniserer nå `display_name` automatisk til
`"{first_name} {last_name}"` hver gang et av de to feltene endres via
`PATCH /auth/profile` — KUN når begge er satt etterpå (unngår et
halvferdig navn ved delvis utfylling). Scratch-verifisert (7/7 sjekker:
fersk konto får fortsatt e-post-plassholder, delvis utfylling (kun
fornavn) rører IKKE `display_name` ennå, komplett for-/etternavn
synkroniserer korrekt, urelaterte PATCH-er (f.eks. HCP) lar
`display_name` stå urørt, en SENERE navneendring re-synkroniserer på
nytt).
**Rullet ut mot ekte systemer 2026-07-25**, bruker bekreftet
eksplisitt (valgte "kjør begge deler"): `teecup_api` redeployet, samt
en engangs data-rettelse kjørt direkte mot ekte `teecup_db`
(`UPDATE app_user SET display_name = ... WHERE first_name/last_name
utfylt OG display_name avvek`) for å rette allerede-utfylte kontoer
som ikke ville blitt rettet av seg selv (krever en NY navneendring for
å trigge synk-koden). Bekreftet FØR kjøring med en dry-run `SELECT`
(2 kontoer berørt: `erolhaagenrud@gmail.com` → "Tore Morell",
`hei@erol.no` → "Erol Haagenrud"), kjørt med `RETURNING` for å
bekrefte nøyaktig hvilke rader som ble endret, `test_isolation.sql`
fortsatt 12/12 etterpå. Ingen migrasjon (ren datarettelse + kodefiks).
- **Varsler (push til telefon + in-app varslingssenter) — DESIGNET
2026-07-25, IKKE bygget.** Brukeren spurte om dagens PWA kan varsle
telefonens eget system (f.eks. ny venneforespørsel), og ba om en
in-app-fallback (bjelle-indikator + uleste-side) med et V0-prompt klart
"i tilfelle". Svar: ekte push er teknisk mulig (ADR-028s PWA-fundament),
men iOS Safari krever PWA-en installert til hjemskjermen for at Web
Push skal fungere i det hele tatt, pluss ny infrastruktur (VAPID,
`push_subscription`-tabell, `pywebpush`-utsending, eksplisitt
tillatelse) — anbefalt som EGEN, senere runde, ikke første steg.
Anbefalte i stedet et in-app varslingssenter først (ny
`notification`-tabell, triggerpunkter ved venneforespørsel sendt/
akseptert, ny `/my-notifications`-rute — ikke `/notifications`, samme
kollisjonsklasse unngått som `/rounds`/`/friends`). V0-prompt skrevet
i FEATURE_BACKLOG.md, ikke sendt ennå. Ingen kode skrevet — ren
design-/dokumentasjonsrunde.
- **Oppfølging samme dag: e-post-fallback for varsler + PWA-
installasjon vurdert, IKKE bygget.** Varsel-V0-prompten sendt til
bruker. Bruker foreslo en betinget e-post-fallback ved venneforespørsel
(kun hvis mottaker har samtykket til e-post fra TeeCup) — sjekket at
INGEN generell kommunikasjons-samtykke-flagg finnes i dag (kun
ADR-017s turnering-registrerings-samtykke, noe annet), foreslo nytt
opt-in `app_user.notification_emails_enabled` + en sjette
`send_friend_request_email()`-funksjon i `app/email.py` (samme mønster
som de fem eksisterende). Bruker spurte også hvordan få brukere til å
installere PWA-en "nærmest umiddelbart" — vurdert grundig: Android kan
fange `beforeinstallprompt` og vise egen timing, iOS Safari har INGEN
programmatisk installasjonsvei (kun instruksjonsoverlegg mulig, hard
Apple-begrensning) — anbefalte å vise oppfordringen rett etter
obligatorisk profil-fullføring (universelt sjekkpunkt alle nye brukere
allerede går gjennom) fremfor bokstavelig "umiddelbart". Begge kun
vurdert/skissert i FEATURE_BACKLOG.md, ingen kode skrevet, intet
V0-prompt for PWA-delen ennå.
- **To nye drøftingspunkter, IKKE besluttet eller bygget (2026-07-25):**
bruker lastet opp to skjermbilder av Golf GameBooks leaderboard-løsning
(egen "Leaderboards"-fane, rangert liste, trykk-ut til fullt
scorekort) og spurte om (1) et tilsvarende leaderboard for
runder/turneringer, usikker på plassering, og (2) om TeeCup burde få
1-2 nye designfarger. Drøftet grundig i FEATURE_BACKLOG.md, ikke
konkludert: fant at "runder" kan få dette NÅ (flere-deltakere-støtte
finnes allerede, ingen ADR-036-avhengighet) mens "turneringer" sitt
EKSISTERENDE leaderboard er lag-poeng (Ryder Cup-matchplay), strukturelt
noe annet enn Golf GameBooks individuelle rangering — åpent
avklaringsspørsmål. For fargespørsmålet: fant at en blåtone ALLEREDE
finnes i `--chart-3` (bare ikke løftet til en kjerne-designtoken) —
anbefalte å gjenbruke DEN i stedet for å finne på en helt ny, og
anbefalte mot flere enn én ekstra farge. Ingen kode skrevet, ingen
V0-prompt — ren refleksjonsrunde på brukerens eksplisitte instruks.
- **In-app varslingssenter + rundeleaderboard-backend BYGGET OG SCRATCH-
VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** to ting i samme runde.
(1) Brukeren lastet opp zip 20 — resultatet av varsel-V0-prompten fra
2026-07-25 (bjelle-ikon i dashbord-header, egen varslingsside). Bygget
matchende backend: migrasjon `026_notifications.sql` (tabell
`notification`, `plain_connection()`-mønster som resten av bruker-eid
data), `app/routers/notifications.py` (fire endepunkter + delt
`create_notification()`-hjelpefunksjon), to trigger-punkter i
`friends.py` (venneforespørsel sendt/akseptert — de eneste hendelsene
som faktisk finnes i dag). Frontend integrert kirurgisk (kun de nye
filene + et uttrekk fra V0s dashbord-eksport, ikke en full revert):
`components/notifications.tsx` skrevet om fra V0s mock-scenario til
ekte fetch, ny rute `/my-notifications` (IKKE `/notifications` — samme
kollisjonsklasse unngått fra start som `/rounds`/`/friends`),
bjelle-komponenten portert inn i den LIVE `dashboard.tsx` sin header,
koblet til et ekte `GET /notifications/unread-count`-kall.
(2) Brukeren ba samtidig om et leaderboard for frittstående runder
(opptil 13+ spillere mulig, flere flighter). Bygget ny
`GET /rounds/{round_id}/leaderboard` i `app/routers/rounds.py` — brutto
OG netto score-til-par + "thru"-antall PER deltaker, sortert stigende
på brutto. Netto bruker samme `allocate_strokes_by_index`-algoritme som
resten av appen (`strokes_received` i `list_holes`), denne gangen
summert over KUN de faktisk spilte hullene.
**Scratch-verifisert grundig, 39/39 sjekker i to testløp** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container, samme mønster som hele prosjektet): full varsel-syklus begge
retninger + `mark-all-read` + kryss-bruker-isolasjon (bruker B kan ikke
markere bruker A sitt varsel som lest via id-gjetting), full
leaderboard-runde (3 deltakere, ulik fremdrift/score, riktig rangering/
thru/to-par for alle), kryss-bruker-autorisasjon (403), ukjent
runde-id (404) — OG en dedikert netto-kryssjekk der resultatet ble
sammenlignet mot en HELT UAVHENGIG beregning via
`handicap_engine.allocate_strokes_by_index` kalt direkte fra
testskriptet (ikke bare "endepunktet svarte 200") — stemte eksakt,
inkl. et bevisst vekslende stroke-index-mønster og kun 9 av 18 hull
spilt, for å teste allokeringen over et REELT delvis spilt sett, ikke
et trivielt sammenfallende tilfelle. `test_isolation.sql` fortsatt
12/12. Ekte typesjekket produksjonsbuild av frontend kompilerte rent,
`/my-notifications` listet blant rutene.
**V0-prompt for selve leaderboard-VISNINGEN skrevet og sendt til
bruker** (se FEATURE_BACKLOG.md for hele prompten) — rangering med
delt plassering, brutto/netto-veksling, "thru X"/"Ferdig"-tilstander,
kompakt mini-variant til rundens detaljside, alltid form+farge for
over/under par. Ikke kjørt i v0.app ennå — leaderboardets FRONTEND
kommer i en senere runde.
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon
026 kjørt mot ekte `teecup_db` (tabell `notification` bekreftet,
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
`/health`/`/dashboard`/`/my-notifications` → 200, `teeoff.no`
upåvirket. Verifisert presist at `/notifications`-ruten faktisk når
FastAPI (ikke bare at Next.js svarte): anonymt `GET /notifications`
over ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.
- **Navneformat-regel + turnering-scope avklart, IKKE bygget (2026-07-26):**
brukeren instruerte at direkte adressering (hilsener, e-post/varsler
rettet til mottakeren) alltid skal bruke KUN fornavn, mens vanlige
visninger (lister, roster, chat) fortsatt bruker fullt navn/initialer —
lagt til som ny stående regel (se egen seksjon over). Kjent brudd
notert: dashbord-hilsenen bruker i dag fullt navn, ikke rettet ennå.
Deretter bedt om analyse av .md-filene for prioritering — landet på å
avklare turnering-leaderboard-spørsmålet fra 2026-07-25 først. Svaret
avdekket et vesentlig STØRRE ønske enn antatt: TeeCup skal etter hvert
støtte ekte INDIVIDUELLE turneringer (ikke bare lag), disse skal kunne
gå over flere runder, og det skal være mulig med et Order of Merit
(sesong-sammenlagt på tvers av flere arrangementer, brukerens eksempel:
"klubbdager" gjennom en sesong). Grundig notert i FEATURE_BACKLOG.md og
ARCHITECTURE_DECISIONS.md (ADR-011s eget notat + ny "Åpne spørsmål"-
post 6) — inkl. en reell strukturell kollisjon identifisert: en
flerrunde-individuell-turnering vil kollidere med ADR-033 sitt bevisst
org-UAVHENGIGE frittstående rundesystem, må avklares eksplisitt før
bygging. **Ren notat-runde, ingen ADR skrevet, ingen kode** — brukeren
ba eksplisitt kun om at dette dokumenteres nå.
- **Flere flighter i én frittstående runde — presisert videre, fortsatt
IKKE besluttet (2026-07-26):** oppfølgende avklaring samme runde.
Brukeren presiserte: oppsett av flere flighter er ÉN handling utført av
én person (samme gjest-mønster som i dag, bare flere flight-grupper),
scoreregistrering per flight er et SENERE, separat ansvar (forventet
løst av ekte medspillere, ADR-036 fase 3) — og viktigst: leaderboardet
skal KUN dekke flightene som ble satt opp SAMMEN i én handling, ikke
"alle som spilte samme bane samme dag". Det siste peker sterkt mot
retning 1 (løs gruppering av separate `round`-rader) fra forrige
runde, men er ikke formelt bekreftet som byggeretning. Brukeren
påpekte selv at dette ligger i grenselandet mot "individuelle
turneringer"-punktet rett over — notert eksplisitt som en mulig
forening av de to, ikke to separate systemer. Se FEATURE_BACKLOG.md
for full detalj. Ren notat-runde, ingen kode, ingen ADR.
- **ADR-037 skrevet: grunnstruktur for individuelle/flerrunde-turneringer
(2026-07-26), samme dag som de to foregående notat-rundene.** Bruker ba
om å starte ADR-runden på strukturspørsmålet direkte (ikke bare
notere). Fire load-bærende beslutninger avklart eksplisitt
(AskUserQuestion), i rekkefølge: (1) ny, PARALLELL org-scopet
datamodell for individuelle turneringer — IKKE en utvidelse av
ADR-033s frittstående `round`-tabeller, bevisst for å unngå hybrid/
betinget RLS (samme risikoklasse som den tidligere RLS-tomstreng-
bugen) og for å bevare ADR-033 Beslutning A urørt; (2) SAMME
`tournament`-tabell med en ny `format_type`-diskriminator
(`'team'`/`'individual'`), ikke en helt ny toppnivå-entitet —
gjenbruker synlighet/join-kode/status (ADR-018/020) uendret; (3)
flerrunde-støtte fra START via en ny `tournament_round`-tabell
(økt-lignende), sammenlagt resultat summert ved lesing på ekte
deltaker-id (samme prinsipp som dagens lag-leaderboard); (4) rå
brutto slagtall lagres OG et ferdig utregnet poengtall CACHES per
scoringsmetode — samme etablerte mønster som `match.status_text`/
`points_side_a/b` — nye formater (Københavner m.fl.) blir dermed i
hovedsak én ny motorfunksjon + én ny CHECK-verdi, ikke en
skjemaendring.
**Viktig presisering som oppsto underveis, endrer forrige rundes
antakelse:** "flight" i en formell org-turnering er KUN en
tee-tid-gruppering — leaderboardet spenner alltid HELE feltet.
Dette er strukturelt ULIKT den ad hoc "flere flighter i en
frittstående runde"-ideen (der leaderboardet bevisst avgrenses til
det man selv satte opp) — de to holdes derfor bevisst ADSKILT, ikke
forent slik forrige runde antydet. Begge berørte FEATURE_BACKLOG.md-
seksjoner oppdatert med denne presiseringen.
**Fortsatt IKKE avgjort:** de fem konkrete formatenes egne poengregler
(Københavner m.fl., egne mindre design-runder oppå denne strukturen),
og Order of Merit (sesong-sammenlagt, bekreftet som naturlig SISTE
steg). **Ingen migrasjon eller kode skrevet** — ADR-037 er ren
struktur-beslutning; neste steg er et konkret migrasjonsutkast
(nye tabeller + `tournament.format_type`) lagt frem til gjennomgang
før noe kjøres.
- **"Fiks alle kjente små bugs"-runde, BEGGE FIKSET OG SCRATCH-VERIFISERT
(2026-07-26), samme dag som ADR-037:** brukeren ba eksplisitt om å
rydde opp i alle dokumenterte, fortsatt åpne små bugs. Systematisk
gjennomgang av CLAUDE.md/FEATURE_BACKLOG.md (søk på "IKKE fikset"/
"urelatert bug" m.fl.) fant at nesten alt tidligere flagget som
"ikke fikset" faktisk ble fikset i en SENERE runde lenger ned i loggen
(stale seksjonsoverskrifter, ikke reelt åpne bugs) — kun to genuint
fortsatt åpne:
1. **Dashbord-hilsen brukte fullt navn** (nettopp etablert
navneformat-regel, se egen seksjon over) — `me.first_name ??
me.display_name` i stedet for `me.display_name` alene.
`/auth/me` eksponerte allerede `first_name`, ingen backend-endring.
2. **Stroke-modus-scoreinnsending på en bane UTEN registrerte hull
krasjet rått (500 IndexError)** i stedet for en ren
`VALIDATION_FAILED` — `scoring.py` sin `_compute_hole_results`
kalte `allocate_over_played_holes` med en TOM stroke-indeks-liste
når `hole`-tabellen var tom for banen (f.eks. en egendefinert bane
der noen glemte å legge til de 18 hullene). Fikset med en eksplisitt
`len(all_18_si) != 18`-sjekk RETT FØR beregningen — samme mønster
som den allerede kjente/fikset manglende-handicap-indeks-krasjen
fra blind draw-runden (2026-07-18).
**Scratch-verifisert grundig (5/5 sjekker)** for backend-fiksen
(isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
API-container, samme mønster som ellers i prosjektet): en bane UTEN
hull ga korrekt `400 VALIDATION_FAILED` ved scoreinnsending (ikke
500), OG en egen regresjonssjekk bekreftet at en NORMAL bane (alle 18
hull) fortsatt scorer helt uendret (begge sider, full
scorekort-henting via `GET .../scorecard`). `test_isolation.sql`
12/12 uendret — ren Python-logikk-fiks, ingen migrasjon. Ekte
typesjekket produksjonsbuild av frontend kompilerte rent (dashbord-
fiksen). To stale FEATURE_BACKLOG.md-seksjonsoverskrifter rettet
samtidig ("Rapporterte UI-/UX-hull" og selve bug-notatet), siden de
fortsatt sa "IKKE fikset" lenge etter at innholdet faktisk var
fikset.
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
begge containere boot-et rent (`Application startup complete`, Next.js
`Ready`), `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **ADR-036 fase 3 (delvis): søk+legg til ekte medspiller på en
frittstående runde, BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE
ENNÅ RULLET UT:** brukeren rapporterte at "+ Gjest"-skjemaet ikke
søkte etter spillere når man skrev et navn — bekreftet reelt (rent
tekstfelt, `/people/search` var aldri koblet på). Spurte om
omfang før bygging: brukeren ville ha "full tilgang nå" (medspilleren
kan selv registrere score), ikke bare rask utfylling — et bevisst
større valg enn det anbefalte minimum.
**Backend:** `POST /rounds/{id}/participants` tar nå ENTEN `user_id`
(funnet via tiered `/people/search`, samme algoritme som vennesøket)
ELLER `guest_name` (uendret) — kjønn/HCP hentes automatisk fra den
valgte personens egen profil. Ny migrasjon `027_round_participant_
user_unique.sql` (partiell unik indeks). Ny `_get_accessible_round_
or_404` (eier ELLER lenket medspiller) for lesing/hull-scoring/
fullføring — `_get_owned_round_or_404` (strengt eier-only) beholdt
for rediger/slett/legg til/fjern/endre stat_level. `RoundOut` sine
`owner_*`-felt omdøpt til `my_*` og gjort VIEWER-relative (regnes nå
fra den spørrende brukerens egen deltaker-rad).
**Reell latent bug funnet OG fikset i SAMME runde, før den nådde
produksjon:** leaderboard-endepunktet (bygget tidligere samme dag,
se over) ville vist et TOMT navn for enhver lenket medspiller, siden
dets `display_name`-utledning kun sjekket `is_owner`, ikke om
`user_id` var satt i det hele tatt. Fanget under scratch-testing av
DENNE runden, fikset før utrulling av noen av delene.
**Frontend:** "+ Gjest" omdøpt til "+ Medspiller" i `round-detail.tsx`,
nytt søk-som-du-skriver-felt (avatar-initialer, hjemmeklubb) med
"Legg til uten konto"-fallback til det gamle tekstfeltet. Viewer-
relativ "Deg"-visning (ny `/auth/me`-bruk for viewerId) — samme fiks
portert til `round-stats.tsx`/`round-scorecard.tsx`, som hadde
identisk latent bug (ville vist eieren som "Deg" for en medspiller
som så på). Rediger/slett/legg-til/fjern-knappene skjules nå for en
ikke-eier-viewer.
**Scratch-verifisert grundig, 39/39 sjekker i to testløp:** søk-og-
legg-til med auto-utfylt kjønn/HCP, avvist duplikat/selv-tillegg/
ukjent bruker/ufullstendig profil, lenket medspiller kan lese runden
+ registrere score for BÅDE egen OG andres rad (whole-flight-
regelen bekreftet), men nektes å forvalte runden (alle
forvaltningskall 403), lenket medspiller KAN fullføre runden,
`/rounds`-listen viser nå runden for en lenket medspiller med DERES
EGEN fremdrift (ikke eierens), urelatert bruker fortsatt 403/ikke i
listen, leaderboard-navn stemmer for alle tre deltakertyper.
`test_isolation.sql` 12/12 uendret. Ekte typesjekket produksjonsbuild
kompilerte rent.
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon
027 kjørt mot ekte `teecup_db` (unik indeks bekreftet,
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
`/health`/`/dashboard`/`/my-rounds` → 200, `teeoff.no` upåvirket.
- **Sanntid for frittstående runder, BYGGET OG SCRATCH-VERIFISERT
(2026-07-26), samme dag som medspiller-søket over:** brukeren spurte om
spillere ser i sanntid at en annen har registrert en score — svaret var
nei (kun engangs-henting ved lasting), og brukeren ba om at det bygges,
gjenbruk av det etablerte "noe endret seg, hent på nytt"-WebSocket-
mønsteret fra ADR-027 ("Følg live" for turneringer).
`app/realtime.py` utvidet (fortsatt rutefri, se moduldoc) med en andre,
parallell kringkastings-registry for runder (`live_sockets_for_round`/
`broadcast_round_update`, delt `_broadcast()`-hjelpefunksjon for å unngå
duplisert kringkastingslogikk). Nytt `@router.websocket("/ws/rounds/
{round_id}/live")` i `rounds.py` — ALDRI anonym tilgang (ulikt
turnering-live), krever eier ELLER lenket medspiller
(`_get_accessible_round_or_404`, samme sjekk som resten av
medspiller-utvidelsen). Kringkasting lagt inn i `update_hole`,
`complete_round`, `add_participant`, `remove_guest_participant`,
`update_round` og `delete_round` — alt som endrer noe de andre på
skjermen bør få vite om. Ingen ny Caddy-endring nødvendig (`/ws/*` er
allerede en wildcard-rute fra ADR-025).
**Reelt funn under scratch-testing, ikke en bug, men verdt å dokumentere:**
Starlette avviser en WebSocket FØR `.accept()` alltid som en bar HTTP
403 under selve håndtrykket — de tre distinkte lukkekodene (4401/4403/
4404) jeg satte når til `websocket.close()` server-side, men skiller seg
IKKE fra hverandre i klientens håndtrykk-avvisning (alle tre ga HTTP 403
i en ekte WS-klienttest, ikke bare curl). Selve sikkerheten (tilkobling
korrekt avvist i alle tre tilfeller) er upåvirket — samme underliggende
Starlette-oppførsel gjelder trolig også den eksisterende turnering-live-
ruten (ADR-027), bare ikke tidligere testet med en ekte WS-klient på
dette presisjonsnivået.
**Frontend:** `round-detail.tsx` åpner en WebSocket ved montering,
refetcher runden ved signal og henter aktiv spillers hull DIREKTE på
nytt (ingen mellomsteg med tom stat som ville blinket for spilleren som
selv nettopp registrerte et slag) — andre spilleres hull-cache droppes
i stedet, hentes friskt ved neste fanebytte. Bruker en ref for å unngå
at socket-en kobles til/fra ved hvert fanebytte. `round-stats.tsx`/
`round-scorecard.tsx` (rene lesevisninger) fikk en enklere
`refreshKey`-basert variant, samme mønster som `public-live.tsx` fra
ADR-027.
**Scratch-verifisert grundig, 10/10 sjekker, ekte WebSocket-klient (ikke
bare REST):** ekte kringkasting bekreftet begge veier — eierens åpne
socket mottok et signal da medspilleren registrerte et slag via REST,
OG medspillerens åpne socket mottok et signal da eieren fullførte runden
— ikke bare at REST-svarene så riktige ut. Alle tre avvisningstilfellene
(uautentisert, urelatert fremmed, ukjent runde-id) korrekt avvist.
`test_isolation.sql` 12/12 uendret (ingen migrasjon). Ekte typesjekket
produksjonsbuild kompilerte rent.
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket. Verifisert presist at `/ws/rounds/*`-ruten faktisk når
FastAPI: et ekte WS-håndtrykk-forsøk mot en ukjent runde-id over
produksjons-https ga FastAPI sin egen JSON-`{"detail":"Not Found"}`,
ikke Next.js sin HTML-404 — samme verifiseringsmønster som ADR-027s
tournament-live-rute.
- **HCP i medspiller-søk + rediger utslag/HCP per deltaker, BYGGET OG
SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren viste et
skjermbilde av "+ Medspiller"-søket og påpekte at spillerens HCP burde
vises der (kun navn+klubb vistes), og ba samtidig om å kunne endre
utslagssted og HCP for medspillere -- "det er ikke sikkert de spiller fra
samme utslagssted som meg, og det er ikke sikkert HCP er riktig", eksplisitt
presisert som en endring KUN for denne runden/turneringen, ikke spillerens
faktiske profil.
**Reelt hull bekreftet ved kodegjennomgang FØR bygging:** ALLE deltakere
på en runde har i dag brukt SAMME `round.tee_name_snapshot` -- ingen
per-deltaker-tee har noensinne eksistert (rating-tallene har vært per
deltaker siden migrasjon 021, men selve utslags-NAVNET var aldri
eksplisitt per rad).
**Bygget:** `PersonMatch` (`app/routers/friends.py`, delt av vennesøk OG
medspiller-søk) fikk `handicap_index: float | None`. Ny migrasjon
`028_round_participant_tee.sql` -- `round_participant.tee_name_snapshot`,
backfylt fra rundens eksisterende tee for alle eksisterende rader. `_create_
participant` skriver nå tee-navnet eksplisitt; `ParticipantCreate` (add_
participant) fikk et valgfritt `tee_name` som faller tilbake til rundens
tee hvis utelatt. Ny `GET /rounds/{id}/tee-options` (gjenbruker
`_ResolvedCourse` sin rating-dict via en ny `tee_options()`-metode) --
brukt av "rediger spiller"-panelet til å liste banens faktiske utslag.
`ParticipantUpdate` skrevet om til vanlig `exclude_unset`-PATCH-semantikk
(var tidligere kun `stat_level`, alltid påkrevd) -- nye valgfrie
`tee_name`/`handicap_index`-felt trigger en full re-beregning av
rating-snapshottene + `course_handicap_snapshot` for AKKURAT den
deltakeren, uten å røre spillerens egen `app_user.handicap_index`.
Eksplisitt `null` på `handicap_index` fjerner HCP-sporing for denne
deltakeren i denne runden (samme mønster som profil-PATCH). **Bevisst
avvist etter at runden er fullført** (409 `ALREADY_COMPLETED`, samme
presedens som `RoundUpdate` sitt bane-bytte -- differensialen er da
allerede beregnet fra det gamle grunnlaget); `stat_level` alene er
fortsatt tillatt uansett fullført-status. `RoundUpdate` sitt eksisterende
bane-bytte (ADR-033-oppfølging 2026-07-24) nullstiller nå eksplisitt ALLE
deltakeres `tee_name_snapshot` til rundens nye utslag (et bane-bytte gjør
individuelle tee-valg fra den gamle banen meningsløse).
**Frontend (`round-detail.tsx`):** søkeresultatene i "+ Medspiller" viser
nå HCP ved siden av hjemmeklubb. Ny "Utslag: X · HCP: Y"-rad under
statistikknivå-velgeren for aktiv spiller, med en "Rediger for denne
runden"-lenke (kun eier, kun før fullført) som åpner en ny
`EditParticipantPanel` -- utslagssted som en `` fylt fra det nye
tee-options-endepunktet (filtrert på spillerens kjønn), HCP som fritekst,
forklarende "endrer ikke profilen"-tekst. Gjelder likt for eierens egen
rad som for medspillere (ingen spesialtilfelle).
**Scratch-verifisert, 27/27 sjekker** (isolert `teecup_app_scratch`-
rolle + isolert scratch-MinIO + engangs API-container): HCP i søk, legg
til medspiller med eksplisitt AVVIKENDE utslag fra eieren, gjest uten
eksplisitt tee faller korrekt tilbake til rundens tee, utslag uten rating
for kjønn avvist (400) både ved tilføyelse og redigering, tee-options
lister riktige utslag, rediger tee+HCP for medspiller lykkes og
course_handicap regnes om, **medspillerens egen profil-HCP forblir
UENDRET** (den kritiske sjekken -- bekrefter runde-scoping), delvis PATCH
(kun stat_level) lar tee/HCP stå urørt, eksplisitt `null` fjerner HCP
round-scoped, lenket medspiller (ikke eier) nektes å redigere deltakere
(403, forvaltning fortsatt eier-only), tom PATCH avvist (400), redigering
blokkert etter fullføring (409) mens stat_level fortsatt tillates. Full
regresjonskjøring av forrige rundes 35-punkts medspiller-søk-testsuite
(`test_round_coplayer.py`) mot samme friske scratch-database, alle 35
fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket
produksjonsbuild kompilerte rent.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
migrasjon 028 kjørt mot ekte `teecup_db` (kolonne bekreftet `NOT NULL`,
2 eksisterende rader backfylt korrekt, `test_isolation.sql` fortsatt
12/12), deretter `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent (`Application startup
complete`, Next.js `Ready`), `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket.
- **V0-prompt for rundeleaderboardet rettet (2026-07-26), FØR den ble
kjørt i v0.app:** samme runde som over, brukeren ba eksplisitt om å få
leaderboardet for frittstående runder på plass. Backend
(`GET /rounds/{id}/leaderboard`) var allerede live siden tidligere samme
dag; V0-prompten (skrevet samme dag, se FEATURE_BACKLOG.md) hadde
derimot en utdatert antakelse -- den ba om et "Deg"-merke basert på at
kun eieren noensinne ser sin egen runde, som ikke lenger stemmer etter
ADR-036 fase 3 (medspillere kan nå også se runden). Rettet til to
uavhengige merker: "Eier" (fra API-ets `is_owner`) og "Deg" (fra en
`participant_id`-prop komponenten mottar utenfra, ikke fra selve
leaderboard-dataen). Prompten er ikke sendt til v0.app ennå.
- **Mottatte slag i hull-headeren + gjeste-e-post/kjønn/navn redigerbart,
BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren
viste et skjermbilde av det nettopp rullede "Rediger for denne runden"-
tillegget og ba om to ting: (1) hvor mange slag aktiv spiller MOTTAR på
det aktive hullet vist i selve hull-headeren (eksempel "Hull 7 - Par 4 -
Hcp 5 - -1"), og (2) for en midlertidig spiller (gjest) skal Navn/Kjønn/
E-post også være redigerbart, ikke bare utslag/HCP. Ba samtidig om et
V0-prompt for å designe om selve spillerlisten (utslag/HCP/rediger inn i
selve spillerknappen, listet vertikalt i stedet for dagens horisontale
scroll-rad).
**Punkt 1 bygget direkte** (triviell, ren frontend, ingen backend-endring
-- `strokes_received` var allerede hentet fra `GET .../holes` fra før,
bare aldri vist FØR scoring): `round-detail.tsx` sin hull-header viser nå
`· −N` (ekte minustegn, golfvis fortegn) når aktiv spiller mottar minst
ett slag på hullet, ellers uendret.
**Punkt 2 krevde ny backend:** ny migrasjon `029_round_participant_
guest_email.sql` (`round_participant.guest_email`, nullable). `Participant
Create` fikk et valgfritt `guest_email: EmailStr | None` (avvist sammen
med `user_id`). `ParticipantUpdate` fikk `guest_name`/`gender`/
`guest_email` -- KUN gyldig for en gjest (`user_id IS NULL`), avvist
(400) på en lenket deltaker. `gender`-endring inngår nå i samme "rating_
changed"-bunt som utslag/HCP (påvirker gyldig rating), avvist etter
fullføring (409, samme presedens); `guest_name` alene har ingen rating-
implikasjon og forblir redigerbar selv etter fullføring (ren metadata).
**Frontend for punkt 2 IKKE bygget ennå** -- overlatt til V0-prompten
(se FEATURE_BACKLOG.md), siden brukeren eksplisitt ba om det og den
eksisterende hånd-bygde `EditParticipantPanel` uansett skal erstattes av
den nye vertikale spillerlisten.
**Scratch-verifisert, 22/22 nye sjekker** (gjest med e-post ved
opprettelse, avvist sammen med user_id, navn+kjønn+e-post-redigering
lykkes og rating regnes om for nytt kjønn, eksplisitt null fjerner
e-post, kjønnsendring til urepresentert rating avvist med full rollback,
alle tre gjeste-feltene avvist på en LENKET deltaker mens utslag/HCP
fortsatt fungerer uendret der, kjønn/utslag/HCP-endring blokkert etter
fullføring mens navneendring fortsatt tillates). Full regresjonskjøring
av samme dags 27+35-punkts testsuiter mot samme friske scratch-database,
alle 62 fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket
produksjonsbuild kompilerte rent (inkl. punkt 1).
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
migrasjon 029 kjørt mot ekte `teecup_db` (kolonne bekreftet,
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **Spillerliste-redesign HÅNDKODET (2026-07-26), IKKE ENNÅ RULLET UT:**
brukeren gikk tom for V0-credits rett etter at prompten over ble sendt.
Spurt eksplisitt (AskUserQuestion): bygg direkte, eller vent på fornyede
credits — svar: bygg direkte. Bygget nøyaktig etter samme prompt/data-
kontrakt som allerede var skrevet (se FEATURE_BACKLOG.md for full
detalj), i samme Tailwind/shadcn-stil som resten av `round-detail.tsx`.
Ny `PlayerList` (erstatter `PlayerTabs`) — vertikal liste av spillerkort,
hvert kort en stor "velg som aktiv spiller"-knapp + separate Rediger-/
fjern-knapper (unngår nestede interaktive elementer), "Deg"/"Eier" som
`Badge`-komponenter. `EditParticipantPanel` utvidet med statistikknivå
(den frittstående `StatLevelPicker` er nå død kode, fjernet) og — kun
for gjester — Navn/Kjønn/E-post, med reaktivt utslagsfilter ved
kjønnsendring. "+ Medspiller"-flyten flyttet inn i selve `PlayerList`.
**Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, pluss
en engangs `next dev`-container mot scratch-backend (ekte runde med en
gjest med avvikende utslag/kjønn/e-post) — siden ga 200, ingen Next.js-
feilside. **Ærlig begrensning:** siden er en klient-komponent (data
hentes etter hydrering), så server-HTML-en viser kun last-skjelettet —
INGEN ekte nettleser-interaksjonstest utført (intet nettleserverktøy
tilgjengelig).
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen ny migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket. **Bruker bør selv klikke gjennom flyten**
(spesielt gjeste-kjønnsendringens reaktive utslagsfilter og "velg vs.
rediger"-trykkflatene) før full tillit.
- **Tildelte slag + jevn korthøyde + rundeleaderboard, HÅNDKODET, IKKE ENNÅ
RULLET UT (2026-07-26):** to oppfølgingspunkter samme dag, full detalj i
FEATURE_BACKLOG.md. (1) Spillerkortet manglet "tildelte slag" (course
handicap for runden) og hadde variabel høyde — rettet: `Player` fikk
`courseHandicap`, kortet fikk fast `min-h-[68px]` med begge tekstlinjer
trunkert til én linje (ikke wrap), alle kort like høye uansett innhold.
(2) Brukeren spurte om jeg "med designerbrillene på" trodde jeg kunne få
rundeleaderboardet til å se like profesjonelt ut som V0 — svarte ja
(gjenbruk av etablerte mønstre, ikke fri utforskning) og bygget det: ny
`components/round-leaderboard.tsx` + rute `/my-rounds/[id]/leaderboard`,
gjenbruker `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`
(som `ToParMark`), samme WS-sanntid-mønster som `round-stats.tsx`, delt
plassering ("T-N"), brutto/netto-veksling, mini-variant på rundens egen
side. Rangeringslogikken VERIFISERT UAVHENGIG i et frittstående
Node-script (19/19, inkl. tie-håndtering og uspilte spillere), pluss en
fersk scratch-runde med ekte API-kall som bekreftet leaderboard-JSON-en
stemte. Ekte typesjekket produksjonsbuild kompilerte rent. Samme ærlige
begrensning som over: ingen ekte nettleser-interaksjonstest.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Hullscorer i leaderboardet + lenke fra rundelisten, BYGGET, IKKE ENNÅ
RULLET UT (2026-07-26):** brukeren presiserte rett etter forrige rulling
tre krav: leaderboardet skal ha en egen visning (allerede tilfellet, se
over), med lenke fra scoreføringssiden (allerede der, `RoundLeaderboardMini`)
OG fra rundelisten (`/my-rounds`, manglet), og skal vise hullscorer
(manglet helt -- kun aggregerte tall fantes).
**Backend:** `LeaderboardEntryOut` (`GET /rounds/{id}/leaderboard`) fikk
et nytt `holes: list[LeaderboardHoleOut]`-felt (hole_number/par/
stroke_index/played/score) -- data var ALLEREDE hentet per deltaker for
å beregne total_score/net_score_to_par, bare aldri eksponert rått før nå.
Ren tilføyelse, ingen migrasjon.
**Frontend:** `round-leaderboard.tsx` sine rader er nå klikkbare
(utvider/kollapser, default kollapset -- samme "trekkspill starter
lukket"-konvensjon som `round-stats.tsx`) og viser en horisontal strip
med per-hull-merker (`HoleMark`/`HoleStrip`, egen lokal variant av
`ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`, tilpasset en
kompakt flerspiller-liste i stedet for én tabell). `round-card.tsx`
(brukt av BÅDE `/my-rounds`-listen og dashbordets "Kommende runder") fikk
en ny "Se leaderboard"-lenke for runder med mer enn én deltaker --
krevde å gjøre om kortets ytre element fra selve lenken til en ``
med lenken som ETT av to barn (unngår nestet `
`, samme feilklasse som
tidligere nestet-form-/knapp-feller i prosjektet).
**Verifisert:** backend-endringen bekreftet mot en fersk scratch-runde
(ekte API-kall, `holes`-arrayet inneholder riktige 18 rader per deltaker
inkl. korrekt `played`/`score` for både spilte og uspilte hull), full
regresjon av forrige rundes 35-punkts co-player-testsuite fortsatt grønn,
`test_isolation.sql` 12/12 (uendret, ingen migrasjon). Ekte typesjekket
produksjonsbuild kompilerte rent (ingen nye ruter, kun endrede
komponenter). Samme engangs `next dev`-container-sjekk som tidligere --
`/my-rounds`, `/my-rounds/[id]` og `/my-rounds/[id]/leaderboard` ga alle
200, ingen Next.js-feilside.
**Samme ærlige begrensning som tidligere håndkodede runder:** ingen ekte
nettleser-interaksjonstest (klikk for å utvide en rad, se hull-stripen,
se "Se leaderboard"-lenken i rundelisten) er utført.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Leaderboard-oppfølging: poeng-modus + minimalistiske rader, BYGGET, IKKE
ENNÅ RULLET UT (2026-07-26):** brukeren påpekte at brutto/netto ble
presentert forskjellig (bryteren byttet BÅDE hvilket tall som vises OG
hele rangeringsrekkefølgen -- forvirrende hopp), og spurte om et eget
stableford-alternativ burde vært med. Foreslo og fikk bekreftet: gi alle
tre modiene (nå: Brutto/Netto/**Poeng**) IDENTISK radform (rangering,
navn, ETT tall, ferdig) i stedet for å prøve å vise flere tall samtidig.
Samtidig ba brukeren om å fjerne alt fra den kollapsede raden utover
navn+tall (Deg/Eier/pokal/hull-spilt-tekst) og fjerne selve
"kort"-følelsen -- radene skal flyte sømløst sammen, kun ekspandere ved
klikk.
**Backend:** `LeaderboardHoleOut` fikk `strokes_received` (samme
allerede-beregnede allokering som `net_score_to_par`, nå eksponert per
hull også). `LeaderboardEntryOut` fikk `total_points` (stableford-total,
samme formel/betingelse som `net_score_to_par` -- null uten HCP). Ingen
migrasjon.
**Frontend (`round-leaderboard.tsx`):** `Mode` utvidet til tre verdier,
poeng rangeres SYNKENDE (motsatt av til-par). Ny `PointsMark`/`ValueMark`
(poeng har ingen retning, kun fylt/uthevet for lederen). Listen er nå ÉN
sammenhengende `` (`divide-y`, kun ytterkanten avrundet/rammet) i
stedet for separate kort med mellomrom mellom hver rad -- ingen
per-rad-bakgrunn utenom en svak tone på en UTVIDET rad. Kollapset rad:
kun rangering+navn+tall+pil. Deg/Eier/pokal/fremdrift FLYTTET (ikke
fjernet) inn i den utvidbare seksjonen sammen med hull-stripen.
**Verifisert:** rangeringslogikken for poeng-modus (synkende sortering,
delt plassering, uten-HCP sortert nederst) UAVHENGIG testet i Node
(10/10). Selve stableford-formelen verifisert mot en fersk scratch-runde
med HÅNDREGNET forventet resultat (course handicap 9 over 18 hull →
slag KUN på de 9 laveste stroke-index-hullene, ikke jevnt fordelt --
fanget en feilaktig antakelse i testens FØRSTE versjon, rettet før
den ble rapportert som bestått) -- 11/11, inkl. kryssjekk av
`total_points`/`net_score_to_par` mot uavhengig rekalkulering fra de
samme rå hull-dataene API-et returnerte. Full regresjon (35-punkts
co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12. Ekte
typesjekket produksjonsbuild kompilerte rent. Samme engangs `next
dev`-container-sjekk som tidligere -- alle tre rutene ga 200.
**Samme ærlige begrensning:** ingen ekte nettleser-interaksjonstest av
det faktiske "sømløse rader"-uttrykket eller poeng-modusen.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Hullscorer i leaderboardet: netto/poeng under brutto, BYGGET, IKKE ENNÅ
RULLET UT (2026-07-26):** brukeren viste to referansebilder fra en
konkurrentapp (Golf GameBook) sin leaderboard-visning -- brutto fremhevet
(fylt/farget merke) på én rad, netto/stableford i vanlig tekst RETT under,
eksplisitt presisert at det skal se likt STRUKTURELT ut men IKKE være en
kopi visuelt. Ren frontend-endring, ingen backend-endring (all data --
`strokes_received` per hull -- var allerede lagt til forrige runde samme
dag).
**`HoleMark`** (`round-leaderboard.tsx`) fikk en tredje, umerket tekstlinje
RETT under det fremhevede brutto-merket, som viser netto eller
stableford-poeng for AKKURAT det hullet -- kun i netto-/poeng-modus (ingen
ny linje i brutto-modus, ingenting nytt å vise der). Nye lokale
`netForHole()`/`pointsForHole()`-hjelpefunksjoner (samme formel som
backend sin `total_points`, men per hull -- bruker `strokes_received`
direkte fra API-et, regner ALDRI ut egen slagfordeling). `HoleStrip` fikk
en liten "Slag · Netto"/"Slag · Poeng"-bildetekst over stripen når en
sekundærrad faktisk vises.
**Bevisst IKKE en kopi:** beholder TeeCups egne farger/former (sirkel/
firkant, grønn/oransje) fra `ScoreMark`-språket som allerede var etablert
-- endret ikke fargevalg for å etterligne referansebildets blåtoner, kun
gjenskapte det STRUKTURELLE prinsippet (fremhevet brutto øverst, rolig
sekundærtall under).
**Verifisert:** netto/poeng-per-hull-formelen testet UAVHENGIG i Node
(9/9, inkl. et gulv-på-0-tilfelle og manglende-HCP/uspilt-hull), samme
tall som det håndregnede scratch-scenarioet fra forrige runde samme dag
(hull med slag: netto 3/poeng 3, hull uten slag: netto 5/poeng 1). Ekte
typesjekket produksjonsbuild kompilerte rent. Samme engangs `next
dev`-container-sjekk som tidligere -- leaderboard-ruten ga 200.
**Samme ærlige begrensning som resten av de håndkodede rundene:** ingen
ekte nettleser-interaksjonstest av selve det visuelle uttrykket.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Scoringsflyt: samlebånd-fremdrift + golf-term-taltastatur, BYGGET, IKKE
ENNÅ RULLET UT (2026-07-26), inspirert av en konkurrentapp (Golf
GameBook):** brukeren delte en skjermopptaksvideo av GameBooks
score-registrering og spurte om jeg forstod HVORFOR den flyten fungerer
bedre enn TeeCups, og om jeg kunne bygge det selv (ingen V0-credits
igjen). Video analysert bilde for bilde (ffmpeg i en engangs Docker-
container, samme mønster som en tidligere videoanalyse i prosjektet) --
identifiserte presist samlebånd-mønsteret: taltastatur med kontekstuelle
golf-termer per tall (Eagle/Birdie/Par/Bogey ut fra hullets par, ikke
bare "par"-knappen merket), og at fullført registrering for én spiller
automatisk åpner NESTE spillers registrering for samme hull -- uten å
måtte navigere manuelt tilbake til en spillerliste.
**Bevisst IKKE en full skjermovertakende steg-for-steg-modal** (GameBooks
egen løsning) -- vurdert som unødvendig risikofylt å bygge korrekt uten
visuell testing. I stedet: samme etablerte inline-side beholdt, men to
konkrete forbedringer lagt til:
1. `NumberPicker` sin Slag-instans fikk en ny `showGolfTerms`-modus --
HVERT synlig tall viser nå Albatross/Eagle/Birdie/Par/Bogey/Dobbel
bogey relativt til hullets par (ikke bare selve par-knappen som før).
Ny lokal `golfTermForScore()`-hjelpefunksjon.
2. Ny `isEntryComplete()`-sjekk (krever kun det `stat_level` faktisk gjør
obligatorisk -- slag alene, eller slag+putter; "full"-nivåets ekstra
detaljer forblir valgfrie og blokkerer ALDRI fremdrift) + ny
`advanceToNextPlayerOrHole()`. Bunnknappraden "Forrige/Neste hull"
endret til "Forrige hull" (uendret) + en kontekstsensitiv primærknapp
som enten viser "Neste: {navn på neste spiller}" (bytter aktiv
spiller på SAMME hull) eller "Neste hull" (er aktiv spiller den
siste, går videre til neste hull OG starter på spiller 1 igjen) --
disabled med forklarende hjelpetekst til de påkrevde feltene er fylt.
**Verifisert:** golf-term-tabellen og fullført-sjekken UAVHENGIG testet
i Node (matcher videoens egne eksempler nøyaktig, f.eks. par 4 + slag
6 = "Dobbel bogey"), samt selve samlebånds-syklusen simulert for 3
spillere over flere hull-grenser OG for en solo-runde (ingen spillerbytte,
kun hull-fremgang) -- 21/21 sjekker. Full regresjon (35-punkts
co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ingen
backend-endring denne runden). Ekte typesjekket produksjonsbuild
kompilerte rent, samme engangs `next dev`-container-sjekk som tidligere
ga 200.
**Samme ærlige begrensning som resten av de håndkodede rundene:** ingen
ekte nettleser-interaksjonstest av selve fremdrifts-følelsen (kun logikk
+ server-render bekreftet).
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Scoringsflyt v2: full skjermovertagende veiviser, BYGGET, IKKE ENNÅ
RULLET UT (2026-07-26) -- ERSTATTER forrige rundes forsøk.** Brukeren
testet forrige rundes "auto-fremdrift-knapp"-versjon live og var tydelig:
"ingen forbedring i det hele tatt", "visuelt like overveldende og
rotete" -- den inline-baserte tilnærmingen var IKKE nok, en ekte
skjermovertagende veiviser (som opprinnelig vurdert og lagt til side pga.
risiko uten visuell testing) var det som faktisk kreves. Bygget nå for
ekte, pluss et eksplisitt nytt krav: akkumulert score-så-langt for RUNDEN
synlig for HVER spiller samtidig (ikke bare aktiv), matchende
konkurrentappens vedvarende "E"/"+1"-visning ved siden av hvert navn.
**Datalasting endret** (nødvendig for punktet over): laster nå hull for
ALLE deltakere med det samme rundén lastes (ikke lenger lat lasting kun
for aktiv spiller), og sanntid-signalet (WS) henter nå alles hull på
nytt, ikke bare énes.
**Ny `ScoringWizard`-komponent** (fullskjerm, `fixed inset-0 z-50`, egen
stack utenfor ``): tre steg maks, drevet av `stat_level` --
"strokes_only" (kun Slag), "strokes_and_putts" (+ Putter/avstand første
putt), "full" (+ ett samlet detalj-steg: kølle/utslag/innspill/chip/
bunker/straffeslag/anywayslag). Bevisst FÆRRE, grovere steg enn
konkurrentens egne 5-6 skjermer (risikoreduksjon uten visuell testing,
og TeeCups stat_level-modell gjør en så fin oppdeling mindre naturlig).
Alle spillerne vises som en fast, ikke-trykkbar kontekst-rad øverst i
veiviseren (aktiv fremhevet) -- speiler konkurrentappens "mist aldri
oversikten"-prinsipp. "Forrige"/"Neste" beveger seg gjennom stegene;
siste steg for siste spiller blir "Ferdig" (lukker + går til neste hull,
starter på spiller 1 igjen), ellers "Neste: {navn}" (bytter spiller i
SAMME veiviser, nullstiller til steg 1).
**Hovedsiden forenklet radikalt:** den gamle Slag/Putter/"flere
detaljer"-inline-blokken er FJERNET -- erstattet med én kompakt liste,
ett kort per spiller: navn, "HCP X · {til-par så langt} ({N} hull)", og
en stor rund knapp som viser gjeldende hulls slagtall (eller "–") og
åpner veiviseren ved trykk. `NumberPicker`s `showGolfTerms`-funksjon fra
forrige runde gjenbrukes uendret inni veiviserens Slag-steg (det arbeidet
var ikke bortkastet).
**Verifisert:** stegmaskinen (steg-antall per stat_level, fremover/
bakover-navigasjon, disabled-gating per steg, "avbryt på steg 1 lukker",
"siste steg for siste spiller fullfører") og akkumulert-score-
beregningen UAVHENGIG simulert i Node (16/16). Et ekte API-rundtur-
script som sender NØYAKTIG samme felt-kombinasjon som veiviseren ville
sendt (fullt detalj-steg for en "full"-spiller, kun slag for en
"strokes_only"-gjest) bekreftet begge lagres korrekt. Full regresjon
(35-punkts co-player-testsuite) fortsatt grønn, `test_isolation.sql`
12/12 (ingen migrasjon). Ekte typesjekket produksjonsbuild kompilerte
rent, samme engangs `next dev`-container-sjekk ga 200.
**Samme ærlige begrensning som alle håndkodede runder denne uken:** ingen
ekte nettleser-interaksjonstest -- gitt at FORRIGE runde ble avvist
nettopp fordi den så gal ut i praksis til tross for at logikken var
korrekt, er dette IKKE en ubetydelig forbehold denne gangen. Bruker bør
teste grundig før tillit.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Scoringsflyt v3: administrasjon og scoring adskilt i to faner, BYGGET,
IKKE ENNÅ RULLET UT (2026-07-26) -- direkte svar på at bruker fortsatt
fant siden "bråkete og lite intuitiv" etter v2.** Brukeren viste et NYTT
sidestilt skjermbilde-par (samme mønster som første gang) og påpekte at
TeeCup fortsatt hadde mye synlig FØR selve scoringslisten (leaderboard-
forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten med Rediger-
ikoner, "+Medspiller") -- nøyaktig det jeg selv identifiserte som GameBooks
kjerneprinsipp i den aller første analysen ("administrasjon og scoring er
ADSKILTE tabs"), men aldri fullt ut gjennomførte i v2 (kun `ScoreSoFar`
ble flyttet forrige runde, resten av det administrative innholdet ble
stående igjen øverst).
**Fikset denne gangen for ekte:** ny `pageTab`-state (`"score" | "manage"`,
default `"score"`), en enkel fanevelger rett under feilmeldingen. "Score"
(default) inneholder nå KUN: fullført-banner (hvis relevant), hull-
navigasjon, og selve hull-panelet (header + scoringslisten fra v2 +
Forrige/Neste hull) -- ingenting annet. "Spillere og runde" samler
leaderboard-forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten
(`PlayerList`), OG `ScoreSoFar` (flyttet HIT fra forrige rundes
"under scoringslisten"-plassering, siden den er detaljert stats-innsyn,
ikke selve registreringsoppgaven).
**Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, samme
engangs `next dev`-container-sjekk ga 200, full regresjon (35-punkts
co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ren
frontend-omrokkering, ingen migrasjon).
**Samme ærlige begrensning som v1/v2:** ingen ekte nettleser-
interaksjonstest av selve fane-følelsen.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_frontend`
(gjenskapte også `teecup_api` som vanlig bivirkning). Begge containere
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **`DESIGN_SYSTEM.md` opprettet (2026-07-27):** brukeren ba om en ny fil
som viser designsystemet arbeidet tar utgangspunkt i. Skrevet fra bunnen,
DESKRIPTIVT (hva som ER i koden, ikke et mål) — grunnet direkte i
`globals.css`/`components/ui/*.tsx` og etablerte tvers-fil-mønstre:
fargetoken (inkl. OKLCH-opprinnelsen til `primary`/`brand-orange` fra
ADR-009/016), typografi, avstand/hjørner/lag (inkl. "én sammenhengende
liste med `divide-y`, ikke separate kort"-regelen fra 2026-07-26s
leaderboard-fiks), lokale komponentmønstre (NumberPicker/Stepper/
ChoiceRow/DirectionCross — bevisst per-fil, ikke delte importer),
golfscore-språket (`ScoreMark`/`ToParMark`/`PointsMark`/`HoleMark`),
ikonografi, tilbakemelding/tilstander, og navneformat (pekt til CLAUDE.md).
- **`teecup-scorekort-og-entry-spec.md` lest og evaluert, delvis BYGGET SOM
COMPLIANCE-PASS SAMME DAG (2026-07-27):** brukeren lastet opp et
PRESKRIPTIVT motstykke til DESIGN_SYSTEM.md (5-app-sammenligning: Golf
GameBook/Golf Pad/Golfshot/Hole 19/18Birdies) og spurte om det ga mening
og kunne forbedre TeeCup. Vurdert som reelt verdifullt — sylskarpeste nye
innsikt: "grønt-på-grønt"-diagnosen (§0.2) forklarer noe av "fortsatt
rotete"-følelsen fra v1-v3-rundene bedre enn tetthet alene: `primary`
(grønn) var brukt om hverandre for BÅDE "aktiv tilstand" og "generisk
fylt/positiv", uten at begge betydde det samme sted. Foreslo (og
brukeren bekreftet) å fikse spec-dokumentets §3 "Compliance-pass" FØR den
større strukturelle §1-omleggingen (scorekort som grid) vurderes som egen,
senere runde.
**To av sjekklistens fem punkter var KONKRETE, VERIFISERBARE bugs, bekreftet
direkte i koden (ikke antatt fra spec-teksten alene) FØR de ble fikset:**
1. **"Deg Deg"-duplikat:** `playerLabel()` (`round-detail.tsx`) erstattet
tidligere selve navnet med "Deg" for viewer-relativ egen rad, OG en
separat `Deg ` sto ved siden av samme sted (spillerkort,
scoringslisten) — bekreftet duplikat, matchet et tidligere skjermbilde
brukeren delte. **Fikset:** `playerLabel()` returnerer nå alltid det
faktiske `display_name` (aldri "Deg") — Badge-en er nå ENESTE
selv-indikator. Dette retter samtidig et videre, ikke tidligere flagget
avvik: spec-dokumentet krever eksplisitt "roster-kontekst → fullt navn"
for BÅDE scorekort-gridet og score-entry-headeren — veiviserens header/
kontekst-rad/"Neste: {navn}"/fullført-banneret viste tidligere "Deg" i
stedet for et fullt navn der også, uten noen badge til å disambiguere
(reelt forvirrende på en delt telefon som sendes rundt en flight).
Det nå overflødige `rawName`-feltet (var identisk med `name` etter
fiksen) fjernet, tre kallsteder oppdatert.
2. **Score-knappen fulgte ikke `§Golfscore-språket`:** den store runde
knappen i den kompakte scoringslisten (`round-detail.tsx`, bygget i
v2-runden) var `rounded-full`+grønn UANSETT om resultatet var under,
over eller på par — bogey og eagle så identiske ut. **Fikset:** ny
`scoreMarkClasses(diff)`-hjelpefunksjon som gjenbruker EKSAKT samme
`primary`/`brand-orange`-fargespråk og fylt-vs-border+10%-tint-omfangs-
regel som `ScoreMark` i `round-scorecard.tsx` (bekreftet ved å lese
`ScoreMark` sin kildekode direkte, ikke gjettet) — sirkel under par,
nøytral sirkel på par, `rounded-2xl` (bevisst mildere enn scorekortets
`rounded-[4px]`, for å matche denne skjermens øvrige 56px-trykkflate-
avrunding) over par, fylt ved 2+ slag fra par.
**Resten av sjekklisten (44px-trykkgulv, `tabular-nums`) auditert
systematisk mot AKKURAT denne filen** (samme fil brukerens skjermbilder
viste) — 2 manglende `tabular-nums` (HCP/tildelte slag i spillerkortet)
og 11 knapper/lenker under 44px (fane-bryteren `min-h-10`→`min-h-11`,
`EditRoundPanel`s lukk-ikon `size-8`→`size-11`, feilside-tilbakelenken,
og åtte knapper i bane-bytte-/legg-til-medspiller-skjemaene) rettet.
**Bevisst UTENFOR omfang denne runden:** spec-dokumentets §1 (scorekort
som fullt grid, celle åpner veiviseren) — en STØRRE strukturell endring
som fortjener et eget, bevisst ja fra brukeren, ikke bygget stille inn i
en "fiks kjente bugs"-runde.
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som
deployes) kompilerte rent, alle 24 ruter listet. **Samme ærlige
begrensning som ALLE håndkodede runder denne uken:** ingen ekte
nettleser-interaksjonstest av det faktiske visuelle resultatet (kun kode-
lesing + build-verifisering + bevisst gjenbruk av en allerede lest,
eksisterende komponents fargespråk for å holde risikoen lav).
**Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket.
- **Spec-dokumentets §1 (scorekort som fullt grid) BYGGET OG LIVE
(2026-07-27), samme dag rett etter compliance-passet:** brukeren
bekreftet eksplisitt "JA" på at grid-redesignet er en egen, bevisst
runde, egen informasjonsarkitektur enn ett-hull-om-gangen-listen
bygget dagen før. `round-detail.tsx` sin "Score"-fane fikk hull-strip
+ ett-hull-panel ERSTATTET av en ny `ScorecardGrid`: spillere som rader
(sticky venstre navnekolonne, navn+HCP, samme tap-target åpner
veiviseren for gjeldende hull), hull som horisontalt scrollbare
kolonner, `Hcp`/`Par`-referanserader over spillerradene (samme
konvensjon som den allerede shippede `round-scorecard.tsx`), sticky
`Ut`/`Inn`/`Sum` til høyre (Ut/Inn gruppert på FYSISK hullnummer 1-9/
10-18, uavhengig av øktens starthull — riktig konvensjon uansett
spillerekkefølge; kun vist for 18-hulls runder, 9-hulls runder får
én samlet Sum). Ny `ScorecardCell` gjenbruker EKSAKT samme klassifisering
og Tailwind-klasser som `ScoreMark`/`classify` i `round-scorecard.tsx`
(lest direkte, ikke gjettet) — ulikt compliance-passets softere
`rounded-2xl`-knapp-variant (fortsatt riktig der, egen visuell kontekst),
siden dette er en LITEN tabellcelle som spec eksplisitt ber om å følge
språket "UBRYTELIG".
**Interaksjon:** tapp en score-celle ELLER spillerens navnecelle ELLER
en hull-kolonneoverskrift åpner `ScoringWizard` (uendret komponent fra
v2-runden) for akkurat den (spiller, hull)-kombinasjonen — kolonne-
overskrift alene (uten å tappe en celle) setter kun "gjeldende hull"
uten å åpne veiviseren, samme jobb som den fjernede `HoleNav` gjorde.
Beholdt en kompakt "Forrige/Neste hull"-knapperad under gridet for
rask sekvensiell registrering uten bred scrolling.
**Dødt kode fjernet i samme runde:** `HoleNav`, `holeIsPlayed`,
GIR-merket/`showGir` (ga ikke lenger mening i en multi-hull-visning),
og OGSÅ compliance-passets `scoreMarkClasses`-hjelpefunksjon (var kun
brukt av den nå fjernede ett-hull-listens store runde knapp).
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
som deployes) kompilerte rent, alle 24 ruter listet. I TILLEGG en
engangs full runner-image bygget og kjørt i en isolert container (ikke
bare `--target builder`) — `/my-rounds/[id]` for en ukjent runde-id
ga 200, ingen React-feilgrense/krasj-markup i responsen. **Samme
ærlige begrensning som ALT håndkodet arbeid denne uken, men STØRRE
konsekvens denne gangen siden dette er en vesentlig strukturell endring
(ny informasjonsarkitektur), ikke en liten fiks:** ingen ekte
nettleser-interaksjonstest (scrolling, tapping av celler/hull-
overskrifter/navnerad, faktisk visuelt resultat av sticky-kolonnene)
er utført — flagget eksplisitt til bruker FØR utrulling, bruker bør
selv klikke seg grundig gjennom før full tillit.
**Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200,
`teeoff.no` upåvirket.
- **Reell produksjonsbug funnet OG fikset SAMME DAG via ekte nettleser-
testing (2026-07-27) — FØRSTE gang denne økten en Chrome DevTools MCP
har vært tilgjengelig.** Brukeren ba meg selv åpne gridet
(`localhost:3000/my-rounds/{id}`), logget meg inn (passord feilet først
— kontoen mangler passord/miljøet pekte annerledes; løst med en ekte
magic-link brukeren limte inn), og jeg tok et ekte skjermbilde av det
nettopp bygde scorekort-gridet på en mobil viewport (390×844) mot ekte
produksjonsdata (runden `c5db2e31-...`, 2 spillere, 4/18 hull spilt).
**Fant umiddelbart en alvorlig, reell rendering-bug** som ALDRI ble
fanget av typesjekking/container-boot-verifisering: `position: sticky`
på ``/` ` inni en `` med `border-collapse` rendret
fullstendig ødelagt i Chrome — de sticky `Ut`/`Inn`/`Sum`-kolonnene
overlappet/utvisket hull-kolonnene bak seg (synlige sammenblandede
siffer, f.eks. "436"/"472" der Par-radens tall lå oppå hverandre).
Nøyaktig den typen feil den gjentatte "ingen ekte nettleser-test
utført"-forbeholdet advarte mot hele uken.
**Første fiks-forsøk (kun `border-collapse` → `border-separate
border-spacing-0`) løste IKKE problemet** — re-skjermbilde etter
redeploy viste samme overlapp. **Rot-årsaken var strukturell, ikke
syntaktisk:** å kombinere en sticky VENSTRE-kolonne MED sticky HØYRE-
kolonner i en tabell som er mye bredere enn viewporten er i seg selv
et ustabilt mønster — ved scroll-posisjon 0 blir de sticky høyre-
kolonnene umiddelbart trukket til synlig høyre kant, og alt som
"egentlig" befinner seg der i normal dokument-flyt (hull 3+) blir
liggende RETT BAK dem, delvis synlig gjennom `bg-muted/60`s
delvise gjennomsiktighet. **Fikset ved å fjerne sticky-posisjonering
fra `Ut`/`Inn`/`Sum`-kolonnene helt** (de scroller nå med resten av
hullene i normal flyt, samme velprøvde, veletablerte mønster som den
gjenværende sticky VENSTRE navnekolonnen, som fungerte korrekt hele
tiden) — droppet også `/60`-gjennomsiktigheten til fordel for en helt
opak `bg-muted`.
**Verifisert presist, ekte, etter fiksen** (ikke bare "ser bedre ut"):
nytt skjermbilde ved scroll-posisjon 0 viste alle tall rene og lesbare
(Hcp/Par-radene, begge spilleres scoringsceller, korrekt sirkel/firkant-
form for bogey/dobbel bogey/par), OG et script som scrollet gridet helt
til høyre (`scrollLeft = scrollWidth`) bekreftet `Ut`/`Inn`/`Sum` også
rene der (`Ut 36/Inn 36/Sum 72` på Par-raden — stemmer eksakt med en
18-hulls par-72-bane), med navnekolonnen fortsatt korrekt pinnet til
venstre gjennom hele scrollingen. Tilgjengelighetstreet (`take_snapshot`)
bekreftet også at aria-labels med golftermer ("Bogey", "Dobbel bogey",
"Par") faktisk leses ut korrekt — `§Golfscore-språket`s "aldri farge
alene"-regel holder i praksis, ikke bare i teorien.
**Rullet ut live 2026-07-27**, samme dag: `docker compose up -d --build
teecup_frontend` kjørt to ganger (én for det mislykkede første forsøket,
én for den faktiske fiksen), `/health` 200 begge ganger, `teeoff.no`
upåvirket.
**Lærdom:** Chrome DevTools MCP-tilgangen endrer risikobildet for alt
fremtidig håndkodet frontend-arbeid denne økten — bruk den til å
FAKTISK se resultatet før noe rapporteres som ferdig, i stedet for kun
typesjekk+container-boot+`curl`-baserte proxyer for "det virker".
- **Full nettleser-gjennomgang av ALLE 22 skjermer, 2026-07-27 —
systematisk browsersjekk av alt som IKKE var reelt nettleser-testet
tidligere.** Brukeren spurte først hvilke visninger som faktisk finnes
(svart med en gruppert oversikt over `frontend/app/`s 22 `page.tsx`-
ruter), deretter ba om at ALLE de ikke-browsersjekkede skjermene faktisk
ble sjekket. Logget inn som `hei@erol.no` (spiller-konto, egne runder)
og — etter en egen runde med å spore opp riktig konto (magic-link fra
brukeren landet først på feil konto to ganger: `erol.haagenrud@gmail.com`
og kontoen manglet 2FA — satt opp TOTP for ekte ved å hente ut den rå
base32-secreten fra oppsett-skjermen og regne ut en gyldig 6-sifret kode
selv med et frittstående RFC 6238-script, ingen autentisator-app
involvert) — som `erol.haagenrud@envide.no` (org-eier for «Tjøme
Gents») for de org-/turnering-scopede skjermene. Gikk gjennom alle 22
ruter med ekte skjermbilder + konsoll-feil-sjekk (`list_console_
messages`) på hver.
**To reelle funn:**
1. Scorekort-gridet (§1, bygget dagen før) hadde EN NY sticky-kolonne-
overlapp-bug som IKKE fantes i utgangspunktet -- se eget punkt over,
fikset samme økt.
2. **Ny, ekte bug funnet i `round-scorecard.tsx`** (post-runde-
scorekortet, bygget i en tidligere økt) -- "til par" i BÅDE
header-hero-tallet og bunn-"TIL PAR"-brikken regnet
`totalGross - totalPar` der `totalPar` var summen av ALLE 18 hulls
par, ikke bare de faktisk spilte -- ga en absurd "−53 til par" for
en runde med 19 slag på kun 4 hull (skulle vært "+2"). Bekreftet
ved at `/my-rounds/[id]/stats` (en ANNEN komponent) viste riktig
"+2,00 til par" for SAMME runde, som isolerte feilen presist til
`round-scorecard.tsx`. Fikset med en ny `playedPar`-variabel
(paret for KUN spilte hull) brukt i de to til-par-utregningene --
"Par"-brikken nederst beholdt bevisst `totalPar` (hele rundens
par, riktig som statisk referanse). Bunn-"Par"/øvre "Hcp"/"Par"-
referanseradene i `ScoreBlock` ble sjekket og bekreftet IKKE
rammet (brukes kun til den statiske referansen, aldri til en
til-par-utregning).
**For å nå de org-/turnering-scopede skjermene: satt org «Tjøme
Gents» sin `public_profile`/`slug` MIDLERTIDIG (bekreftet med bruker
FØR endring) for å teste `/clubs/[slug]`, deretter revertert
eksplisitt til nøyaktig opprinnelig tilstand (`slug=null`,
`public_profile=false`) — bekreftet med en direkte databasespørring
etterpå at reverten var eksakt.
**Alle 22 skjermer bekreftet uten krasj/konsoll-feil** (utenom
forventede 401/403 på steder som SKAL avvise — feil passord-forsøk,
privat lagchat, ugyldig verify-token). Full liste med status i
chat-loggen denne runden.
**Rullet ut live 2026-07-27**, ingen migrasjon for til-par-fiksen,
`docker compose up -d --build teecup_frontend`, verifisert direkte i
nettleseren mot den samme runden som viste bugen (nå "+2 til par",
korrekt).
- **Dashboard-runde: bane-navn-fiks, to nye designfarger, bane-detaljvisning
og aggregert statistikk — BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE
(2026-07-28):** fem punkter reist av brukeren i samme runde, alle
bekreftet eksplisitt før bygging (AskUserQuestion) unntatt fargevalget,
der brukeren ba om at jeg selv "tok på designerbrillene" og foreslo.
1. **"Kommende runder" → "Runder"** på dashbordet (`dashboard.tsx`,
`UpcomingRounds`) — ren tekstendring, ingen annen forekomst i kodebasen.
2. **Tjøme-bane-duplikatet rettet i ekte `teecup_db`:** presist
diagnostisert FØR noe ble kjørt — alle tre av `hei@erol.no` sine
runder pekte til nøyaktig samme `teeoff_facility_slug`/
`teeoff_course_id` (`tjome-golfklubb`/140), kun `round.
course_name_snapshot`-TEKSTEN differerte (én runde fra FØR
enkeltbane-navnefiksen 25.07 hadde "Tjøme Golfklubb – Hovedbanen").
Ingen delt banetabell å slå sammen — frittstående runder har (bevisst,
ADR-033 Beslutning C) ingen egen `course`-rad, kun et navn-snapshot per
runde. Fiksen var én presist scopet `UPDATE round SET
course_name_snapshot = 'Tjøme Golfklubb' WHERE id = 'fa6e528f-...'`
(1 rad), kjørt etter eksplisitt bekreftelse, verifisert med en
read-only spørring rett etterpå.
3. **To nye kjernefarger** — brukeren ba eksplisitt om minst én, "kanskje
to", etter først å ha fått presentert (og avvist "kun én") en
tidligere anbefaling fra 25.07 om å løfte den allerede eksisterende
`--chart-3`-blåtonen. Løftet BEGGE allerede validerte statistikk-
fargene i stedet for å finne opp nye OKLCH-verdier: `--info`
(fra `--chart-3`, blå) og `--gold` (fra `--chart-4`, gul/gull) —
nye tokens i `globals.css` (`:root`/`.dark`/media-dark-blokken, alle
tre synkronisert som vanlig). Konkret, begrunnet førstebruk for
begge, ikke bare dekorativt: `--info` på et nytt "Hcp spilt til
X"-merke (før: ren grå tekst) på rundekort (`round-card.tsx`),
`--gold` på et nytt "Personlig rekord"-merke (`Medal`-ikon) som vises
når en fullført runde er brukerens laveste til-par blant MINST to
fullførte runder (unngår at den eneste fullførte runden feilaktig
kalles en "rekord"). Beregnet i `dashboard.tsx`/`own-rounds.tsx`/
`course-rounds.tsx` sine respektive `findPersonalBestRoundId()`.
4. **Bane-detaljvisning:** "Spilte baner" på dashbordet er nå klikkbar
(`PlayedCourses`, `dashboard.tsx`) — ny rute
`/my-rounds/course/[name]` (`components/course-rounds.tsx`), gjenbruker
eksisterende `GET /rounds` (ingen nytt backend-endepunkt), filtrerer
client-side på nøyaktig samme `course_name_snapshot`-nøkkel dashbordets
egen gruppering allerede bruker. "Personlig rekord" regnes likevel over
HELE rundelisten, ikke bare denne banens, for at merket skal bety det
samme uansett hvor et rundekort vises.
**Reell bug funnet OG fikset UNDER browserverifisering, ikke antatt
riktig fra kildekoden alene:** første versjon leste `params.name` rått
uten `decodeURIComponent` (matchet et eksisterende mønster i
`/clubs/[slug]/page.tsx` som aldri hadde blitt testet med et navn som
inneholder mellomrom/æøå) — et ekte skjermbilde viste tittelen som
`Tj%C3%B8me%20G...` og "0 runder funnet", siden matchen skjedde mot den
RÅ URL-kodede strengen. Rettet med et eksplisitt `decodeURIComponent`,
bekreftet med et nytt skjermbilde: riktig tittel og alle tre Tjøme-
rundene listet, inkl. både `--info`- og `--gold`-merkene rendret
korrekt.
5. **Aggregert statistikk, klikkbar fra dashbordet:** ny
`GET /rounds/stats/summary` (`app/routers/rounds.py`), ny side
`/my-rounds/stats` (`components/rounds-stats-summary.tsx`), lenket
fra dashbordets "Statistikk"-seksjon ("Se full statistikk"). Bruker
valgte det BREDESTE av tre foreslåtte omfang (utover kun runder/snitt-
til-par/putt: fairwaytreff, GIR, én-putt, scrambling, sand save,
snitt chip/bunker/straffeslag/anywayslag per runde) — portert fra de
allerede R&A/manuelt verifiserte formlene i `round-stats.tsx` sin
`computeStats()` for ÉN runde, generalisert til å pole alle kvalifiserte
hull på tvers av ALLE fullførte runder (ikke gjennomsnitt av per-runde-
prosenter, som ville vektet små utvalg feil).
**Putt/18-hull-regelen** (brukerens eksplisitte instruks, presist
bekreftet tolkning FØR bygging via AskUserQuestion): `round_hole` har
alltid nøyaktig 18 rader per deltaker uansett `holes_planned` (9 eller
18) — verifisert i `_create_participant`. For hver fullført runde der
puttsporing faktisk var på (`stat_level != 'strokes_only'`) telles
derfor alle 18 lagrede rader, et hull uten registrert putt-verdi
(uspilt, eller utenfor et 9-hulls spilleomfang) telles som 2 putter —
runder UTEN puttsporing holdes helt utenfor tallet (ellers ville alle
18 hull feilaktig blitt padded). Kun putt-tallet padder — alle andre
andelstall bruker KUN faktisk registrerte hull, som instruert.
**Verifisert i to lag:** (a) en frittstående Python-simulering av
nøyaktig samme aggregeringslogikk mot et hånd-konstruert 3-runde-
datasett (én `strokes_only`-runde padding-ekskludert, én
`strokes_and_putts`-runde med 9 av 18 hull padded) — alle hånd-regnede
forventninger stemte eksakt, inkl. det kritiske tilfellet (padded runde
ga nøyaktig 36 putt/18, strokes_only-runden talte 0 mot totalen); (b)
ekte typesjekket produksjonsbuild + import-sjekk av hele FastAPI-appen
i det faktiske prod-imaget (ikke bare syntaks) + et ekte browserbesøk
som viste reelle, korrekt utregnede tall for `hei@erol.no` sine 2
fullførte runder.
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt for
databaseskrivingen (punkt 2) og fargevalget (punkt 3, "to farger i stedet"
for anbefalt én); resten bygget direkte på brukerens egen presise
instruks. Ingen migrasjon. `docker compose up -d --build teecup_api
teecup_frontend` (kjørt to ganger — én gang for hovedleveransen, én gang
for `decodeURIComponent`-fiksen over). `/health`/`/dashboard`/
`/my-rounds/stats`/`/my-rounds/course/...` alle bekreftet 200 og
konsoll-feilfrie i en ekte innlogget nettleser-sesjon, `teeoff.no`
upåvirket.
- **Statistikk-siden utvidet: miss-retning, tidsvindu + forrige-periode-
sammenligning — BYGGET, TESTET OG LIVE (2026-07-28), samme dag, rett
etter forrige punkt:** brukeren reiste tre ting samtidig — ønsket
miss-RETNING (ikke bare treff%), trender, og et presist spørsmål om
hvorvidt GIR-fra-score-og-putt-inferens faktisk var forstått/utnyttet.
**Siste punkt avklart, ikke en bug:** bekreftet at `isGir = score - putts
<= par - 2` (allerede i `round-stats.tsx`, portert uendret til det nye
aggregerte endepunktet dagen før) ER akkurat denne generelle inferensen —
fungerer for ALLE score/putt/par-kombinasjoner, ikke bare brukerens
eksempel. Eneste reelle begrensning (forklart, ikke fikset): krever
putt-tall, så `strokes_only`-runder får aldri en GIR-verdi — score alene
er nesten aldri nok til å BEVISE GIR (en scrambling-birdie fra utenfor
green gir identisk score som en ekte GIR+1-putt).
**Miss-retning bygget:** `_summarize_rounds` i `app/routers/rounds.py`
utvidet med `fairway_left_pct`/`fairway_right_pct` (samme
`fairway_tracked`-pool som treff%) og `green_miss_long/short/left/
right_pct` (egen pool — hull der `approach_result` er registrert og ≠
"hit", UAVHENGIG av om putt er kjent, samme adskilte spor som
`round-stats.tsx` sin `missedGreen`/`missDir`). Ny `MissBar`-komponent i
`rounds-stats-summary.tsx` (fordelingsbar, samme visuelle idé som
`SegmentedBar`/`DistributionBar` andre steder i appen).
**Tidsvindu + forrige-periode bygget:** ny `StatsWindow`-type (`Literal`)
og `_resolve_stats_window()` — brukeren ba eksplisitt om BÅDE en full
velger (Siste runde/5/10/Denne måneden/I år/Siste år/Alltid) OG
sammenligningspiler mot forrige periode (utover det opprinnelig
anbefalte "kun siste 5 vs. alltid, ingen piler"). Én ENSARTET regel for
"forrige periode" på tvers av alle vindutyper — samme LENGDE (antall
runder for rullerende antall-vinduer, antall DAGER for dato-vinduer)
rett før gjeldende vindus start — i stedet for å måtte spesialdefinere
"forrige måned"/"forrige år" ulikt for hver kalenderbasert type.
`GET /rounds/stats/summary?window=...` returnerer nå `{window, current,
previous}` (`RoundStatsWindowSummary`) — samme `_summarize_rounds()`-
funksjon kalt to ganger på to ulike round_id-mengder, ingen duplisert
aggregeringslogikk. Frontend fikk en ny `Delta`-komponent (pil + farge,
farget etter en eksplisitt `goodDirection`-per-tall — lavere er bedre
for til-par/putt/chip/bunker/straffeslag/anywayslag, høyere er bedre for
treff-/rednings-prosentene, INGEN farge på de rene miss-retnings-tallene
siden venstre/høyre ikke er "bedre/verre").
**Verifisert i to lag:** (a) en frittstående Python-simulering av
`_resolve_stats_window()` mot syntetiske datasett — rullerende antalls-
vindu (riktig current/previous-splitt, riktig tomt `previous` når for få
runder finnes), dato-vindu (kalender-til-dato + rullerende forrige
periode), og en isolert miss-retning-poolingstest (`None`-verdier
korrekt ekskludert fra nevneren); (b) ekte typesjekket produksjonsbuild
(måtte rette en TypeScript-nullbarhets-feil underveis — `current`/
`previous` pakket ut via en IIFE inni JSX for at TS skulle smalne begge
riktig), full import-sjekk av hele FastAPI-appen i prod-imaget, OG et
ekte browserbesøk som viste reelle tall (fairway-miss 36/43/21,
green-miss-retning 0/75/13/13) + bekreftet vindu-velgeren fungerer
(klikket "Siste 10", pillen ble grønn, tallene oppdaterte seg) + et
direkte `fetch()`-kall fra siden selv som bekreftet `{window, current,
previous}`-formen (2 runder i `current`, korrekt tomt `previous` siden
brukeren kun har 2 fullførte runder totalt).
**Rullet ut live 2026-07-28**, ingen migrasjon, `docker compose up -d
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
`/health` → 200, anonymt `GET /rounds/stats/summary?window=last_5` → 401
(bekrefter query-parameteren ruter riktig gjennom Caddy/rewrites),
`teeoff.no` upåvirket.
- **Statistikk-siden: visuell v2 (kompass-diagram + donuter), HÅNDKODET og
BROWSERVERIFISERT MED EKTE ITERASJON, LIVE (2026-07-28), samme dag:**
brukeren delte et referansebilde av en konkurrent-app sin statistikk-
skjerm (donuter med sentertall, kompass for miss-retning) og ba om noe
tilsvarende. Skrev først et fullt V0-prompt (data-kontrakt, komponent-
for-komponent) — brukeren var tom for V0-credits og spurte eksplisitt om
jeg kunne "gi promptet til meg selv" og bygge det direkte, siden Chrome
DevTools MCP nå gjør det mulig å faktisk SE resultatet underveis (ulikt
tidligere håndkodede runder denne uken som kun var typesjekket).
**Backend:** tre nye felt i `RoundStatsSummary`/`_summarize_rounds`
(`app/routers/rounds.py`) — `putt_dist_one_pct`/`two_pct`/
`three_plus_pct`, EGNE eksakte bøtter (`putts==1`/`==2`/`>=3`, samme som
`round-stats.tsx` sin `puttCategories`), bevisst forskjellig fra det
allerede eksisterende `one_putt_pct` (som bruker `<=1` — en annen,
allerede etablert rate, ikke en distribusjon).
**Frontend, `rounds-stats-summary.tsx` skrevet om betydelig:** ny
`GirCompass` — greentreff-prosenten i midten (grønn fremheving), fire
retningsceller rundt (Langt=topp, Kort=bunn, Venstre=venstre,
Høyre=høyre), samme visuelle språk som den allerede etablerte
`DirectionCross`/`DirButton` fra `round-detail.tsx` (plusstegn-rutenett,
`min-h-16 rounded-2xl border`-celler) — bevisst IKKE fargekodet
grønn/oransje på retningscellene (retning er beskrivende, ikke god/
dårlig), kun sentercellen. Ny gjenbrukbar `Donut`-komponent
(conic-gradient, sentertall + valgfri forklaringsliste via
`showLegend`) brukt til BÅDE puttfordelingen (3 segmenter: 1-putt/
2-putt/3-putt+) og to enkle rednings-gauger (scrambling/sand save, ett
segment). Bevisst IKKE et flyt-/beslutningstre-diagram for scrambling/
sand save (som referansebildet hadde) — det er to uavhengige prosenter i
datamodellen, ikke en forgrening, et flytdiagram ville antydet en
struktur som ikke finnes. Bevisst INGEN trendlinje (referansebildets
fjerde element) — for lite rundehistorikk til å vise noe meningsfullt
ennå, samme vurdering som tidligere samme dag.
**Reelt funn UNDER selve visuell iterasjon, ikke bare kodegjennomgang:**
første versjon av "Redning"-kortet viste samme tall TO GANGER per gauge
(`Donut` sin egen auto-genererte forklaringsliste "● Scrambling 9 %" RETT
OVER en egen, manuelt lagt til "Scrambling"-bildetekst) — sett direkte i
et ekte skjermbilde, ikke antatt. Fikset ved å legge til en
`showLegend`-prop på `Donut` og slå den av for de to gaugene, beholdt kun
min egen kompakte bildetekst+delta under ringen.
**Verifisert:** ekte typesjekket produksjonsbuild (to runder — én for
hovedversjonen, én for legend-fiksen), full backend-import-sjekk i
prod-imaget, OG ekte skjermbilder tatt FØR og ETTER legend-fiksen i en
innlogget nettleser-sesjon (samme mønster som sticky-kolonne-bug-fiksen
tidligere denne uken) — kompasset viser reelle tall (39 % greentreff,
75 % kort, 13/13 % venstre/høyre, 0 % langt for `hei@erol.no` sine 2
runder), puttfordelings-donuten viser korrekt fargede segmenter (17/61/
22 %), vindu-velgeren fungerer fortsatt uendret (klikket "Siste 5" etter
redesignet, ingen konsoll-feil). Ingen migrasjon.
**Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api
teecup_frontend` (kjørt to ganger). `/health` → 200 begge ganger,
`teeoff.no` upåvirket.
**Oppfølging, samme dag:** brukeren ba om at fairway-baren sin venstre/
høyre-bom bruker SAMME farge (i stedet for to ulike chart-farger) --
begge er tross alt bare "bom", ingen grunn til å skille dem visuelt.
Byttet begge til `--brand-orange` (appens etablerte "bom/over par"-farge),
beholdt grønn kun for selve fairwaytreffet. Verifisert med et nytt
skjermbilde. Rullet ut, ingen migrasjon.
- **"Til par: med vs. uten"-splitt, BYGGET, TESTET OG LIVE (2026-07-28),
samme dag:** brukeren ba eksplisitt om snitt-til-par splittet på om en
hull-hendelse inntraff eller ikke -- greentreff, fairwaytreff, bunker,
OG anywayslag (eksplisitt fremhevet). Fire nye par felt i
`RoundStatsSummary`/`_summarize_rounds` (`app/routers/rounds.py`):
`avg_to_par_with/without_gir`, `_fairway_hit/miss`, `_with/without_bunker`,
`_with/without_anyway` -- pooler ENKELTHULL på tvers av alle runder i
perioden (ikke per-runde-snitt, siden dette er hull-nivå-betingelser),
samme `diff()`-formel som `round-stats.tsx` sin `avgToParWithGir`/
`avgToParFairwayHit`/`avgToParWithBunker` bruker for én runde (portert
uendret) -- anywayslag-splitten er en ny, konsekvent utvidelse av
akkurat samme mønster (fantes ikke fra før for enkeltrunder heller).
Ny `CompareToPar`-komponent i `rounds-stats-summary.tsx`: to bokser side
om side, den med FAKTISK lavest til-par denne perioden fremheves grønn
-- ingen hardkodet antakelse om hvilken side som "skal" vinne (bekreftet
reelt i data: "Med anywayslag" var faktisk verre enn "Uten anywayslag"
som forventet, men "I bunker" var marginalt BEDRE enn "Ikke i bunker"
for denne brukerens 2 runder -- fremhevingen fulgte automatisk det
virkelige tallet, ikke en antakelse).
**Verifisert:** en frittstående Python-simulering av alle fire splittene
mot et hånd-konstruert 5-hulls datasett (eksakte forventede gjennomsnitt
regnet ut for hånd og sammenlignet), full backend-import-sjekk i
prod-imaget, ekte typesjekket build, OG et ekte skjermbilde i innlogget
nettleser som viste reelle, korrekt utregnede og korrekt fargede tall
for alle fire kategoriene. Ingen migrasjon.
**Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api
teecup_frontend`, `/health` → 200, `teeoff.no` upåvirket.
- **Gjennomgang av frittstående runder (ADR-033) + det viktigste hullet
lukket: faktisk (beregnet) HCP, BYGGET, SCRATCH-/BROWSERVERIFISERT OG
LIVE (2026-07-28, ADR-038):** brukeren ba om en vurdering av om alt var
tenkt gjennom for single-runder. Fant ved grep at hele WHS-indeksmotoren
(`handicap_index_from_differentials`/`low_handicap_index`/
`apply_index_caps` i `handicap_engine.py`, 41/41 testet siden ADR-033)
ALDRI ble kalt fra noe API-endepunkt — `round_participant.
score_differential` ble regnet og lagret per runde, men `app_user.
handicap_index` endret seg kun manuelt. Bekreftet eksplisitt i
`rounds.py` sin egen moduldoc ("skjer IKKE automatisk her -- eksplisitt
uavklart punkt i ADR-033"). Sekundære, mindre hull notert samtidig
(offline-kø kun for turnering-scorekortet, ikke frittstående runder;
ingen Stableford; ingen rundedeling/visibility) — brukeren valgte å ta
tak i HCP-hullet.
**To load-bærende design-avklaringer** (AskUserQuestion, se ADR-038):
(1) hver INNLOGGET deltaker (ikke bare eieren) styrer sin egen
eksklusjon fra faktisk HCP; (2) faktisk HCP designes NÅ for å kunne
inkludere begge kilder (frittstående runder OG en fremtidig turnering-
kilde), men v1 bygger kun runder-delen — løst med ett bevisst tynt,
navngitt skjøtepunkt (`_gather_qualifying_differentials`), IKKE en ny
generell "scoring record"-tabell (for tidlig abstraksjon).
**Ny migrasjon `030_actual_handicap_index.sql`:** `app_user.
computed_handicap_index`/`computed_handicap_index_updated_at` (den
faktiske, beregnede WHS-indeksen — ALDRI direkte redigerbar, kun
avledet), `round_participant.exclude_from_handicap` (manuell opt-out,
uavhengig av den automatiske `counts_for_handicap`-kvalifiseringen),
`round.play_format` (`stroke`/`match`, selvdeklarert — ingen egen
match-motor for frittstående runder), `handicap_history.source`
(`manual`/`computed` — gjenbruker EKSISTERENDE tabell fra ADR-031-
oppfølgingen i stedet for en parallell historikk-tabell, siden Low
Handicap Index/cap (Rule 5.7/5.8) trenger nøyaktig samme
dato+indeks-form som allerede fantes der).
**WHS-kilde lest og lagt til grunn** (`WHS_Rules_of_Handicapping_2024.
pdf`, Rule 3.3): matchspill-scorer ER teknisk et gyldig HCP-grunnlag
under WHS, MEN et konsedert/ikke-utspilt hull krever en subjektiv "most
likely score" TeeCups rene slagregistrering ikke har noen vei til å
representere presist — begrunner "spør (med anbefalt eksklusjon), ikke
tving"-designet i stedet for et hardkodet forbud mot matchspill i
HCP-grunnlaget.
**Backend (`app/routers/rounds.py`):** ny `_recompute_computed_
handicap_index(conn, user_id)` — henter de ≤20 nyeste kvalifiserende
differensialene, kaller den allerede-testede motoren, henter tidligere
`computed`-historikk for Low HI-cap (hopper bevisst over capping ved
FØRSTE beregning noensinne — Low HI er udefinert før en indeks er
etablert). Kalt fra `complete_round` (alle deltakere med `user_id`),
`update_participant` (eksklusjon endret på en ALLEREDE fullført runde),
`remove_guest_participant` (dekker faktisk enhver ikke-eier-fjerning,
til tross for navnet) og `delete_round` (fanger berørte brukere FØR
kaskade-slettingen fjerner radene). `update_participant` sin
autorisasjon utvidet presist: en ikke-eier kan KUN sende
`exclude_from_handicap`, KUN på sin egen rad (`_OWNER_ONLY_
PARTICIPANT_FIELDS`-sjekk + eksplisitt eier-eller-selv-gate) — alle
andre felt forblir strengt eier-only, uendret.
**Backend (`app/routers/auth.py`):** `Me` fikk `computed_handicap_
index`/`computed_handicap_index_updated_at`. Ny `POST /auth/profile/
handicap/apply-computed` — kopierer gjeldende beregnet verdi inn i det
manuelt satte HCP-et (samme skrivevei/historikk-logging som en vanlig
manuell PATCH, kun `source='manual'`). `GET /auth/profile/handicap-
history` eksponerer nå `source` også.
**Frontend:** `/my-rounds/new` fikk en "Spilleform"-bryter
(Slagspill/Matchspill) — velges Matchspill, forhåndsutfylles (ikke
tvinges) en eksklusjons-avkrysning med forklarende tekst.
`round-detail.tsx` fikk en "Matchspill"-badge i headeren, en
spilleform-bryter i `EditRoundPanel` (ren metadata, redigerbar uansett
fullført-status), og `EditParticipantPanel` fikk en ny `restricted`-
modus: en ikke-eier som redigerer SIN EGEN rad, ELLER EIEREN etter at
runden er fullført, ser KUN eksklusjons-toggelen (ikke tee/HCP/navn/
statistikk) — `PlayerList` sin "Rediger"-knapp vises nå også for en
ikke-eiers egen rad, ikke bare for eieren. `/account` fikk et nytt
"Faktisk HCP (beregnet)"-kort (verdi + sist-beregnet-dato +
"Bruk som mitt HCP →"-knapp), og HCP-historikk-listen viser nå
"beregnet"/"manuelt" per rad.
**Scratch-verifisert grundig, 111/111 sjekker** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container via ekte HTTP, samme mønster som hele prosjektet):
hånd-utregnet WHS-matte bekreftet PRESIST (tre runder med kjente
differensialer [10.0, 12.0, 14.0] ga nøyaktig 8.0 — beste-1-av-3 med
-2.0-justering fra Rule 5.2a-tabellen), <3 tellende runder gir fortsatt
`null` (ikke en feil), sletting av en tellende runde regner faktisk HCP
på nytt (tilbake til `null` under 3), matchspill-runde eksplisitt
ekskludert ved opprettelse telles korrekt IKKE med, "Bruk som mitt
HCP"-overføring bekreftet (+ `handicap_history` bærer nå begge kilder),
og hele autorisasjonsmatrisen for eksklusjons-feltet (medspiller nektes
andre felt og eierens rad, men kan endre EGEN eksklusjon; eieren kan
fortsatt overstyre medspillerens). `test_isolation.sql` 12/12 uendret.
**Reelt funn UNDER selve scratch-oppsettet, ikke i produksjon:** første
forsøk på en engangs API-container monterte kildekoden til feil sti
(`/app/app` i stedet for `/srv/app`, som er `Dockerfile` sin faktiske
`WORKDIR`) — containeren boot-et rent, men kjørte stille det GAMLE,
innbakte imagekoden uendret. Fanget FØR noe ble stolt på, ved at
`/auth/me` manglet det nye feltet helt i et faktisk API-svar — rettet
ved å montere til riktig `/srv`-sti, deretter bekreftet på nytt.
**Ekte nettleser-verifisert** (Chrome DevTools MCP, engangs `next dev`-
container mot scratch-backend — samme "sett resultatet, ikke bare
typesjekk det"-arbeidsmåte som resten av uken): logget inn via ekte
magic-link, opprettet en matchspill-runde → bekreftet
forhåndsutfylt-men-overstyrbar eksklusjonsavkrysning i skjemaet,
"Matchspill"-badge i rundens header, fullførte runden → bekreftet
`EditParticipantPanel` automatisk bytter til restriktert modus (kun
eksklusjons-toggel, ingen av de andre feltene), lagret en endring →
bekreftet ekte `PATCH .../participants/{id}` 200 i nettverksfanen.
Ekte typesjekket produksjonsbuild (alle 24 ruter) kjørt og bekreftet.
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: migrasjon
030 kjørt mot ekte `teecup_db` (alle fire nye kolonnesettene bekreftet,
`test_isolation.sql` fortsatt 12/12 mot ekte database), deretter
`docker compose up -d --build teecup_api teecup_frontend`. Begge
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket. Verifisert presist at den nye ruten faktisk når FastAPI:
anonymt `POST /auth/profile/handicap/apply-computed` over ekte https ga
korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.
**Bevisst utenfor omfang, notert i ADR-038:** turnering-/organisasjons-
scoring teller fortsatt ikke mot faktisk HCP (venter på videre
ADR-037-arbeid), ingen egen "aging"-bakgrunnsjobb utover at spørringen
alltid henter kun de 20 nyeste differensialene.
- **Ekte spillformer for frittstående runder (match/skins/fourball/
foursome/greensome/scramble), BACKEND BYGGET OG SCRATCH-VERIFISERT,
IKKE ENNÅ RULLET UT MOT EKTE SYSTEMER (2026-07-28, ADR-039):** reist av
brukeren rett etter ADR-038: "Er dette en slagspillsrunde, en match
mellom to spillere, skins, eller en par- eller lag-konkurranse.
Avhengig av svaret så må hcp beregnes forskjellig, og også scorekortet
vil se annerledes ut." Fire load-bærende avklaringer (AskUserQuestion,
to runder) FØR bygging — bruker valgte det bredeste omfanget i alle
runder: to sider (Beslutning A), ALLE fire par-/lag-underformater fra
start inkl. de to delt-ball-krevende (Beslutning B), skins med BEGGE
akser konfigurerbare av oppsetteren (netto/brutto OG rullerer/deles,
Beslutning D), og gå rett til migrasjon+motor samme økt (ikke bare
dokumentere).
**Kjerneinnsikt som gjorde dette trygt å bygge fort:** hele match-play-
motoren (`handicap_engine.py` sin `Format`/`AllowanceStrategy`-familie/
`match_play_strokes`/`compute_match_state`) og hele "delt-ball vs.
individuell"-mønsteret (`hole_score.match_participant_id` NULLABLE,
delt-ball-rader identifisert av `team_side` alene) fantes ALLEREDE,
bygget og produksjonskjørt for org-scopede turnering-matcher. Denne
runden PORTERER dette mønsteret til frittstående runder (nye
`round_side`/`round_participant.round_side_id`/`round_participant.
playing_handicap`/`round_hole.round_side_id`) i stedet for å finne opp
noe nytt — kun skins (ingen turnering-motstykke) fikk EKTE ny
motorkode (`compute_skins`, 7 nye tester, 50/50 i `test_handicap_
engine.py`). `app/handicap.py` sin `_SIDE_IS_UNIT` omdøpt til
`SIDE_IS_UNIT` (gjort delt for gjenbruk, samme "fjern understrek når
et andre bruksted dukker opp"-mønster som tidligere runder).
**Skjema, migrasjon `031_round_play_formats.sql`:** `round.play_format`
utvidet til 8 verdier, nye `round.skins_scoring`/`skins_tie_handling`,
ny `round_side`-tabell (nøyaktig to per runde, håndhevet i app-laget
som ADR-011s to-lags-grense), `round_participant.round_side_id`/
`playing_handicap` (sistnevnte ALDRI det samme som det eksisterende
`course_handicap_snapshot` — den absolutte WHS-verdien rørt av INGEN
av denne rundens kode), `round_hole.round_participant_id` gjort
NULLABLE + ny `round_hole.round_side_id` + XOR-CHECK + to partielle
unike indekser (samme mønster som org-scopet `hole_score`, migrasjon
001).
**Reelt, bekreftet funn UNDER selve designet (ikke antatt), avklart
eksplisitt med bruker FØR bygging:** foursome/greensome/scramble har
ÉN kombinert score per SIDE per hull -- INGEN individuell score
finnes i det hele tatt å bygge en Score Differential fra. Løst
(Beslutning E, bekreftet av bruker): disse deltakerne får
`counts_for_handicap` ALDRI sann for disse rundene -- og viste seg,
presist verifisert i scratch, å følge HELT AUTOMATISK av den
eksisterende `complete_round`-logikken uten noen kodeendring i det
hele tatt (en delt-ball-deltaker har null individuelle `round_hole`-
rader, så `played_count` blir alltid 0, som `round_counts_for_
handicap` allerede tolker som "teller ikke" for både 9- og
18-hulls-intensjon).
**API (`app/routers/rounds.py`):** `POST/DELETE .../sides` (eier-only,
maks to, avvist for slagspill/skins), `ParticipantCreate`/
`ParticipantUpdate` fikk `round_side_id` (eier-only reassignment,
validerer siden hører til samme runde), `_recompute_side_handicaps`
(porterer `compute_and_store_side_handicaps` — venter på at siden når
forventet spillerantall FØR den skriver noe, samme
"ikke komplett ennå = ikke skriv"-filosofi som originalen),
`_relative_strokes_for_round` (porterer `relative_strokes_for_match`),
nye `GET/PATCH .../sides/{id}/holes/{n}` (delt-ball-scoring, kun
slagtall — ingen av de andre detalj-feltene gir mening for en delt
ball), og et nytt lese-endepunkt `GET .../format-result` som regner
løpende matchstatus (gjenbruker `compute_match_state`/`HoleResult`
uendret) for de to-sidede formatene, eller en skins-tavle
(`compute_skins`) for skins — aldri lagret, alltid avledet ved lesing.
**To reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde
produksjon:** (1) side-tildelings-recompute kjørte FØR responsen ble
hentet i stedet for ETTER — testen fanget dette presist (forventet
`playing_handicap` i responsen, fikk `null` fra FØR omregningen); (2)
individuell-ball-gren i format-result-spørringen nøkkel-forvekslet
"enhet" (satte `round_side_id` som nøkkel i stedet for
`round_participant_id`, mens `side_net()`-oppslaget forventet
deltaker-id) — ga et tomt `hole_results` til tross for gyldige
registrerte scorer, fanget da `match_holes_played` kom ut som 0 i
stedet for det forventede 3.
**Scratch-verifisert grundig, 96/96 sjekker** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container via ekte HTTP, samme mønster som hele prosjektet): en
hånd-utregnet 3-hulls singles-match (hcp 5 vs. 10, riktig slagmottak
på de fem vanskeligste hullene) ga eksakt `lead=0`/"AS"/riktig
hole-for-hole-mønster; fourball bekreftet INDIVIDUELL (ikke kombinert)
90 %-beregning per spiller, korrekt "ikke komplett ennå" før andre
spiller på siden var tildelt; foursome bekreftet 18 round_hole-rader
opprettet på SIDEN ved side-opprettelse (før noen deltaker lagt til),
korrekt kombinert 50 %-Playing-Handicap først når begge var tildelt,
ekte delt-ball-scoring via det nye sub-endepunktet, OG at
`counts_for_handicap` ble `False` for begge etter fullføring; skins
(netto+carry) ga eksakt `{sk3: 2.0}` for et 2-hulls scenario med et
bevisst konstruert uavgjort-så-carry-så-outright-vinn-mønster, OG
bekreftet skins teller NORMALT mot faktisk HCP etter full fullføring
(ulikt match). `test_isolation.sql` 12/12 uendret (additiv migrasjon).
Alle 50 handicap_engine-tester (43 eksisterende + 7 nye skins) grønne.
**IKKE bygget i denne runden, bevisst utsatt (Beslutning F):**
frontend — ingen skjerm for å opprette sider, tildele deltakere,
konfigurere skins, eller vise løpende matchstatus/skins-tavle. Backend
er fullt funksjonelt og testet, men ubrukelig fra selve appen inntil
frontend bygges i en egen, senere runde (samme lagdelings-mønster som
ADR-038: motor/skjema/API FØR frontend).
**Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
eksplisitt: migrasjon 031 kjørt mot ekte `teecup_db` (nye kolonner/
`round_side`-tabell bekreftet, `test_isolation.sql` fortsatt 12/12),
deretter `docker compose up -d --build teecup_api`. Ren boot,
`/health`/`/dashboard` → 200, ny rute bekreftet nåbar (anonymt
`POST .../sides` → 401, ikke en rå 404), `teeoff.no` upåvirket.
- **Oppfølging samme dag: manglende minimums-spiller-håndhevelse fanget
og fikset, BYGGET OG SCRATCH-VERIFISERT, RULLET UT LIVE (2026-07-28):**
brukeren påpekte presist et hull ADR-039 selv ikke fanget opp: "Er det
match-spill, skins eller lagspill så MÅ det jo være flere spillere.
Dette fanges ikke opp." Riktig — ingenting hindret å fullføre en
"match" med kun eieren, eller la flere spillere enn formatet tillater
havne på samme side. Bekreftet med bruker (AskUserQuestion): skins
krever minst 3 spillere (2 gjør skins i praksis identisk med en vanlig
match — 3+ er der en oppsamlet, uavgjort pott faktisk gir mening).
**Bygget, ren Python-logikk, INGEN migrasjon:** ny
`_format_setup_status()` — slagspill alltid klar; skins krever minst
3 deltakere; to-sidede formater (match/fourball/foursome/greensome/
scramble) krever NØYAKTIG to sider, INGEN uassignerte deltakere, og
hver side nøyaktig riktig spillerantall for formatet
(`_SIDE_PLAYER_COUNT`, allerede definert). Ny `setup_complete`/
`setup_message` på `RoundOut` (alltid synlig, uansett format/status).
`complete_round` avviser nå (409 `SETUP_INCOMPLETE`) hvis oppsettet
ikke er komplett — FØR noe regnes ut. Ny `_check_side_capacity()`
avviser (409 `SIDE_FULL`) proaktivt ved SELVE tildelingen (både
`POST .../participants` med `round_side_id` og `PATCH .../
participants/{id}`), i stedet for å først oppdage overtallet ved
fullføring — med eksplisitt unntak for en no-op-reassignment til
samme side (en deltaker teller ikke seg selv ut av plassen sin egen
side har).
**Scratch-verifisert grundig, 122/122 sjekker** (samme isolerte
scratch-oppsett som resten av runden — full regresjon av alle
tidligere 96 sjekker PLUSS 26 nye): 3. spiller avvist på en full
match-/foursome-side (409 SIDE_FULL), `setup_complete` korrekt False
ved kun 1 av 2 sider / uassignerte deltakere / for få skins-spillere,
fullføring korrekt avvist (409 SETUP_INCOMPLETE) i alle disse
tilstandene, og korrekt True (+ vellykket fullføring) først når
oppsettet faktisk er komplett for formatet. `test_isolation.sql`
uendret (ingen skjemaendring).
**Rullet ut live 2026-07-28**, ingen migrasjon, kun
`docker compose up -d --build teecup_api`. Ren boot, `/health`/
`/dashboard` → 200, `teeoff.no` upåvirket.
- **Frontend for ADR-039 (sider/skins/delt-ball-scoring) BYGGET, BROWSER-
VERIFISERT OG LIVE (2026-07-28), samme dag:** ingen backend-endring i
denne runden (alt allerede live) — ren frontend-jobb, testet reelt i
nettleser mot en isolert scratch-backend (Chrome DevTools), ikke bare
typesjekk.
**`new-round.tsx`:** spilleform-velgeren utvidet fra to (Slagspill/
Match) til alle åtte format (Skins/Fourball/Foursome/Greensome/
Scramble 2/4 lagt til), med en ny skins-konfigurasjonsseksjon (netto/
brutto, rullerer/deles) som kun vises for `play_format="skins"` og et
forklarende "du setter opp sidene inni runden etterpå"-notat for de
to-sidede formatene (ADR-039 Beslutning A -- sider kan ikke opprettes
før deltakerne finnes).
**`round-detail.tsx` (hoveddelen):** ny `SidesPanel` (manage-fanen) --
opprett/slett de to sidene, tildel/fjern deltakere (kompakte
"→ Side"-hurtigknapper, deaktivert når siden er full), viser
`playing_handicap` per side. Ny `FormatResultPanel` -- henter
`GET .../format-result`, viser løpende matchstatus (oversetter
motorens bokstavelige "(A)"/"(B)" til faktiske side-navn via
`round.sides[0]/[1]`, samme sorteringsrekkefølge som backend) eller en
skins-tavle (sortert synkende). `setup_complete`/`setup_message`
gater nå "Fullfør runde"-knappen klientside også (server er fortsatt
autoritativ). For delt-ball-formatene (foursome/greensome/scramble):
ny `SideScorecardGrid` (rader = sider, ikke spillere) + ny, forenklet
`SideScoreWizard` (kun slagtall, ingen putt/detalj-steg) mot de
eksisterende `GET/PATCH .../sides/{id}/holes/{n}`-endepunktene.
**To reelle stale-state-bugs funnet UNDER selve browserverifiseringen
(ikke i kodegjennomgang), begge fikset før utrulling:**
1. `SidesPanel` sin `assign()` oppdaterte kun deltaker-listen lokalt
(via `onPatchParticipant`) -- `setup_complete`/`setup_message`
(server-beregnet) ble stående utdatert etter en vellykket
side-tildeling ("ikke tildelt en side" fortsatte å vises til tross
for at begge var tildelt). Fikset: `assign()` kaller nå
`onSidesChanged()` (full runde-refetch) etter en vellykket PATCH.
2. `FormatResultPanel` sin refetch var kun koblet til hull-registrering
og WebSocket-signaler, ikke til side-/deltaker-tildeling --
matchstatus ble stående på "venter..." selv etter at oppsettet var
komplett og "Fullfør runde" allerede var aktivert. Fikset ved å
bumpe `formatResultRefreshTick` ved HVER vellykket `loadRound()`
(enklere og mer robust enn å spore hvert enkelt kallsted som kan
påvirke handicap-beregningen).
**Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
`teecup_scratch`-database, isolert scratch-MinIO, engangs API-
container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP --
ekte innlogging via magic-link, ekte profil-fullføring): tre komplette
runder bygget og spilt gjennom UI-et alene, ende-til-ende:
- **Match:** opprettet med eksklusjons-avkrysning forhåndshuket,
opprettet to sider, tildelte eier+gjest, bekreftet `playing_handicap`
(60/20) vist riktig, scoret hull 1 (4 mot 6) via `ScoringWizard`
(ubrørt komponent), bekreftet `FormatResultPanel` viste "1 UP
(Erol)" + riktig fargede hull-merker -- kryssjekket med
`aria-label`-attributtet direkte via `evaluate_script` for å bekrefte
semantisk korrekt side-navn bak den rå A/B-bokstaven.
- **Skins:** konfigurasjons-UI-et (netto/brutto, rullerer/deles) bekreftet
visuelt, opprettet med kun 1 spiller (satte-message "krever minst 3"),
la til to gjester til (meldingen forsvant idet den tredje ble lagt til,
"Fullfør runde" aktivert), scoret hull 1 for alle tre (via direkte
API-kall for hastighet, samme kontrakt som UI-et bruker), bekreftet
skins-tavlen -- **hånd-regnet og kryssjekket eksakt**: netto 1/4/3 for
de tre spillerne (course handicap 60/12/6, alle mottar 1 slag på
hull 1 unntatt eieren som mottar 4) ga korrekt "1 skin" til laveste
netto.
- **Foursome:** bekreftet "Opprett begge sidene..."-meldingen i Score-
fanen FØR sidene fantes (ingen krasj), opprettet to sider, la til tre
gjester, scoret hull 1 via `SideScorecardGrid`/`SideScoreWizard`
(5 mot 5 -- observerte LIVE at gridet oppdaterte seg bak selve
veiviseren), fant OG fikset de to stale-state-bugene over midt i
denne runden (glemte først å tildele spillerne til sider -- avdekket
nettopp fordi UI-et da IKKE viste feil tilstand, men en ekte utdatert
en), bekreftet til slutt `playing_handicap` kombinert riktig per side
(38/38 og 17/17) og at `FormatResultPanel` viste "1 UP (Rødt lag)"
-- kryssjekket for hånd at Rødt lag (høyere kombinert CH) mottar
slag på det vanskeligste hullet og derfor vinner nettoduellen 5 mot 5.
Ekte typesjekket + full produksjonsbuild kjørt på nytt ETTER
bug-fiksene (ikke bare før), alle 24 ruter listet.
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
migrasjon (ren frontend), `docker compose up -d --build
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
`/dashboard` → 200, `teeoff.no` upåvirket. **ADR-039 er dermed
fullstendig ferdig, backend og frontend, live.**
- **Auto-hopp i scoringsveiviserne, LIVE (2026-07-28), samme dag:**
brukeren ba om at "i det øyeblikket [scoren] nå registreres" skal
veiviseren hoppe videre av seg selv, uten å måtte trykke "Neste"/
"Ferdig". Presiserende avklaring FØR bygging (AskUserQuestion): "Avstand
første putt" lå tidligere PÅ SAMME steg som selve putt-tallet i
`ScoringWizard` -- auto-hopp idet putt-tallet velges ville gjort
avstandsfeltet uoppnåelig (ingen annen inngang finnes). Løst ved å
splitte putt-steget i to (bekreftet anbefalt løsning): `WizardStep`
utvidet med et eget `"puttDistance"`-steg, `wizardStepsFor()` gir nå
`["strokes","putts","puttDistance"]`/`["strokes","putts","puttDistance",
"details"]` for de to høyere statistikknivåene.
**Mekanisme (samme mønster i `ScoringWizard` og den enklere
`SideScoreWizard` for delt-ball-formater):** to refs -- `enteredWithValueRef`
fanger om steget sitt eget felt ALLEREDE hadde en verdi idet steget ble
vist (et allerede utfylt hull skal ikke hoppe videre bare fordi
veiviseren åpnes, og "Forrige" tilbake til et allerede besvart steg skal
ikke re-trigge et nytt hopp), `firedRef` hindrer dobbelt-triggering.
Kun steg med ETT entydig felt (Slag, Putter, Avstand, samt hele
`SideScoreWizard` sitt eneste Slag-felt) auto-hopper -- "flere
detaljer"-steget (kølle/retning/chip/bunker/straffeslag/anywayslag) har
ingen enkelt "dette er ferdig"-verdi og beholder derfor "Neste"/
"Ferdig"-knappen som manuell handling, bevisst uendret.
**Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
database/MinIO/API-container, ekte `next dev`, Chrome DevTools):
full "full"-nivå-runde spilt gjennom Slag→Putter→Avstand (alle tre
auto-hoppet uten et eneste "Neste"-trykk) →detaljer (korrekt IKKE
auto-hoppet, krevde et bevisst "Ferdig"-trykk, som deretter gikk videre
til neste hull av seg selv). "Forrige" fra Putter tilbake til Slag
bekreftet trygt (viste den allerede valgte verdien, hoppet IKKE
automatisk fremover igjen). Delt-ball (`SideScoreWizard`, foursome)
bekreftet separat: valgt slagtall for "Rødt" hoppet umiddelbart til
"Blått" uten trykk. Ekte typesjekket + full produksjonsbuild kjørt før
utrulling, alle 24 ruter listet.
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
migrasjon (ren frontend), `docker compose up -d --build
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Scorekort for spillformater (match/skins/fourball/foursome/greensome/
scramble): to reelle bugs bekreftet og fikset, BYGGET, SCRATCH-/
BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om at scorekortene
faktisk sjekkes ved å simulere 3-4 spilte hull i hvert spillformat, med
et konkret forventningsbilde (en match bør vise hvem som vant hvilket
hull og HVORFOR -- brutto vs. netto, match-hcp). Simulert systematisk mot
en isolert scratch-backend (Python/urllib-testskript) FØR noe ble antatt
riktig -- fant to distinkte, bekreftede problemer, presentert til
brukeren som fikk velge omfang (AskUserQuestion) og valgte full løsning:
1. **Ekte blokkerende bug:** foursome/greensome/scramble (ADR-039,
delt-ball -- score lagres PER SIDE, `round_hole.round_participant_id`
settes ALDRI for disse) viste 0 spilte hull/ingen score på
`/my-rounds/[id]/scorecard`, `/leaderboard` OG `/stats`, uansett
faktisk fremdrift -- disse tre sidene spurte alle kun mot
deltaker-endepunktet (`round_participant_id`), som strukturelt aldri
kan ha data for disse formatene.
2. **Reell designmangel:** for match/fourball/skins var rå brutto/netto/
stableford riktig, men "hvem vant hvilket hull, og hvorfor" fantes
KUN i `FormatResultPanel` (manage-fanen i `round-detail.tsx`) som et
rent vinn/tap-merke -- ingen synlige tall (brutto vs. netto, slag
mottatt) noe sted, og ingenting av dette på de dedikerte Scorekort-/
Leaderboard-sidene brukeren faktisk testet.
**Backend:** ny `compute_skins_detail()` i `handicap_engine.py`
(hull-for-hull-forløp -- verdier/pott-før/tildelt/carried per hull),
`compute_skins()` omskrevet til en tynn wrapper rundt den (uendret
signatur/oppførsel, alle 50 eksisterende tester fortsatt grønne + 5 nye).
`GET /rounds/{id}/format-result` (ADR-039) utvidet med et nytt
`holes`-felt -- full hull-for-hull-oppløsning (brutto/netto/slag mottatt
PER enhet PER hull, pluss for fourball hvilken av de to partnernes netto
som faktisk talte for siden det hullet, R&A-regelen gjort synlig i
stedet for skjult). **Reell refactor-bug funnet OG fikset UNDER egen
scratch-verifisering, før noe ble stolt på:** en samlet `side_net()` for
BEGGE gren-typene (individuell-ball og delt-ball) brukte format-nivåets
`expected_players` (spiller-ANTALL, f.eks. 2 for foursome) som
fullstendighetssjekk også for delt-ball, der en side alltid er NØYAKTIG
ÉN enhet uansett spillerantall -- ga `match_holes_played=0`/tom
`holes`-liste for ALLE delt-ball-formater til tross for korrekt lagrede
side-scorer. Rettet med en egen `required_units`-variabel (1 for
delt-ball, `expected_players` for individuell-ball). `GET .../sides/
{id}/holes` fikk samtidig et nytt `strokes_received`-felt (samme
allokeringsalgoritme, nå basert på sidens kombinerte `playing_handicap`).
**Frontend:** `round-scorecard.tsx` bruker nå SIDER (ikke deltakere) som
"enhet" for delt-ball-formater -- samme visuelle `ScoreBlock`-tabell,
bare mot `/sides/{id}/holes`, med en forklarende melding hvis sidene
ikke er opprettet ennå. Ny `MatchProgressTable`-seksjon (to-sidede
formater: Hull/Par/Side A/Side B/Resultat, brutto→netto per enhet, ikke-
tellende fourball-partner tonet ned i stedet for fjernet) og
`SkinsProgressTable` (skins: hull-for-hull med hvem som vant/hvilket
hull som rullet videre, netto med brutto i parentes). `round-
leaderboard.tsx`: to-sidede formater viser nå en `MatchStatusSection`
(status + hull-merker + lenke til scorekortets fulle oppløsning) i
stedet for en individuell rangering som uansett ikke gir mening for et
1v1/lag-format (og alltid var tom for delt-ball) -- `RoundLeaderboardMini`
returnerer `null` for disse formatene i `round-detail.tsx` sin manage-
fane, siden `FormatResultPanel` allerede dekker akkurat det der. `round-
stats.tsx` viser en tydelig forklarende melding for delt-ball-formater
(individuell slag-for-slag-statistikk er strukturelt umulig der) i
stedet for en stille tom/misvisende side.
**Scratch-verifisert grundig, flere lag:** isolert `teecup_scratch`-
database + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
API-container (samme mønster som hele prosjektet), 55/55
`handicap_engine`-tester, et Python/urllib-simuleringsskript som spilte
4 hull i alle 6 ikke-trivielle formater og sammenlignet rå API-svar før/
etter fiksen. **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome
DevTools MCP, engangs `next dev` mot scratch-backend, ekte innlogging):
opprettet alle 7 formatene (inkl. `stroke` som regresjonssjekk) via
ekte API-kall fra en innlogget nettleserøkt, besøkte deretter
scorekort/leaderboard/stats-sidene for hver -- foursome sitt tidligere
BLANKE scorekort viste nå korrekte side-tabs + reelle score + riktig
`Matchforløp`-tabell; fourball sin `Matchforløp` viste presist BEGGE
partnernes brutto/netto med den ikke-tellende partneren korrekt tonet
ned; skins sin hull-for-hull-tabell viste riktig vinner-navn og
"Uavgjort — rullet videre" nøyaktig der forventet; leaderboardets
matchstatus stemte hull-for-hull med scorekortets egen utregning (bevisst
kryssjekket for hånd); fanebytte mellom sider (Side A/Side B) bekreftet
å faktisk refetche og re-rendre riktig data. Ekte typesjekket +
produksjonsbuild (alle 24 ruter) kjørt både før og etter refactor-bug-
fiksen. `test_isolation.sql` uendret (ingen migrasjon, ren kode-endring).
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket.
- **Offline-kø (ADR-028) utvidet til frittstående runder, BYGGET,
BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om det som ble
identifisert som viktigste gjenstående hull etter forrige runde --
ADR-028s offline-skrivekø var kun koblet til turnering-scorekortet
(`session-scorecard.tsx`), IKKE frittstående runder
(`round-detail.tsx`), nettopp der man oftest står alene ute på banen
uten dekning, og nå som frittstående runder er bekreftet appens
hovedfokus var dette et reelt hull i kjerneflyten.
**Enklere port enn originalen, ikke bare en kopi:** turnering-
scorekortets PATCH-endepunkter er en append-historie (krever egne
`pendingStrokes`/`pendingResults`-verdi-overlays for å vise queued
verdier). Frittstående runders `PATCH .../participants/{id}/holes/{n}`
og `PATCH .../sides/{id}/holes/{n}` erstatter derimot HELE hull-raden
per kall, og `statToPatchBody()` sine feltnavn matcher `ApiHole` 1:1 --
en køet skriving kan dermed speiles direkte inn i
`holesByParticipant`/`holesBySide` med en enkel `{...h, ...body}`-spread,
ingen egen verdi-overlay nødvendig. Kun to lette `Set` (nøkkel
`id:hullnummer`) beholdt, utelukkende til selve "lagret lokalt"-
indikatoren i veiviseren.
Samme kø/synk-mønster som originalen ellers: `navigator.onLine`-sjekk
FØR forsøk, `try/catch` rundt selve fetch-kallet som queuer ved en EKTE
nettverksfeil (ikke ved et avvist HTTP-svar -- det vises fortsatt som
vanlig feiltekst), auto-synk ved `window`s `online`-event, manuell
"Synkroniser nå"-knapp, og en engangs-sjekk for allerede køede
skrivinger fra en TIDLIGERE økt (f.eks. siden ble lukket mens offline)
ved mount. `flushPending()` matcher URL-mønsteret på hver synkronisert
kø-oppføring (`/participants/{id}/holes/{n}` vs. `/sides/{id}/holes/
{n}`) for å vite hvilke deltakere/sider som trenger en ekte refetch
etterpå (reconciles bl.a. `strokes_received`, som den optimistiske
speilingen ikke kan regne ut selv). Banner ("Du er offline"/"N
endringer venter") lagt til rett under fane-velgeren, synlig uansett
fane. "Lagret lokalt · venter på synk"-indikator lagt til i BÅDE
`ScoringWizard` (vanlig scoring) og `SideScoreWizard` (delt-ball).
**Browserverifisert grundig, IKKE bare kodegjennomgang/build denne
gangen** (i motsetning til den opprinnelige ADR-028-runden, som
brukeren selv måtte teste manuelt siden intet nettleserverktøy var
tilgjengelig da) -- Chrome DevTools MCP sin ekte nettverks-emulering
(`Offline`, bekreftet at `navigator.onLine` faktisk flippet til
`false`) mot en isolert scratch-backend: registrerte slag+putter
offline for en vanlig runde -- bekreftet 2 kø-oppføringer i ekte
IndexedDB, optimistisk oppdatert scorekort-grid, "1 endring venter"-
banner, "Lagret lokalt"-indikator i veiviseren; koblet til nett igjen
-- bekreftet AUTOMATISK synk (ingen manuelt trykk), kø tom etterpå, OG
et direkte API-kall som bekreftet serveren faktisk hadde de riktige
verdiene (score=5, putts=1, strokes_received=1). Gjentok hele syklusen
for en ny foursome-runde via `SideScoreWizard` (delt-ball) -- samme
resultat, kø tom og server bekreftet score=4 for siden etterpå. Ingen
konsollfeil i noen av rundene. Ekte typesjekket + full produksjonsbuild
(alle 24 ruter) kjørt før utrulling.
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
migrasjon (ren frontend), `docker compose up -d --build
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket.
- **Turnering-scorekortets offline-flyt (den OPPRINNELIGE ADR-028, session-
scorecard.tsx) FAKTISK browserverifisert, samme dag (2026-07-28):**
eneste reelt gjenstående punkt fra forrige runde — offline-koden i
turnering-scorekortet er over ett år gammel (funksjonelt), men var ALDRI
browser-testet, kun kodegjennomgang/build (se opprinnelig ADR-028-notat).
Bygget en full isolert scratch-turnering fra bunnen via API for å nå
frem til selve scorekortet (organisasjon → turnering → to lag → to
spillere rostret som kapteiner → egendefinert bane med 18 hull + tee →
økt (`format=singles`, `scoring_mode=stroke`) → match → to
match-deltakere → begge lag låst) -- ingen slik full turnering-scaffold
fantes fra før i noe testskript denne uken (frittstående runder trenger
ikke dette apparatet i det hele tatt, ADR-033 Beslutning A), måtte bygges
fra grunnen ved å lese `tournaments.py`/`matches.py`/`courses.py` sine
faktiske endepunkt-kontrakter direkte.
**Verifisert identisk mønster som frittstående runder samme dag:** ekte
DevTools-nettverksemulering (`navigator.onLine` bekreftet `false`),
registrerte et slag (5, Spiller B, hull 1) offline -- bekreftet ÉN
kø-oppføring i ekte IndexedDB (`POST .../hole-scores`), "1 endring
venter"-banner, "Lagret lokalt · venter på synk"-tekst under
tallvelgeren, hull-navigasjonens sjekkmerke. Koblet til nett igjen --
bekreftet AUTOMATISK synk (ingen manuelt trykk på "Synkroniser nå"
nødvendig), kø tom etterpå, "Bogey"-etiketten dukket opp (bevis på at
`refetchScorecard()` faktisk hentet det avledede resultatet fra
serveren), OG et direkte API-kall mot `.../scorecard` som bekreftet
serveren faktisk hadde `gross_strokes=5` lagret. Ingen konsollfeil.
**Ingen kodeendring i denne runden** — ren verifisering av allerede
levert funksjonalitet, ingen utrulling nødvendig.
- **Undersøkt: grensesnitt for å følge en venns runde live — BEKREFTET AT
DET IKKE FINNES (2026-07-28), ren undersøkelse, ingen kode skrevet.**
Brukeren spurte om en spiller kan gå inn på en venns profil og følge en
pågående frittstående runde live, gitt at rettighetene er gitt. Lest
direkte i koden (ikke antatt): (1) `frontend/components/friends.tsx` har
INGEN lenke/rute til en vennprofil-side i det hele tatt — kun
send/aksepter/avvis-knapper og kategorisering, ingen `/friends/[id]`-
eller lignende rute finnes noe sted i `frontend/app/`. (2) `round`-
tabellen har INGEN `visibility`-kolonne (bekreftet med grep over ALLE
migrasjoner — `visibility` finnes kun på `tournament`, fra
`009_landing_pages_and_visibility.sql`); `020_personal_rounds.sql` sin
egen kommentar (linje 39-42) slår eksplisitt fast v1-avgrensningen: en
runde er kun synlig for `owner_user_id`. (3) `app/routers/rounds.py` sin
`_get_accessible_round_or_404` (linje 1220-1240, lest direkte) gir
tilgang KUN til eieren ELLER en lenket `round_participant.user_id`
(ADR-036 fase 3) — ingen sjekk mot `friendship`-tabellen noe sted,
bekreftet med `grep -n -i "friend" app/routers/rounds.py` → null treff.
(4) `GET /ws/rounds/{id}/live` bruker NØYAKTIG samme
`_get_accessible_round_or_404`-sjekk FØR `websocket.accept()` (samme
fil, linje ~2763) — en venn som ikke er lenket deltaker får
websocketen lukket med kode 4403, ulikt turnering-live (ADR-027), som
bevisst TILLATER anonym tilgang. Ingen forberedt/påbegynt kode for
"følg venns runde" funnet noe sted (grep etter "follow"/"følg"/"friend"
i både `rounds.py` og `friends.py` — null relevante treff).
**Konklusjon: dette er nøyaktig ADR-036 fase 2 (rundevisibilitet
public/private/friends), som lenge har stått notert som IKKE bygget i
FEATURE_BACKLOG.md/CLAUDE.md** — bekreftet nå med presis kode-evidens i
stedet for bare et notat om at det mangler. Ingen ny beslutning tatt,
ingen kode skrevet — dette var en ren undersøkelse på brukerens
eksplisitte forespørsel. Se FEATURE_BACKLOG.md for ADR-036 fase 2 sitt
design (public/private/friends, eksplisitt gruppevalg).
- **ADR-036 fase 2 (rundevisibilitet) BYGGET, GRUNDIG SCRATCH-/
BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging
av undersøkelsen over — brukeren bekreftet eksplisitt retningen fra
Beslutning B (allerede fullt designet i ADR-036, ikke funnet opp på
nytt): tre nivåer (`public`/`private`/`friends`), der `friends` krever
et EKSPLISITT kategori-valg (ikke "alle venner").
**Migrasjon `032_round_visibility.sql`:** `round.visibility_mode`
(`public`/`private`/`friends`, default `private`), ny tabell
`round_visible_category` (samme faste kategori-sett som
`friend_categorization`, migrasjon 025, bevisst duplisert CHECK fremfor
delt ENUM — samme pragmatiske mønster som resten av skjemaet).
**Kjernestykket, `app/routers/rounds.py`:** ny `_can_view_round()` —
eier ELLER lenket medspiller ser alltid; ellers `public`→alle (også
anonyme), `private`→ingen, `friends`→krever et AKSEPTERT vennskap MED
eieren OG at EIERENS kategorisering av viewer (retningen er bevisst
omvendt av hva man skulle tro — det er eieren som begrenser, basert på
egen gruppering) treffer minst én av rundens synlige kategorier.
**Reelt funn under selve designarbeidet:** `_can_view_round()` viste
seg å være en STRIKT SUPERSETT av den eksisterende
`_get_accessible_round_or_404()` sin logikk (dens to første grener ER
nøyaktig eier+medspiller-sjekken) — i stedet for å bygge en helt
parallell endepunkt-familie fra bunnen, ble fire eksisterende
autentiserte lese-endepunkters KROPP ekstrahert til delte
hjelpefunksjoner (`_build_participant_holes`/`_build_side_holes`/
`_build_leaderboard`/`_build_format_result` — mekanisk gjort med et
Python-script for presis inndenting fremfor manuell redigering av
~270+95 linjer, verifisert med `ast.parse` + full regresjonskjøring
etterpå), gjenbrukt av BÅDE de originale autentiserte endepunktene OG
syv nye `/public/rounds/*`-endepunkter (samme `get_current_user_
optional`-mønster som turnering sin offentlige side, ADR-018/026/027)
pluss et nytt offentlig WS-endepunkt `/ws/public/rounds/{id}/live`
(ulikt det eksisterende PRIVATE `/ws/rounds/{id}/live`, som fortsatt
krever ekte autentisert eier/medspiller-sesjon, uendret). Egen, leaner
`PublicRoundOut` (aldri `guest_email`, som er PII, aldri `my_*`/
`setup_*`, som kun gir mening for eier/deltaker) i stedet for å
gjenbruke den fulle `RoundOut` med etterhånds-redigering.
Ny `GET /people/{id}` (friends.py, samme lavsensitive felt-sett som
`/people/search`, bevisst OPTIONALT autentisert — en offentlig runde
må kunne nås via en delt lenke selv av en anonym leser, og
vennprofil-siden som leder dit må da fungere anonymt også) og
`GET /public/people/{id}/rounds` (rounds.py, lister eierens runder
filtrert gjennom `_can_view_round()` per rad — "pågår nå" alltid øverst).
**Scratch-verifisert grundig, 191 automatiserte sjekker i tre testløp**
(isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
API-container, samme mønster som hele prosjektet): et NYTT 34-punkts
skript som dekket hele synlighetsmatrisen presist (tre brukere A/B/C +
en helt anonym opener) — B/C/anonym nektet privat OG venner-runde FØR
vennskap, alle ser offentlig (inkl. anonym), venner-runde fortsatt
nektet RETT ETTER vennskap men FØR kategorisering, tilgjengelig
UMIDDELBART etter riktig kategorisering, feil kategori (close_family
mot golf_friends) fortsatt nektet, PATCH kan endre synlighet i
etterkant (bekreftet begge retninger), offentlig+autentisert
leaderboard ga BEVIST IDENTISK resultat (kryssjekket), ukjent
runde-id ga 404 (ikke 403). PLUSS en full regresjonskjøring av to
eksisterende testsuiter fra tidligere runder denne uken (35-punkts
co-player-flyt, 122-punkts spillformat-flyt) — begge 100 % grønne,
bekrefter at ekstraheringen av de fire delte funksjonene ikke endret
noen eksisterende oppførsel.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
isolert browser-kontekst for en helt anonym tredje "bruker" ved siden
av to ekte innloggede faner): (1) synlighetsvelgeren i opprett-runde-
veiviseren fungerte som designet (tre knapper, kategori-multiselect
dukker kun opp ved "Venner", advarselstekst ved 0 valgte kategorier) —
runde opprettet med `friends`+`golf_friends`, bekreftet via API;
(2) rediger-runde-panelet viste korrekt FORHÅNDSUTFYLT synlighet,
PATCH til `public` bekreftet lagret; (3) en HELT ANONYM
nettleserkontekst (ingen cookies i det hele tatt) kunne se den
offentlige runden direkte på `/watch/{id}` (live-puls, leaderboard,
riktig eiernavn) OG via `/my-friends/{eier-id}`-profilsiden (navn/
avatar/HCP/hjemmeklubb + rundeliste med lenke inn); (4) en ekte andre
bruker sendte venneforespørsel, eieren aksepterte og kategoriserte
vedkommende som "Golfvenner" VIA DEN FAKTISKE UI-EN (ikke bare API) —
satte deretter runden til `friends`+`golf_friends`, bekreftet vennen
fikk tilgang UMIDDELBART via profilsiden; (5) **negativ kontroll,
samme venn**: byttet runden til `friends`+`close_family` (en kategori
vennen IKKE var satt i) — profilsiden viste korrekt INGEN runder
lenger, og et direkte `/watch/{id}`-forsøk ga en tydelig "Du har ikke
tilgang"-melding (403), atskilt fra en egen "Denne runden finnes
ikke"-melding for en ukjent id (404) — begge bekreftet med ekte
skjermbilder. Ekte typesjekket + full produksjonsbuild (26 ruter, inkl.
de to nye `/my-friends/[id]` og `/watch/[id]`) kjørt før utrulling.
**Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
eksplisitt (viste frem full plan for migrasjon FØR den kjørte, per
CLAUDE.md sin ufravikelige regel): migrasjon 032 kjørt mot ekte
`teecup_db` (kun additivt — ny kolonne med default, ny tabell, ingen
eksisterende rader rørt), `test_isolation.sql` fortsatt 12/12,
deretter `docker compose up -d --build teecup_api teecup_frontend`.
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket. Verifisert presist at de nye rutene faktisk når FastAPI
(ikke bare at Next.js svarte): et ukjent person-id mot
`/public/people/{id}/rounds` over ekte https ga korrekt JSON-formet
`404 NOT_FOUND` (ikke en rå Next.js-404-side).
**ADR-036 er dermed HELT ferdig, alle tre faser** (venner-kjernen,
rundevisibilitet, ekte medspillere) — backend + frontend, live.
- **Rundevarsler koblet til det eksisterende varslingssenteret, BYGGET,
SCRATCH-VERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging
av ADR-036 fase 2 — varslingssenteret (2026-07-26) hadde fra start
reservert `type` for `"round"`/`"result"` (skjema OG frontendens
`KIND_ICON`/`KIND_LABEL`), men INGENTING skrev noensinne en slik rad —
kun venneforespørsler trigget et varsel. Bruker bekreftet omfang
eksplisitt (AskUserQuestion, alle tre valgt): (A) lagt til som ekte
medspiller på en runde, (B) en venn starter en runde du kan se
(offentlig, ELLER `friends`-synlig med treffende kategori), (C) en
runde du er koblet til (eier/medspiller/tredjeparts-venn fra B) blir
fullført.
**Ren backend-endring, INGEN frontend-endring nødvendig** — bekreftet
ved lesing av `notifications.tsx` FØR noe ble bygget: `NotificationKind`/
`KIND_ICON`/`KIND_LABEL` dekker allerede `"round"`/`"result"` fullt ut,
siden de ble reservert med akkurat dette for øye i den opprinnelige
runden.
**Bygget i `app/routers/rounds.py`:** ny delt `_friends_who_can_see_
round(conn, round_id, owner_user_id)` — GJENBEREGNER (ikke en lagret
mottakerliste) nøyaktig samme regel som `_can_view_round` (offentlig =
alle aksepterte venner, `friends` = kun de hvis EGEN kategorisering av
venn treffer rundens synlige kategorier), brukt BÅDE ved opprettelse og
ved fullføring — aldri ute av synk med selve tilgangskontrollen.
`create_round` sender (B) rett etter transaksjonen (kun for
`public`/`friends`, aldri `private`), lenke til `/watch/{id}`.
`add_participant` sender (A) inni transaksjonen når `user_id` er satt
(aldri for gjester — de har ingen konto å varsle), lenke til
`/my-rounds/{id}` (full tilgang, ikke tredjeparts-visningen).
`complete_round` sender (C) til to ATSKILTE mottakergrupper med ulik
lenke, siden de har ulik tilgang: lenkede medspillere (unntatt den som
selv fullførte) → `/my-rounds/{id}`; tredjeparts-venner fra (B),
gjenberegnet på nytt → `/watch/{id}` — ingen dobbel-varsling hvis noen
skulle være i begge grupper (settoperasjon), og aldri et varsel til
personen som selv utførte handlingen.
**Scratch-verifisert grundig, 28/28 nye sjekker** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container, samme mønster som hele prosjektet, alle 32 migrasjoner kjørt
friskt): venn fikk `round`-varsel ved synlig opprettelse, fremmed fikk
det IKKE, privat runde ga INGEN varsel til noen, medspiller fikk
`round`-varsel med riktig `/my-rounds/`-lenke ved tilføyelse, fullføring
ga `result`-varsel til BÅDE medspiller (`/my-rounds/`) og venn
(`/watch/`) men ALDRI til den som selv fullførte, en privat solorunde
sin fullføring ga fortsatt ingen varsler, mark-as-read uendret. PLUSS
full regresjon av tre eksisterende testsuiter (co-player-flyt 35/35,
spillformat-flyt 122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle
fortsatt 100 % grønne, ingen utilsiktet bivirkning av de nye
`create_notification()`-kallene inni eksisterende transaksjoner.
`test_isolation.sql` 12/12 uendret (ingen migrasjon, ren Python-logikk).
**Rullet ut live 2026-07-28**, ingen migrasjon, kun
`docker compose up -d --build teecup_api`. Ren boot
(`Application startup complete`), `/health`/`/dashboard` → 200,
`teeoff.no` upåvirket.
- **E-post-fallback for varsler, per type, BYGGET OG SCRATCH-VERIFISERT
(2026-07-28), samme dag, rett etter rundevarsel-runden:** brukeren ba
eksplisitt om at mottakeren selv skal kunne velge HVILKE varseltyper som
skal utløse e-post — ikke en enkelt global av/på-bryter. Ny migrasjon
`033_notification_email_prefs.sql`: `user_notification_email_pref`
(`user_id`, `type`, samme CHECK-sett som `notification.type`) — samme
"tilstedeværelse = valgt"-mønster som `round_visible_category`/
`friend_categorization`, TRYGG STANDARD ingen rad = ingen e-post for
noen type (samme "se ingenting til noen har valgt"-filosofi som resten
av appen).
**Bevisst ÉN felles innsnevring, ikke ett kallsted per varseltrigger:**
e-post-utsendingen ligger INNI `create_notification()` selv (etter selve
INSERT-en) — modul-docstringen kalte den allerede "den eneste
skrivevegen inn", så alle nåværende OG fremtidige varsel-triggere (i dag
2 i friends.py, 4 i rounds.py) får e-post-støtte helt uten å røres,
ingen risiko for at et fremtidig kallsted glemmer det. Samme
`SMTP_CONFIGURED`-sjekk + try/except + `traceback.print_exc()`-mønster
som all annen e-postutsending i appen (routers/auth.py,
organizations.py) — en driftsfeil i selve SMTP-en skal ALDRI hindre at
in-app-varselet (allerede skrevet FØR e-post-forsøket) består.
Ny `send_notification_email()` i `app/email.py` — bevisst tospråklig KUN
i ramme-teksten (emne/hilsen/lenkeforklaring); selve `message`-teksten
er allerede en ferdig norsk snapshot-tekst (samme prinsipp som
in-app-varselet), ingen full i18n av selve varselinnholdet i denne
runden.
Nye `GET`/`PUT /notifications/email-prefs` — PUT er en FULL erstatning
(samme kontrakt som `PUT /friends/{id}/categories`), ikke en delvis
PATCH.
**Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på:**
forrige rundes `add_participant`-varsel (medspiller lagt til) lå INNI
den åpne DB-transaksjonen for selve deltaker-innsettingen — flyttet til
RETT ETTER (samme mønster som `create_round`/`complete_round` allerede
fulgte), slik at en fremtidig blokkerende SMTP-utsending aldri skjer
mens en transaksjon holder låser.
**Frontend:** ny seksjon "Varsler på e-post" i `/account`
(`NotificationEmailPrefsSection`, `account-settings.tsx`) — fire
avkrysningsbokser (Venneforespørsler/Runder/Resultater/Turneringer, sistnevnte
reservert for fremtidig bruk), samme avkrysningsboks-mønster som
kølle-bag-listen lenger opp i samme fil, lagrer umiddelbart ved hvert
klikk (ingen egen "lagre"-knapp).
**Scratch-verifisert grundig, 19/19 nye sjekker** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container, alle 33 migrasjoner kjørt friskt): trygg standard bekreftet
(ingen typer valgt fra start), full-erstatning bekreftet (PUT med ett
sett fjerner det forrige, legger ikke til), e-post-fallback FAKTISK
logget (dev-log-varianten av `SMTP_CONFIGURED`-grenen) for en bruker med
`round`/`result` valgt inn — BÅDE ved medspiller-tilføyelse og ved
fullføring — INGEN e-post til brukeren som selv utførte handlingen,
INGEN e-post til en bruker (eieren) som ikke har valgt inn NOE, og etter
å ha slått AV `result` igjen: ingen ny e-post ved neste fullføring MENS
in-app-varselet fortsatt opprettes helt uendret (de to er reelt
atskilte, ikke koblet). PLUSS full regresjon av fire eksisterende
testsuiter (rundevarsler 28/28, co-player-flyt 35/35, spillformat-flyt
122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle fortsatt 100 %
grønne, ingen bivirkning av at `add_participant`s varsel flyttet utenfor
transaksjonen. `test_isolation.sql` 12/12 uendret. Ekte typesjekket
produksjonsbuild (samme `Dockerfile` som deployes) kompilerte rent,
alle 24 ruter listet uendret.
- **Tre organisator-oppfølgingspunkter fra FEATURE_BACKLOG.md, ALLE
BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:**
brukeren ba om at alle tre gjenstående, godt avgrensede oppfølgings-
punkter fra en tidligere gjennomgang av `.md`-filene tas i én runde,
inkludert oppdatering av de relevante backlog-/beslutningsfilene.
1. **Flytte spiller mellom lag:** ny
`POST /orgs/{id}/teams/{team_id}/roster/{roster_id}/move`
(`{"target_team_id": ...}`, `app/routers/tournaments.py`) —
atomisk `UPDATE team_roster SET team_id = ...`, avviser 409
`ALREADY_IN_MATCH` hvis spilleren allerede er lagt til i en match
(roster-raden er referert av `match_participant.team_roster_id`
med `ON DELETE RESTRICT` -- en flytting ville da gjort matchens
`team_side` inkonsistent med spillerens faktiske lag), 400 ved
flytting til samme lag, 404 ved ukjent mållag/roster-id.
Kapteinmerket nullstilles eksplisitt ved flytting (følger ikke med
til det nye laget). Frontend: ny "Flytt til {annet lag}"-handling i
`TeamPanel` sin per-spiller-meny (`tournament-detail.tsx`) — v1s
to-lags-grense (ADR-011) gjør målet entydig, ingen dropdown
nødvendig. Ingen migrasjon.
2. **Individuell rangering PER ØKT i en org-turnering:** ny
`GET /orgs/{id}/sessions/{id}/individual-leaderboard` — bevisst
AVGRENSET til én økt (ikke summert på tvers av turneringen, samme
avklaring som rundeleaderboardets omfang tidligere), og KUN
meningsfull for `scoring_mode='stroke'`-økter i individuell-ball-
format (singles/fourball; delt-ball-formater og hole_result-modus
avvist med 400, ikke krasj, siden de ikke har noen individuell
brutto-score å rangere fra). Gjenbruker `mp.playing_handicap`
(allerede beregnet, allowance-justert) + `allocate_over_played_
holes` (samme mønster som eksisterende match-play-scoring i
`scoring.py`, som fikk sin `_played_hole_numbers` omdøpt til
`played_hole_numbers` -- "fjern understrek når et andre bruksted
dukker opp"-mønsteret, samme som `SIDE_IS_UNIT` tidligere) — ingen
ny regnelogikk. Respekterer samme reveal-gating (`locked_team_ids`/
`own_team_ids`, ADR-013/026) som den eksisterende matchlisten.
Frontend: ny side `/tournaments/[id]/sessions/[sessionId]/
individual-leaderboard` (`session-individual-leaderboard.tsx`,
egen enklere lokal variant av `round-leaderboard.tsx` sitt
rangerings-/mode-toggle-mønster -- brutto/netto/poeng, delt
plassering "T-N"), lenket fra blind draw-skjermen for
kvalifiserende økter. Ingen migrasjon.
3. **Midlertidige spillere + automatisk etter-runde-invitasjon
(økt-nivå):** de tre tidligere åpne spørsmålene avklart eksplisitt
(AskUserQuestion) -- nivå ØKT (ikke turnering), dobbel-utsending-
sperre JA, locale bevisst alltid `nb`. Ny migrasjon
`034_session_scorecard_invitations.sql`
(`match_participant.invitation_sent_at`, samme "tidsstempel =
skjedd"-mønster som `round.started_at` m.fl.). Ny
`POST /orgs/{id}/sessions/{id}/send-scorecard-invitations` —
sender KUN til spillere uten konto (`player.user_id IS NULL`) OG
med registrert e-post OG uten en tidligere sendt invitasjon for
akkurat denne (økt, deltaker)-kombinasjonen; responsen skiller
`sent`/`skipped_has_account`/`skipped_no_email`/
`skipped_already_sent` for full gjennomsiktighet. Gjenbruker
SAMME magic-link-token-mekanisme som vanlig innlogging (ikke bare
en "logg inn senere"-henvisning) -- ny `send_session_result_email()`
i `app/email.py` (begge nb/en-maler klare, kun `nb` faktisk brukt).
E-postens innhold: matchresultat (`status_text`) + individuelt
slagtotal når tilgjengelig (individuell-ball-formater -- `SUM
gross_strokes` er naturlig `NULL` for delt-ball-formater uten noen
egen format-sjekk, siden `match_participant_id` aldri settes i
`hole_score` der). Frontend: ny "Send scorekort til alle med
e-post"-knapp i blind draw-skjermen (`session-blind-draw.tsx`),
synlig når økten har minst én match.
**Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på**
(delt med forrige rundes rundevarsel-mønster): dev-log-grenen for den
nye invitasjons-e-posten skrev først KUN en oppsummeringstekst, ikke
selve token-en -- umulig å teste innloggingslenken i et scratch-/dev-
miljø uten SMTP. Rettet til å skrive to linjer: én i SAMME format som
`request_magic_link` sin etablerte `[DEV] Magic link for ...`-linje
(så eksisterende dev-verktøy som harvester token derfra fungerer
uendret), pluss en egen lesbar oppsummeringslinje.
**Scratch-verifisert grundig, 54/54 nye sjekker** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
container, alle 34 migrasjoner kjørt friskt, full org-turnering-
scaffold bygget fra bunnen via API -- org/bane/18 hull/tee/turnering/
to lag/seks spillere/roster/singles-stroke-økt/to matcher): flytting
happy-path + kaptein-nullstilling + alle tre feilveier (409/400/404)
bekreftet, individuell rangering bekreftet tom→fylt→korrekt brutto/
netto/poeng for et hånd-utregnet 4-spiller-scenario (kryssjekket at
netto-til-par alltid ≤ brutto-til-par for alle fire), begge ikke-
kvalifiserende økt-typer (hole_result, foursome) avvist rent,
invitasjons-utsending bekreftet presist (1 sendt/2 manglet e-post/1
hadde allerede konto via en EKTE innlogget "linket" bruker), andre
kall bekreftet idempotent (0 nye, riktig `skipped_already_sent`), OG
den utstedte lenken bekreftet FAKTISK brukbar (spilleren logget inn
med den, kontoen ble koblet til spiller-profilen, samme ADR-017-
mekanisme uendret). PLUSS regresjon av scoring.py-omdøpingen
(`test_holeless_course_crash.py`, 5/5) og to notifikasjons-/e-post-
testsuiter fra forrige runde (28/28, 19/19) — alle fortsatt grønne.
`test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild
kompilerte rent, ny rute `/tournaments/[id]/sessions/[sessionId]/
individual-leaderboard` listet.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
isolert scratch-backend, ekte innlogging inkl. tvungen 2FA-oppsett for
en fersk organisasjonseier): flyttet en spiller mellom lag direkte i
UI-et og bekreftet begge lags roster-lister/-antall oppdatert
umiddelbart; åpnet individuell rangering og bekreftet alle tre
visningsmodiene (Brutto/Netto/Poeng) viste korrekte, ulike tall for de
samme to spillerne; trykket "Send scorekort til alle med e-post" og
bekreftet resultatteksten "1 invitasjon sendt, 1 mangler registrert
e-post" -- trykket samme knapp igjen og bekreftet "0 invitasjoner
sendt, 1 allerede sendt tidligere, …" (dobbel-sperren synlig direkte i
UI-et, ikke bare i et API-svar). Ingen konsollfeil i noen av de tre
rundene.
**Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
eksplisitt: migrasjon 034 kjørt mot ekte `teecup_db`
(`invitation_sent_at`-kolonnen bekreftet, `test_isolation.sql`
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/
`/dashboard` → 200, `teeoff.no` upåvirket.
**Samtidig, ren dokumentasjonshygiene:** et par steder i
`FEATURE_BACKLOG.md`/`ARCHITECTURE_DECISIONS.md` hadde blitt hengende
etter `CLAUDE.md` (ADR-039-utrulling/frontend og PWA-offline-
browsertesting fremstod fortsatt som "ikke gjort" til tross for at
begge var fullført i tidligere økter denne uken) — rettet til å
stemme med den faktiske, allerede leverte tilstanden.
- **Fire gjenstående forslag fra en tidligere "hva nå?"-runde, ALLE FIRE
BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28, samme dag:**
brukeren ba eksplisitt om å ferdigstille alle fire samtidig
(PWA-installasjon, flere flighter i frittstående runder, scramble/
greensome-statistikk, push-varsler til telefonens OS) — se
FEATURE_BACKLOG.md for full detalj per punkt, kort oppsummert her.
Tre nye migrasjoner (`035_round_flight_group.sql`,
`036_round_hole_selected_participant.sql`, `037_push_subscriptions.sql`),
alle rent additive.
1. **PWA-installasjonsoppfordring** — `lib/pwa-install.ts` (globalt
fanget `beforeinstallprompt`, mountet via `sw-register.tsx` på alle
sider siden eventet kun fyres én gang per side-liv) + ny
`components/install-prompt.tsx`, vist på dashbordet rett under
hilsenen. Android/Chrome-familien får en ekte "Installer"-knapp,
iOS Safari et instruksjonsbanner (ingen programmatisk vei finnes
der), andre nettlesere uten reell installasjonsvei viser ingenting.
2. **Flere flighter i én frittstående runde** (retning 1, løs
gruppering av separate `round`-rader bundet sammen av en klient-
generert delt UUID, `round.flight_group_id`) — ny
`GET /rounds/{id}/flight-group` + `GET .../flight-group/leaderboard`
(slår sammen hver tilgjengelig søsken-flights EGET leaderboard til
én rangert liste, `score_to_par` allerede normalisert og dermed
sammenlignbart på tvers av baner). Ny `FlightGroupPanel` i
`round-detail.tsx` ("+ Legg til en flight til" gjenbruker HELE
opprett-runde-flyten, forhåndsutfylt via URL-parametre — banen
velges på nytt per flight, bevisst), ny side
`/my-rounds/[id]/flights`.
3. **Scramble/greensome: valgt utslag per spiller** — avgrenset til
frittstående runder (ADR-039 sitt delt-ball-format), IKKE
org-scopede turneringer i denne runden (bevisst scope-kutt, se
FEATURE_BACKLOG.md). Ny `round_hole.selected_participant_id`,
`PATCH .../sides/{id}/holes/{n}` fikk et nytt valgfritt felt (samme
"full overwrite hvert kall"-kontrakt som `played`/`score`).
`SideScoreWizard` fikk en ny valgfri "Hvem sitt utslag ble brukt?"-
seksjon (auto-hopp bevisst slått av her, ellers ville brukeren blitt
revet videre før valget kunne gjøres); `round-stats.tsx` fikk en ny
"Utslag brukt"-oppsummering per side.
4. **Push-varsler til telefonens OS** (Web Push/VAPID) — ny
`push_subscription`-tabell, nytt `app/push.py`
(`send_push_to_user`, `pywebpush`), kalt fra `create_notification()`
for ALLE fire varseltyper. Bevisst INGEN egen per-type opt-in
(ulikt e-post-fallbacken) — å abonnere ER samtykket. VAPID-nøkler
valgfrie (`PUSH_CONFIGURED`), samme grasiøs-degraderings-mønster som
SMTP. `public/sw.js` fikk `push`/`notificationclick`-håndtering, ny
`lib/push-subscribe.ts` + seksjon i `/account`
(`PushNotificationSection`).
**VAPID-nøkkelpar generert lokalt** (Python `cryptography`, EC P-256,
rå base64url-kodet privat/offentlig nøkkel — samme format `pywebpush`
og nettleserens `applicationServerKey` forventer), ALDRI vist i
klartekst i chatten (skrevet direkte til en midlertidig fil med 600-
rettigheter, lest inn i ekte `.env` ved utrulling, slettet fra
scratchpad etterpå) — samme regel som alle andre hemmeligheter i
prosjektet.
**Scratch-verifisert grundig, 27/27 sjekker i ett delt testløp**
(isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
API-container med `pywebpush` installert, alle 37 migrasjoner kjørt
friskt): flight-group happy-path + ugyldig UUID avvist + kryss-bruker-
isolasjon (403/404, ingen lekkasje) + slått-sammen leaderboard rangerer
korrekt på tvers av to flighter; scramble-valg PATCH lykkes + feil side
avvist (400) + eksplisitt null nullstiller; VAPID-nøkkel eksponert +
abonnement lagret + anonymt abonnement avvist (401) + en EKTE
`webpush()`-utsendelse forsøkt mot en syntaktisk ugyldig test-nøkkel
(`WebPushException` fanget og logget, in-app-varselet ble uansett
opprettet normalt — beviser feil-isolasjonen fungerer i praksis, ikke
bare i teorien). `test_isolation.sql` 12/12 uendret.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
isolert scratch-backend, ekte innlogging): PWA-knappen rendret og var
klikkbar (Chrome fyrte faktisk `beforeinstallprompt` i testøkten), ingen
konsollfeil; hele "+ Legg til en flight til"-flyten klikket gjennom FRA
KNAPPETRYKK TIL FERDIG RUNDE (riktig forhåndsutfylt skjema, korrekt
`flightGroupId` i URL-en, DIREKTE DATABASE-bekreftelse at begge rundene
delte samme gruppe-id etterpå), kombinert leaderboard-siden bekreftet
visuelt med korrekt rangering; scramble-valget satt i veiviseren,
bekreftet DIREKTE I DATABASEN, OG "Utslag brukt"-oppsummeringen
bekreftet visuelt på statistikksiden; push-UI-et rendret korrekt og
håndterte avslått tillatelse med en forklarende tekst uten konsollfeil
(denne automatiserte nettleserøkten hadde `Notification.permission`
forhåndssatt til "denied" av selve miljøet — en ekte innvilget
tillatelse → ekte levert OS-varsel er derfor IKKE bevist, ærlig
begrensning, bruker bør selv teste dette på en ekte enhet før full
tillit). Ekte typesjekket produksjonsbuild (`docker build --target
builder`) kompilerte rent, alle 27 ruter listet inkl. den nye
`/my-rounds/[id]/flights`.
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt (plan vist
FØR kjøring, per CLAUDE.md sin ufravikelige regel): migrasjon 035-037
kjørt mot ekte `teecup_db` (alle tre bekreftet med direkte spørring,
`test_isolation.sql` fortsatt 12/12), VAPID-nøkler lagt inn i ekte
`.env`, `docker compose up -d --build teecup_api teecup_frontend`.
Begge containere boot-et rent, `/health`/`/dashboard` → 200,
`GET /push/vapid-public-key` over ekte https ga korrekt offentlig
nøkkel, `GET /rounds/{ukjent-id}/flight-group` ga korrekt
`401 NOT_AUTHENTICATED` (ikke en rå 404 — bekrefter ruten faktisk når
FastAPI), `teeoff.no` upåvirket.
- **Designinstruksen erstattet/formalisert + seks brukerpunkter, ALLE
BYGGET OG LIVE (2026-07-29):** `alternativ designinstruks.md` (fra
dagen før) erstattet med en revidert versjon fra bruker (allerede
forankret i det eksisterende token-systemet, ikke lenger i konflikt med
oransje/hardkodet-slate-spørsmålene) — INNHOLDET er nå slått sammen inn
i `DESIGN_SYSTEM.md` som den gjeldende fasiten (8-punkts rutenett,
`shadow-md shadow-black/8`, `font-normal` OK ved `text-base`+god
kontrast, `:active`-tilstander, safe-area, 16px input, `transition-all
duration-200`). `alternativ designinstruks.md` selv beholdt kun som
arkiv/historikk. **Konsistens-sveip:** det gamle svake skygge-mønsteret
(`shadow-sm shadow-black/5`) erstattet mekanisk på tvers av 20 filer
(`sed`, verifisert 0 gjenværende treff) — IKKE en fullstendig
strukturell gjennomgang av alle 27 skjermer, kun dette ene, trygge,
mekaniske mønsteret.
**Hurtighandlinger:** dashbordets tre knapper er nå `grid-cols-3` også
på mobil (var `grid-cols-1 sm:grid-cols-3`, tok unødvendig mye plass) —
mindre ikon/tekst/høyde for å fortsatt være lesbare i smalere kolonner.
**Par/stroke-index manglet på rundeleaderboardet** (`/my-rounds/{id}/
leaderboard`) — ny `HoleReferenceStrip` viser Hull/Par/Hcp ÉN gang
(banedata, identisk for alle deltakere) rett over den rangerte listen.
**Venner: kategorisering nå OBLIGATORISK fra vennskapet inngås**
(presisert av bruker: "det skal ikke finnes ukategoriserte venner") —
`POST /friends` og `POST /friends/{id}/accept` krever begge minst én
kategori (`Field(min_length=1)`), satt AV BEGGE PARTER på hvert sitt
naturlige tidspunkt (avsender ved sending, mottaker ved aksept) — ikke
en frivillig senere handling. `PUT .../categories` nekter også å sette
et tomt sett. Ny delt `CategoryPicker`-komponent (friends.tsx, alle
forhåndsvalgt — "man må heller velge bort", presisert av bruker) brukt
tre steder (send/godta/rediger), nekter å fjerne SISTE avkrysning.
Selv-helbredende sikkerhetsnett for venner fra FØR denne regelen: ny
advarselsbanner øverst i `/my-friends` ("N venner mangler kategori"),
tvinger vedkommendes kategori-panel åpent til det er løst — bekreftet
reelt nødvendig i produksjon (Erol hadde aldri kategorisert Tore
Morell, sin faste matchspill-motstander — vil nå bli fanget opp og
tvunget løst neste gang `/my-friends` besøkes).
**Synlighetsregelen for "Venner"-runder endret fra "minst én treffende
kategori" til "ALLE vennens kategorier må være i det synlige settet"**
(presisert av bruker: "'ikke vise' overstyrer 'vise'") — en venn med
ÉN ikke-valgt kategori ekskluderes nå helt, selv om en annen av
kategoriene deres er valgt. En venn med NULL kategorier vises ALDRI
(avklart eksplisitt med bruker via spørsmål — skal i praksis aldri
forekomme lenger pga. regelen over, kun en igjenværende tilstand fra
FØR den ble håndhevet). `_can_view_round`/`_friends_who_can_see_round`
(rounds.py) omskrevet til denne AND-semantikken (fra tidligere OR).
`new-round.tsx` sin kategori-multiselect for "Venner"-synlighet starter
nå med ALLE forhåndsvalgt (samme "velg heller bort"-prinsipp).
**"Match leaderboard" — undersøkt grundig, IKKE en backend-bug:**
bekreftet direkte mot ekte `teecup_db` (kun lesing) at brukerens egen
Tjøme-matchrunde (`d857289c-...`) hadde begge sider korrekt satt opp
med beregnet `playing_handicap` — `format-result`-endepunktet ville
altså allerede regnet ut riktig "1 UP (A)"-status. Det reelle hullet
var at `FormatResultPanel` (matchstatus/skins-tavle, med hull-for-hull
vinner/AS-visning) KUN lå under "Spillere og runde"-fanen, usynlig fra
"Score"-fanen der scoring naturlig skjer. Fikset ved å vise samme
komponent (ikke duplisert logikk) øverst på BEGGE faner.
**Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API-
container, samme mønster som hele prosjektet): full kategori-håndheving
(422 uten kategorier ved både send/godta/rediger, 200 med), presis
eksklusjon-overstyrer-inklusjon-test (venn med golf_friends+close_family
ekskludert når kun golf_friends er synlig, inkludert når begge er
synlige — bekreftet mot BÅDE rå SQL og de faktiske API-endepunktene),
match-runde med sider satt opp fra bunnen ga korrekt "1 UP (A)".
Deretter en FULL, ekte nettleser-gjennomgang (Chrome DevTools, mobil
390×844): 3-kolonners hurtighandlinger, matchstatus-banner synlig på
Score-fanen, Hull/Par/Hcp-referanserad på leaderboardet, hele venne-
søk→kategorivelger(alle forhåndsvalgt, avkrysning fungerer)→send-
flyten, og selv-helbredende-banner-flyten (simulerte en gammel
ukategorisert venn direkte i databasen, bekreftet banneret dukket opp
OG forsvant igjen etter kategorisering via UI-et).
**Rullet ut live 2026-07-29**, ingen migrasjon (kun eksisterende felt/
logikk endret), `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, anonymt `POST /friends` ga korrekt `401` (ikke en rå 404 —
bekrefter ruten når FastAPI), `teeoff.no` upåvirket.
- **Match-scorekort redesignet på tvers av appen (frittstående runde +
turnering), BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
(2026-07-29):** brukeren delte et referansebilde av en konkurrentapp sitt
1v1-matchscorekort (horisontalt rutenett, farget etter hvem som vant
hvert hull, løpende "Stilling"-rad AS/X UP/X&Y, navn+HCP-banner) og ba om
det tilsvarende for ALLE match-scorekort i TeeCup, uten direkte plagiat.
**Bevisst IKKE en kopi:** beholdt appens egne, allerede etablerte
fargespråk i stedet for referansens røde/blå -- frittstående runder bruker
primær/oransje (samme "side A/side B"-konvensjon som `ResultChip`/
`FormatResultPanel` fra tidligere runder), turnering-matcher bruker lagets
faktiske `team.color` (samme konvensjon som resten av turnering-UI-et).
Droppet bevisst referansens "Matchplay NET/Stableford NET"-fane (ga ikke
entydig mening for delt-ball-formater) og "Lik/Kommentar til
spillfeeden/Spillere"-ikonraden (en helt ny sosial funksjon, ikke en
scorekort-redesign -- utenfor denne rundens omfang).
**Kjernemekanisme, ny og delt idé (separat implementert i begge filer,
ikke faktisk delt kode siden filene allerede har egne lokale typer per
etablert konvensjon):** `computeRunning()` speiler
`handicap_engine.py` sin `compute_match_state()`/`describe()` presist,
men regnet ETT PREFIKS om gangen client-side (backend cacher i dag kun
SLUTT-tilstanden) -- gir en løpende AS/X UP/dormie/X&Y-status per hull i
stedet for kun et sluttresultat.
**`round-scorecard.tsx`:** den generiske `ScoreBlock`-tabellen erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/
liste) erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/
Par/en rad per spiller-ELLER-side/Stilling), identitetsbanner (navn+HCP,
farget dot, løpende sentral status + "Ferdig"-merke når avgjort). Fargen
på scorecellen følger HVEM SOM VANT hullet (fylt sirkel), ikke over/under
par -- egen semantikk fra `ScoreMark` med vilje, siden dette er en match,
ikke en individuell runde. `ApiParticipant` fikk `playing_handicap`
(allerede eksponert av backend, kun en frontend-typeutvidelse).
**`session-scorecard.tsx`:** `HoleSummaryTable`/`OutcomeBadge`/
`grossPairLabel` erstattet med `TournamentMatchGrid` (samme struktur,
brukt som HOVEDVISNING for pågående matcher, og i `DecidedView`s "Hull
for hull" -- én komponent, ikke to). `Unit`-typen fikk `playingHandicap`.
**Ekte, nødvendig backend-endring:** `match_participant.playing_handicap`
ble beregnet og lagret siden ADR-039, men var ALDRI eksponert i
`MatchParticipantOut` (matches.py) -- lagt til i modellen og i begge
SELECT-spørringene som populerer den (`fetch_matches` og
`add_participant`). **Reelt funn, fikset FØR utrulling:**
`add_participant` sin `row` hentes FØR
`compute_and_store_side_handicaps()` kjører -- en naiv
`MatchParticipantOut(**dict(row))` ville derfor alltid returnert
`playing_handicap: null` for en NYLIG lagt til deltaker, selv når verdien
faktisk ble beregnet et øyeblikk senere i samme kall. Fikset med et
eksplisitt re-oppslag av `playing_handicap` RETT FØR responsen bygges.
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
friskt, samme mønster som resten av prosjektet): en full match bygget fra
bunnen for BEGGE surfacene (en frittstående match-runde med to sider, OG
en full turnering-scaffold -- org/bane-import/tee/tournament/to lag/
roster/singel-økt/match/deltakere -- bygget fra API-et for FØRSTE gang i
et testskript denne uken, siden frittstående runder aldri har trengt det
apparatet før). 8-9 hull registrert med et bevisst blandet vinn/tap/delt-
mønster, bekreftet at `format-result`/`scorecard` sine per-hull
resultater matchet forventet handicap-justert utfall.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP):
et REELT, tidkrevende miljøproblem ble diagnostisert og løst underveis --
React-komponentenes egne `useEffect`-datahentinger kjørte ALDRI (evig
lastespinner på HVER side, ikke bare de nye) når dev-serveren ble besøkt
via `127.0.0.1:3100` i stedet for `localhost:3100`; Next.js 16 sin
`allowedDevOrigins`-beskyttelse blokkerer stille dev-ressurser (HMR-
websocket m.m.) for det som oppfattes som et fremmed opphav, og dette
fikk hele klient-hydreringen til å henge uten en eneste konsollfeil.
Løst ved å konsekvent bruke `localhost` i stedet for `127.0.0.1` --
ren miljø-lærdom for fremtidige scratch-frontend-økter, ikke en bug i
selve appen. Etter fiksen: bekreftet BEGGE match-scorekortene visuelt,
i BÅDE lys og mørk modus, i BÅDE pågående (løpende "2 UP"/farget
Stilling-rad) og avgjort tilstand ("8 UP"/"Ferdig"-merke, cachet
banner), inkl. et helt nytt 2FA-oppsett fullført på fersk konto via en
engangs e-post-2FA-kode lest fra dev-loggen (org-eier/admin krever 2FA,
ADR-021) for å nå frem til turnering-siden. Ingen konsollfeil i noen av
rundene.
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
→ 200 upåvirket.
- **Match-scorekort-redesignet også på selve LIVE scoringssiden
(`/my-rounds/{id}` sin Score-fane), BYGGET, GRUNDIG SCRATCH-/
BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag rett etter forrige
punkt:** brukeren viste tre skjermbilder (referansen på nytt, samt et
mobil- OG et PC-skjermbilde av "hvordan du har løst det nå") og spurte
direkte hvorfor det ikke så riktig ut ennå. **Presis diagnose FØR noe ble
bygget:** PC-skjermbildets URL (`teecup.teeoff.no/my-rounds/{id}`, uten
`/scorecard`) avslørte at forrige runde kun traff `round-scorecard.tsx`
(en egen, separat LESE-visning) og `session-scorecard.tsx` (turnering) --
aldri `round-detail.tsx` sin egen, ELDRE `FormatResultPanel`+
`ScorecardGrid`/`SideScorecardGrid`, som er det brukeren faktisk ser og
bruker under selve scoringen. `FormatResultPanel` viste dessuten en
reell, synlig svakhet: falt tilbake til det generiske "Side B" i stedet
for spillerens faktiske navn når siden ikke hadde et eget satt navn.
**Bygget:** `ApiFormatResult` utvidet med samme `holes`-per-hull-
oppløsning som round-scorecard.tsx allerede har (ingen backend-endring
-- feltet fantes allerede i API-svaret, bare ikke lest her). Selve
fetch-en LØFTET fra `FormatResultPanel` opp til hovedkomponenten (`Round
Detail`) som ny delt `formatResult`-state, siden BÅDE banneret OG de to
scorekort-gridene nå trenger den samme dataen (én henting, ikke to).
`FormatResultPanel` gjort om til en ren presentasjonskomponent (tar
`result` som prop) med et nytt identitetsbanner (navn+HCP, farget prikk,
stor sentrert løpende status, "Ferdig"-merke) -- samme visuelle språk
som `MatchScorecardGrid`/`TournamentMatchGrid` fra forrige runde, egen
lokal kopi per prosjektets "ett sted, én fil"-konvensjon. Den tidligere
hull-for-hull-sirkel-listen under banneret FJERNET (overflødig nå som
selve gridet under viser dette per hull).
`ScorecardGrid` (individuell-ball: match/fourball) og `SideScorecardGrid`
(delt-ball: foursome/greensome/scramble) fikk begge: en farget prikk ved
siden av spiller-/side-navnet (grønn=side A, oransje=side B), en ny
`MatchScorecardCell` som farger scorecellen etter HVEM SOM VANT hullet
(fylt sirkel) i stedet for over/under par -- men KUN når formatet
faktisk er to-sidet (`isTwoSided`-prop, gate på `TWO_SIDED_FORMATS`) --
slagspill og skins beholder uendret over/under-par-fargelegging siden de
ikke har noe "vant hullet"-konsept. Fourball sin ikke-tellende
partner-score tones ned (samme "laveste netto teller"-nedtoning som
round-scorecard.tsx). Ny "Stilling"-rad nederst i BEGGE grid, regnet med
en lokal `matchStillingByHole()` (samme prefiks-vise
`compute_match_state()`-speiling som forrige rundes `computeRunning()`,
egen kopi her siden denne trenger å være justert til `holeOrder`, ikke
bare en flat liste). Alt dette er BEVISST lagt OPPÅ den eksisterende
klikk-for-å-registrere-interaksjonen (ScoringWizard/SideScoreWizard)
uendret -- ingen endring i selve registreringsflyten, kun presentasjonen
av allerede registrerte hull.
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
friskt): tre nye runder bygget fra bunnen via API for å dekke alle tre
strukturelle grenene -- en MATCH-runde (Erol vs. en gjest, ulik HCP),
en FOURBALL-runde (2v2, individuell netto, bevisst konstruert med et
hull der "feil" partner sin lavere brutto ikke telte pga. handicap-
justering), og en FOURSOME-runde (delt ball). Ekte typesjekket
produksjonsbuild kompilerte rent.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
samme `localhost`-ikke-`127.0.0.1`-lærdom fra forrige runde anvendt fra
start denne gangen): alle tre rundene bekreftet visuelt -- identitets-
banneret viste ekte spillernavn+HCP (ikke lenger "Side B"), gridcellene
fargela nøyaktig riktig hull som vunnet (kryssjekket presist mot de
faktiske innsendte tallene og aria-labelene, inkl. et hull der en
tilsynelatende brutto-uavgjort (4-4) faktisk ble avgjort på netto pga.
et stort HCP-gap -- bekreftet korrekt, ikke en bug), fourball sin
nedtonede ikke-tellende partner-celle bekreftet nøyaktig på det
konstruerte hullet, Stilling-raden fulgte riktig gjennom AS/1 UP/2 UP-
mønsteret for alle tre rundene. Bekreftet at et klikk på en scorecelle
FORTSATT åpner riktig veiviser (`ScoringWizard`/`SideScoreWizard`)
uendret, i begge grid-typer. Kjørte i tillegg en ren REGRESJONSSJEKK
mot en vanlig `stroke`-formatert runde -- bekreftet ingen Stilling-rad,
ingen fargede prikker, ingen bytte av cellespråk (uendret over/under-
par-styling som før). Ingen konsollfeil i noen av rundene, i verken lys
eller mørk modus.
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (ba om fiksen
rett etter diagnosen): ingen migrasjon, `docker compose up -d --build
teecup_api teecup_frontend`. Begge containere boot-et rent,
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket. **Alle tre stedene
et match-scorekort vises i appen (frittstående runde sin live Score-
fane, frittstående runde sin egen lesevisning, turnering-match) bruker
nå samme konsistente visuelle språk.**
- **Dashbordets rundeboks: viewer-relativt matchresultat + netto til-par,
pluss mottatte slag vist FØR hullet fylles ut, BYGGET, SCRATCH-/
BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag:** brukeren ba om to
ting samtidig -- at rundeboksen (`round-card.tsx`, brukt av dashbordet,
"Egne runder" og "Spilte baner") viser spillerens eget resultat i
matchen/runden ("1 UP"/"+4 netto"/"-2 brutto" osv.), og at det er
tydelig hvor mange slag en spiller MOTTAR på et hull -- kommunisert på
scorekortet, allerede FØR hullet er fylt ut (ikke bare etterpå som
netto-tallet i dag). Fire load-bærende avklaringer bekreftet eksplisitt
(AskUserQuestion, alle anbefalte valg): vis resultat BÅDE for pågående
og fullførte runder (ikke bare fullførte som før), samme "UP"/"AS"/
"X&Y"-golfkonvensjon som scorekortets Stilling-rad (ikke et eget
dagligdags språk kun for dashbordkortet), vis BÅDE netto og brutto
til-par for slagspill når HCP spores, og vis slag-indikatoren INNI selve
den tomme score-ruten (ikke en egen ny rad).
**Backend (`app/routers/rounds.py`):** `RoundOut`/`_load_round_out`
(delt av BÅDE `GET /rounds` (liste) OG `GET /rounds/{id}`, ingen egen
kode for listen) fikk tre nye felt: `my_net_score_to_par` (kun
`play_format=="stroke"` OG `course_handicap_snapshot` satt, samme
`allocate_strokes_by_index`-algoritme som list_holes/update_hole
allerede bruker -- ingen ny utregningsmåte), og `my_match_status`/
`my_match_lead` (to-sidede formater KUN, ALLTID viewer-relativt --
ny `_viewer_match_status_text()`-formatter tar en fortegns-FLIPPET
`lead` fra `_build_format_result` sitt allerede eksisterende, testede
resultat -- gjenbrukt UENDRET, ikke en ny match-motor -- slik at
positivt alltid betyr "spørrende bruker leder", uansett hvilken side de
faktisk sitter på). Et avgjort resultat vises som "Vunnet 2&1"/"Tapt
2&1" (eksplisitt prefiks, siden "2&1" alene ikke lenger sier hvem sin
side det gjaldt når visningen ikke er side-A/B-basert).
**Frontend (`round-card.tsx`):** ny `Round.matchStatus`/`matchLead`/
`netToPar`. To-sidede formater viser ett "Resultat"-felt med
matchstatusen (farget grønn ved ledelse, oransje ved etterslep, samme
"form+farge"-språk som resten av appen -- ALDRI backend sin bokstavelige
side A/B). Slagspill beholder "Resultat"+"Til par" (brutto) og fikk et
nytt "Netto"-felt ved siden av, kun når tilgjengelig. Resultatraden er
nå IKKE lenger gatet til kun fullførte runder -- vises også mens runden
pågår. Alle tre kallstedene (`dashboard.tsx`, `own-rounds.tsx`,
`course-rounds.tsx`) oppdatert til å lese og videresende de tre nye
API-feltene, samme mønster i alle tre (ingen egen logikk per fil).
**Slag-indikator FØR utfylling (`round-detail.tsx`):** `ScorecardGrid`
(individuell-ball) og `SideScorecardGrid` (delt-ball) viser nå en liten
blå "−N"-tekst inni den tomme score-ruten når spilleren/siden faktisk
mottar minst ett slag på det hullet -- samme `strokes_received`-verdi
som allerede ble hentet (kun aldri vist før noe var registrert). Egen
farge (`text-info`, den etablerte blåtonen) for å skille denne FØR-
visningen tydelig fra den eksisterende grå netto-visningen ETTER
utfylling. **Reelt, tidligere ubrukt hull funnet underveis:**
`SideScorecardGrid` sin egen frontend-type `ApiSideHole` manglet
`strokes_received` helt (backend har alltid eksponert feltet i
`RoundSideHoleOut`, bare aldri lest her) -- lagt til.
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
friskt): en slagspill-runde ga korrekt brutto til-par 4 / netto til-par
2 (både i enkelt-GET og i listen), en matchrunde der spilleren vant
begge registrerte hull ga korrekt `"2 UP"`/`lead=2`. Deretter en FULL,
ekte nettleser-gjennomgang (samme `localhost`-lærdom anvendt fra start):
rundeboksen viste "2 UP" i grønt for en pågående matchrunde og "Resultat
17 / Til par +4 / Netto +2" (oransje) for en pågående slagspill-runde,
i BÅDE lys og mørk modus; scorekortet viste "−1"-hint i blått på
nøyaktig de tomme rutene der spilleren/siden faktisk mottar et slag,
bekreftet i BÅDE individuell-ball- (match) og delt-ball-grid (foursome).
Ingen konsollfeil i noen av rundene.
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket.
- **Match-identitetsbanneret: territorium-bar i stedet for flat 50/50-boks,
BYGGET, GRUNDIG BROWSERVERIFISERT (inkl. en reell overlapp-bug funnet OG
fikset) OG LIVE (2026-07-29), samme dag:** brukeren lastet opp to bilder
av dagens banner (samme identitetskort som ble bygget dagen før -- navn+
HCP i hver ende, en sentrert statusboks) og en "VELDIG DÅRLIG, kun
illustrerende" skisse av eget forslag: den ledende sidens halvdel bør
markeres OVER på motstanderens halvdel (ikke en statisk 50/50-boks), og
ved AS skal begge sider markeres likt (nøytralt).
**Design:** ny `leadZoneFraction(lead, totalHoles)` -- `0.5 + (lead /
totalHoles) * 0.5`, klippet til `[0.18, 0.82]` for å alltid holde begge
navn lesbare selv ved en ekstrem ledelse. Dominant side får en solid,
mettet farge (`bg-primary`/`bg-brand-orange`, hvit/kontrastfarget tekst
via allerede eksisterende `--primary-foreground`/`--brand-orange-
foreground`-tokens) og strekker seg proporsjonalt forbi midtlinjen;
underlegne side beholder samme fargetone som en svak, lys tint
(`/12`-opacity) med vanlig temafarget tekst. Ved AS (eller ingen hull
spilt ennå): begge soner nøyaktig 50/50 og nøytralt `bg-muted` -- ingen
side ser ut til å lede. Egen lokal kopi i alle tre filer der et match-
identitetsbanner finnes (samme "én fil, én kopi"-konvensjon som
MatchScorecardCell-arbeidet dagen før): `round-detail.tsx` sin
`FormatResultPanel` (primary/brand-orange-tokens), `round-scorecard.tsx`
sin nye delte `LeadBar`+`LeadZone` (samme tokens, portert fra den
tidligere `IdentitySide`+`CenterBadge`-duoen), `session-scorecard.tsx`
sin `IdentityBlock` (FAKTISK lagets `team.color`-hex i stedet for faste
tokens -- dominant sone `backgroundColor: color` + hvit tekst, svak sone
`color-mix(in srgb, ${color} 15%, transparent)`, samme kontrastvalg som
den eksisterende `SegmentedBar` i tournament-leaderboard.tsx).
**Reell overlapp-bug funnet OG fikset UNDER selve browserverifiseringen,
ikke antatt riktig fra kodegjennomgang alene:** første forsøk brukte en
`position:absolute`-sentrert statusboks OPPÅ to `width:X%`-delte soner --
et ekte skjermbilde på en smal (390px) mobil-viewport viste at et langt
navn ("Erol Haagenrud") ble delvis SKJULT bak statusboksen ("l Haagenrud"
vist i stedet, ikke en ellipse-trunkering, et reelt visuelt overlapp).
Første fiksforsøk (CSS Grid med `minmax(0,X fr) auto minmax(0,Y fr)` i
stedet for absolutt posisjonering) løste IKKE problemet alene -- fortsatt
samme overlapp ved re-test. Rot-årsaken var dypere: identitetsboksen
brukte `items-start`/`items-end` på sin YTRE flex-kolonne, som sizer
boksen etter INNHOLDETS egen bredde (shrink-to-fit) i stedet for sonens
faktiske tildelte bredde -- innholdet kunne dermed visuelt strekke seg
utover sin egen sone og inn i midt-kolonnen uansett hvor korrekt selve
grid-/bredde-fordelingen var. Fikset ved at boksen alltid STREKKER SEG
(fjernet items-start/items-end helt), med venstre/høyre-justering i
stedet løst via `justify-start`/`justify-end` på navne-raden og
`text-left`/`text-right` på HCP-teksten -- INNI en boks som nå alltid har
sonens fulle, korrekte bredde, slik at `truncate` faktisk virker presist.
**Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
`teecup_scratch`-database + isolert scratch-MinIO + engangs API-
container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP, 390×
844 mobil-viewport): bygget en ekte match-runde fra bunnen via API (egen-
definert 18-hulls bane, Erol HCP 10 vs. gjest "Tore Morell" HCP 54,
samme HCP-mønster som brukerens skjermbilde) og testet re-render etter
HVER kodeendring, ikke bare én gang til slutt -- fanget nettopp DERFOR
både at CSS Grid-fiksen alene ikke var nok, og at stretch-fiksen
faktisk løste det. Bekreftet: normal ledelse ("1 UP", dominant sone
moderat bredere), en EKSTREM ledelse (avgjort "9&7", dominant sone
nesten fyller hele baren, underlegne navn korrekt trunkert "T…"/"HCP…"
i stedet for å overlappe), AS/ingen-hull-spilt (nøytral 50/50, `bg-
muted`), BÅDE lys og mørk modus (kontrast bekreftet i begge), og samme
fiks bekreftet å fungere identisk i `round-scorecard.tsx` sin
`MatchScorecardGrid`-visning (samme runde, `/my-rounds/{id}/scorecard`).
`session-scorecard.tsx` sin tournament-variant IKKE egen nettleser-
testet denne runden (identisk kodemønster, kun `team.color`-hex i stedet
for faste tokens -- lavere risiko, men flagget ærlig som ikke eget
bevist). Ekte typesjekket produksjonsbuild kjørt (to runder -- én etter
første, mislykkede CSS Grid-only-forsøk, én etter den faktiske stretch-
fiksen), alle 24 ruter listet begge ganger.
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
migrasjon (ren frontend), `docker compose up -d --build
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
`/dashboard` → 200, `teeoff.no` upåvirket.
- **Territorium-bar-språket fullført på de to siste "Matchstatus"-boksene,
BYGGET, BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag -- brukeren ba
eksplisitt om "visuell konsistens ferdig helt ut" etter at jeg flagget
disse to som gjenstående:** `round-leaderboard.tsx` sin
`MatchStatusSection` (den innloggede rundens leaderboard-side) og
`watch-round.tsx` sin `MatchStatus` (den offentlige "Følger live"-siden
for en delt/synlig runde, ADR-036 fase 2) hadde begge fortsatt den gamle
flate boksen (kun stor tekst, ingen fargesone). Disse to har INGEN
navn å vise (deltakernavnene vises allerede andre steder på samme side)
-- lagt til KUN de to fargesonene + den sentrerte statuspillen (samme
`leadZoneFraction`+CSS-Grid-mønster som identitetsbanneret dagen før,
egen lokal kopi i hver fil), ikke selve `LeadZone`-identitetsboksen.
Begge filenes lokale `ApiFormatResult`-type manglet `match_lead` helt
(aldri lest her før, selv om backend alltid har eksponert det) -- lagt
til i begge.
**Verifisert i en isolert scratch-nettleserøkt** (fersk `teecup_scratch`-
database + isolert scratch-MinIO + engangs API-container, ekte `next
dev`, Chrome DevTools MCP, 390×844 mobil-viewport): bygget en ekte
OFFENTLIG (`visibility_mode:"public"`) match-runde fra bunnen via API,
bekreftet BEGGE sider (innlogget leaderboard OG anonym `/watch/{id}`)
viser identisk, korrekt fargesatt "1 UP (Side B)"-tilstand med riktig
dominant/lys sone -- OG en egen AS/0-hull-spilt-test (nøytral 50/50,
ingen farge). Begge bekreftet i BÅDE lys og mørk modus. Ingen
konsollfeil (kun en godartet, urelatert PWA-install-loggmelding). Ekte
typesjekket produksjonsbuild kjørt og bekreftet.
**Rullet ut live 2026-07-29**, ingen migrasjon (ren frontend), `docker
compose up -d --build teecup_frontend` (gjenskapte også `teecup_api`
som vanlig bivirkning, ingen backend-kode rørt). Begge containere
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
**Territorium-bar-språket er dermed konsistent på ALLE fem stedene en
matchstatus vises i appen** (tre fulle identitetsbannere + disse to
navnløse variantene) -- listevisningene (blind draw-reveal, offentlig
turnering-live) beholder bevisst sin egen, ulike kort-per-match-stil
(farget topplinje + statuschip), siden de viser MANGE matcher samtidig
og ikke er en fokusert enkelt-match-visning.
- **Stableford som ekte spilleform + "plukket opp"-tilstand for frittstående
runder, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT (inkl. TO reelle bugs
funnet OG fikset UNDER browserverifiseringen) OG LIVE (2026-07-29), samme
dag:** brukeren ba om å ta fatt på det siste, klart avgrensede hullet fra
ADR-038-gjennomgangen -- Stableford-POENGENE var allerede beregnet og vist
flere steder (round-detail.tsx/round-scorecard.tsx/round-leaderboard.tsx
sin `stablefordPoints()`/`total_points`, bygget 2026-07-25/26), men to
ting manglet reelt: (1) `'stableford'` fantes ikke som et faktisk
selvdeklarert `play_format` (kun "stroke"/"match" fantes som individuelle
format-etiketter), (2) ingen vei til å registrere at en spiller plukket
opp ballen fordi hullet uansett var klart 0 poeng.
**Design, ny migrasjon `038_stableford_and_pickup.sql`:** `round_hole.
picked_up boolean DEFAULT false`, `'stableford'` lagt til
`round_play_format_check`. "Plukket opp" lagres IKKE som en NULL-score --
serveren skriver eksplisitt Net Double Bogey (par + 2 + mottatte slag,
`handicap_engine.py` sin allerede eksisterende og testede `max_hole_
score_for_handicap()`, Rule 3.1b) som selve `score`-verdien når
`picked_up=true` sendes til `PATCH .../holes/{n}` -- dette gir automatisk
presis 0 Stableford-poeng (samme formel som ellers, ingen spesialkoding)
OG et korrekt AGS-bidrag for faktisk-HCP (ADR-038) helt uendret -- INGEN
ny motorlogikk trengs, kun ett nytt felt for VISNING ("PU"-merke i stedet
for det underliggende tallet, samme "form+farge, aldri farge alene"-språk
som resten av golfscore-visningen). `HoleUpdate` beregner strokes_received
for AKKURAT det aktuelle hullet FØR selve UPDATE-en (omstrukturert fra
tidligere "beregn etterpå"-rekkefølge) for å kunne skrive riktig NDB-verdi
i samme kall. Avvist tydelig (400 `VALIDATION_FAILED`) hvis deltakeren
ikke har noen beregnet course handicap ennå.
**To reelle bugs funnet OG fikset UNDER selve browserverifiseringen, ikke
antatt riktig fra kodegjennomgang alene** -- begge samme underliggende
mønster: eksisterende kode som spesialsjekket `play_format === "stroke"`
(for å bety "individuelt, ikke to-sidet format") måtte utvides til
eksplisitt å ekskludere `"stableford"` også, ellers falt det nye formatet
feilaktig gjennom til den TO-SIDEDE grenen:
1. `new-round.tsx` sin `needsSidesSetup = playFormat !== "stroke" &&
playFormat !== "skins"` -- et ekte skjermbilde viste "Du setter opp
sidene..."-teksten under Stableford-valget, til tross for at formatet
aldri bruker sider. Rettet ved å legge til `&& playFormat !==
"stableford"`.
2. **Alvorligere:** `round-detail.tsx` sin `FormatResultPanel` (`if
(round.play_format === "stroke" || !result) return null`) -- et ekte
skjermbilde av en fersk Stableford-runde viste en fullstendig
meningsløs "Side A"/"Side B"-territorium-bar (samme komponent bygget
tidligere samme dag for ekte to-sidede formater) med "Ingen hull
spilt ennå", siden backend sin `_build_format_result()` returnerer en
harmløs tom `ready:true`-respons for ethvert format utenfor
`_TWO_SIDED_FORMATS`/`"skins"` (inkl. det nye "stableford"), som
frontend-sjekken ikke fanget opp. Rettet samme sted (lagt til
`round.play_format === "stableford"` i den samme betingelsen), pluss
den tilhørende `useEffect` som utløser selve HTTP-kallet (unødvendig
nettverkskall for Stableford, samme fiks som for "stroke").
**Systematisk oppfølging etter de to funnene, IKKE bare disse to
stedene:** grep'et gjennom HELE frontend-kodebasen etter samme
`"stroke"`-spesialsjekk-mønster og fant tre til ekte hull i
`watch-round.tsx` (den offentlige "Følger live"-siden) -- leaderboard-
henting, `StrokeLeaderboard`-visning, og "ingen live-visning
tilgjengelig"-fallback-meldingen ville alle feilaktig behandlet en
offentlig Stableford-runde som enten to-sidet eller "ukjent format".
Alle tre rettet samme runde, FØR de kunne bli oppdaget i produksjon.
Backend-siden av samme klasse spesialsjekk (`app/routers/rounds.py`)
var allerede korrekt fra første forsøk (`_format_setup_status`,
`my_net_score_to_par`-gaten, `RoundCreate`/`RoundUpdate` sine
`Literal`-typer) -- kun frontend hadde det gjentatte hullet.
`EditRoundPanel` sin spilleform-brytar (rediger-runde-panelet) utvidet
fra to til tre valg (Slagspill/Stableford/Matchspill), samme mønster
som `new-round.tsx`.
**Scratch-verifisert grundig i to lag** (isolert `teecup_app_scratch`-
rolle + isolert scratch-MinIO + engangs API-container, alle 38
migrasjoner kjørt friskt, samme mønster som hele prosjektet): et
hånd-utregnet scenario (course handicap 18, slope 113/rating lik par,
1 slag mottatt per hull) bekreftet "plukket opp" på et par-4-hull med
1 mottatt slag ga eksakt score 7 (4+2+1, identisk med håndregning),
leaderboardets `total_points` stemte eksakt (0 for plukket-opp-hullet +
3 for et normalt netto-birdie-hull = 3), reversering (fjern plukket-
opp, sett ekte score, plukk opp igjen) fungerte, avvist tydelig for en
deltaker uten HCP (400), `_format_setup_status` bekreftet IKKE lenger
krasjer (KeyError) for stableford (`setup_complete:true` uten
sider), `RoundUpdate.play_format` PATCH til "stableford" i etterkant
bekreftet, runde fullført uten krasj.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
samme `localhost`-ikke-`127.0.0.1`-lærdom fra tidligere runder): hele
opprett-runde-flyten klikket gjennom i UI-et (Stableford-valg, riktig
beskrivelsestekst, INGEN sider-notis etter fiksen), "Plukket opp (0
poeng)"-knappen i selve `ScoringWizard` bekreftet å auto-hoppe videre
akkurat som et vanlig slagtall, og "PU"-merket bekreftet konsekvent på
ALLE FIRE stedene det vises: scorekort-gridet på selve Score-fanen,
det post-runde `round-scorecard.tsx`-scorekortet (med korrekt "Poeng
0"/netto 6), `round-leaderboard.tsx` (både i hull-stripen og i
Poeng-modus, total "3" korrekt), og "Så langt i runden"-tabellen. En
separat REGRESJONSSJEKK av et eksisterende `match`-format (to sider,
territorium-bar, "1 UP") bekreftet UENDRET oppførsel -- de to bug-
fiksene rørte kun stableford-grenen. Ingen konsollfeil i noen av
rundene.
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist
FØR migrasjonen, per CLAUDE.md sin ufravikelige regel): migrasjon 038
kjørt mot ekte `teecup_db` (kolonne + utvidet CHECK-constraint
bekreftet, `test_isolation.sql` fortsatt 12/12), deretter `docker
compose up -d --build teecup_api teecup_frontend`. Begge containere
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **Scramble/greensome: "utslag brukt"-statistikk NÅ OGSÅ for org-scopede
turneringer, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
(2026-07-29), samme dag:** direkte oppfølging av brukerens eget forslag
fra "hva nå?"-runden rett etter Stableford-arbeidet -- funksjonen ble
bevisst holdt utenfor migrasjon 036 (frittstående runder, 2026-07-25),
se dens egen kommentar; dette er den avgrensede oppfølgeren.
**Design, ny migrasjon `039_hole_score_selected_participant.sql`:**
`hole_score.selected_participant_id uuid` (nullable), composite FK
`(organization_id, selected_participant_id) REFERENCES match_
participant(organization_id, id) ON DELETE SET NULL` -- samme mønster
som `hole_score.match_participant_id` sin egen FK, men SET NULL (ikke
CASCADE): fjernes en deltaker fra matchen (mulig FØR lås) skal ikke
slette allerede registrerte hull-scorer, kun nullstille selve valget.
Kun meningsfullt på en DELT-BALL-hull-rad (`match_participant_id IS
NULL`) -- håndhevet i app-laget (`submit_hole_score`), ikke en CHECK,
samme mønster som migrasjon 036.
**Backend (`app/routers/scoring.py`):** `HoleScoreCreate`/`HoleScoreOut`
fikk `selected_participant_id`. Validering: avvist (400
`WRONG_PARTICIPANT_MODE`) for individuell ball (`match_participant_id`
satt) -- gir ingen mening der. For delt ball: validert å tilhøre
matchen OG samme `team_side` som selve scoren (400 `VALIDATION_FAILED`
ellers), speiler `round.py` sin identiske sjekk for frittstående runder.
Begge INSERT/UPSERT-setningene i `submit_hole_score` oppdatert (delt-
ball-grenen skriver/oppdaterer feltet, individuell-ball-grenen inkluderer
det aldri i det hele tatt -- alltid NULL der). `get_scorecard` sin
`stroke_entries`-SELECT utvidet -- feltet flyter automatisk gjennom
siden den allerede delte `HoleScoreOut`-modellen gjenbrukes.
**Frontend (`session-scorecard.tsx`):** ny "Hvem sitt utslag ble
brukt?"-knapperad (samme mønster som round-detail.tsx sin
`SideScoreWizard` fikk 2025-07-25) under `StrokePicker` for delt-ball-
enheter -- vises KUN når et slagtall allerede er registrert for hullet
(ulikt frittstående runders `round_hole`, som alltid forhåndsopprettes
med nullbar score, krever `hole_score`-raden faktisk å EKSISTERE først,
siden `gross_strokes` er `NOT NULL` i skjemaet -- en reell strukturell
forskjell fra migrasjon 036s mønster, løst ved å gjenbruke samme
`submitStroke`-funksjon med et nytt valgfritt fjerde argument som
enten beholder gjeldende valg (utelatt) eller setter et nytt (inkl.
eksplisitt `null` for å fjerne). Ny `SelectedDriverSummary`-komponent
(samme presentasjon som round-stats.tsx sin tilsvarende, tilpasset
denne sidens `teams`/`match.participants`/`scorecard.stroke_entries`-
datform) lagt inn i den eksisterende "Vis full oversikt"-seksjonen.
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-container, alle 39 migrasjoner
kjørt friskt): en FULL org-turnering-scaffold bygget fra bunnen via API
for FØRSTE gang i en test for akkurat denne funksjonen (org/egendefinert
bane/18 hull/tee/turnering/to lag/fire spillere/roster/foursome-økt+
match/fire deltakere/lås) -- 51/51 sjekker: skriving uten valg, skriving
med valg (gross_strokes bevart), feil-side-valg avvist (400
VALIDATION_FAILED), individuell-ball+valg avvist (400
WRONG_PARTICIPANT_MODE), persistens bekreftet via `GET .../scorecard`,
korrekt opptelling (2 vs. 1 utslag), eksplisitt `null` fjerner valget.
`test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket
produksjonsbuild kjørt og bekreftet.
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
isolert scratch-backend, inkl. et ekte 2FA-oppsett for en fersk
organisasjonseier siden ADR-021 krever det): naviger til foursome-
matchens scorekort, bekreftet at allerede-registrerte valg (satt via
API) vises korrekt som trykte knapper for RIKTIG spiller på RIKTIG
hull -- OG et ekte klikk i UI-et som byttet valgt spiller for et hull,
bekreftet persistert direkte i databasen etterpå (ikke bare at UI-et
så riktig ut). "Vis full oversikt" sin nye "Utslag brukt"-seksjon
bekreftet med nøyaktig riktig opptelling for begge lag (2/1 og 1/0,
inkl. "Registrert på N av M spilte hull"-teksten). Ingen konsollfeil.
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist
FØR migrasjonen): migrasjon 039 kjørt mot ekte `teecup_db` (kolonne +
FK-constraint bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
`docker compose up -d --build teecup_api teecup_frontend`. Begge
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
upåvirket. **"Utslag brukt"-statistikken finnes dermed nå for BEGGE
domener** (frittstående runder siden 2026-07-25, org-scopede
turneringer siden i dag) -- se ARCHITECTURE_DECISIONS.md sitt åpne
spørsmål 4 (Scramble-grensesnitt), som dermed er helt avsluttet.
- **ADR-037 (individuelle/flerrunde-turneringer): migrasjon + motor + API
BYGGET OG SCRATCH-VERIFISERT, IKKE ENNÅ RULLET UT (2026-07-30):** bruker
ba om å gå videre med anbefalingen fra en dypdykk-gjennomgang av
.md-filene -- det eneste gjenværende punktet med en ferdig, load-bærende
strukturbeslutning (ADR-037, 2026-07-26) uten kode. Bygget i tre lag,
samme "test i isolasjon FØR resten"-rekkefølge som ADR-005/033/038/039.
**Migrasjon `040_individual_tournaments.sql`:** `tournament.format_type`
(`team`/`individual`, default `team` -- alle eksisterende rader uendret)
+ `tournament.scoring_method` (`stroke_gross`/`stroke_net`/`stableford`,
nullable). Fem nye, RLS-beskyttede tabeller: `tournament_round` (samme
rolle som `session`, peker til org-ens EGEN bane -- ingen snapshot,
ulikt ADR-033), `tournament_participant` (samme rolle som `team_roster`,
fryser `handicap_index_snapshot`), `tournament_round_participant`
(tee + cachet course/playing-handicap PER runde), `tournament_round_hole`
(rå brutto slag, kilde-sannhet), `tournament_round_score` (ferdig
utregnet brutto/netto/Stableford-total PER deltaker PER runde, cachet --
samme mønster som `match.status_text`/`points_side_a/b`; sammenlagt over
flere runder summeres VED LESING i leaderboardet, ingen egen tredje
cache-tabell, Beslutning C).
**Scratch-verifisert alene FØR API-et ble bygget:** alle 40 migrasjoner
kjørte rent i rekkefølge, `test_isolation.sql` fortsatt 12/12, 10 egne
funksjonelle sjekker (kryss-org-isolasjon på BÅDE lesing og skriving,
`format_type`-CHECK+default, unik-constraints, `gross_strokes`-CHECK,
kaskade-sletting).
**Motor** (`handicap_engine.py`, ny seksjon rett etter
`allocate_over_played_holes`): `stroke_play_gross_total`/
`stroke_play_net_total`/`stableford_points_for_hole`/`stableford_total`
-- rene funksjoner, ingen ny slagfordeling (bruker samme
`allocate_over_played_holes`-output som resten av motoren). 8 nye
tester, alle 63 (55 eksisterende + 8 nye) bestått i
`test_handicap_engine.py`.
**API** (nytt `app/routers/individual_tournaments.py`, registrert i
`main.py`): CRUD for runder/turnering-deltakere/rundedeltakere (med
handicap-beregning ved tilføyelse -- v1 har INGEN allowance-prosent for
individuelle turneringer, `playing_handicap` er alltid identisk med
avrundet `course_handicap`, ulikt lagturneringenes komplekse relative
`AllowanceStrategy`-familie, som er bygget for et to-siders oppgjør og
ikke gir mening for et flatt felt), hull-for-hull-scoring
(`PATCH .../holes/{n}`, cacher totalen på nytt ved hver innsending),
leaderboard som summerer på tvers av runder ved lesing.
`tournaments.py` sin `TournamentCreate`/`TournamentUpdate`/`Tournament`
utvidet med `format_type`/`scoring_method` (samme `exclude_unset`-PATCH-
mønster som resten av filen).
**To reelle funn, begge fikset FØR utrulling:**
1. Leaderboard-endepunktet kunne IKKE hete
`/orgs/{id}/tournaments/{id}/leaderboard` -- den stien er allerede
`tournaments.py` sitt LAG-leaderboard, og siden `tournaments.router`
registreres FØR `individual_tournaments.router` i `main.py`, ville
det stille skygget for det nye endepunktet (funnet presist ved en
ekte API-test som krasjet på feil responsform). Løst med et eget
navn, `/individual-leaderboard` -- samme kollisjonsklasse som
`/rounds` vs. `/my-rounds` tidligere, denne gangen unngått fra start.
2. `list_rounds`/`list_tournament_participants` manglet en eksplisitt
"finnes turneringen"-sjekk (samme mønster `list_sessions` allerede
har) -- ga stille en tom liste under RLS for en fremmed
turnering-id i stedet for 404 (ingen sikkerhetslekkasje, RLS
blokkerte fortsatt all faktisk data, men inkonsistent med resten av
API-et). Rettet til å matche `list_sessions` presist.
**Scratch-API-verifisert grundig, 52/52 sjekker** (isolert
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
API-container, ekte HTTP via `requests`, ekte magic-link-innlogging via
dev-log): full happy path fra org til leaderboard, hånd-utregnet netto-
kryssjekk for to spillere (course handicap 11/20, stemte eksakt),
`front_9`-runde avviser hull utenfor omfang, kryss-org-isolasjon
(bekreftet BÅDE lesing og en FK-basert skrivesperre), slette-vern (runde
MED deltakere avvist 409, turnering-deltaker fortsatt referert av en
rundedeltaker avvist 400 RESTRICT-FK, løst opp igjen etter fjerning).
`test_isolation.sql` 12/12 uendret (additiv migrasjon).
**IKKE bygget i denne runden, bevisst neste steg:** frontend (ingen
skjerm ennå -- samme lagdelings-rekkefølge som ADR-033/038/039).
Autorisasjon er bredt org-medlemskap for ALT i denne runden, inkl. selve
scoreregistreringen -- en senere innstramming (analogt ADR-023s
kaptein-only) er en naturlig, separat oppfølger. De fem konkrete
formatene (Københavner m.fl.) og Order of Merit fortsatt ikke designet.
**Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt
(plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige regel):
migrasjon 040 kjørt mot ekte `teecup_db` (alle fem nye tabeller + de to
nye `tournament`-kolonnene bekreftet, eksisterende turnering fikk
korrekt default `format_type='team'`, `test_isolation.sql` fortsatt
12/12 mot ekte database), deretter `docker compose up -d --build
teecup_api` (kun backend, ingen frontend-kode denne runden). Containeren
boot-et rent (`Application startup complete`), `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket. Verifisert presist at den nye ruten
faktisk når FastAPI gjennom hele produksjonsstacken (Caddy → Next.js
rewrite → `teecup_api`): et anonymt kall mot
`/orgs/.../tournaments/.../rounds` ga korrekt `401 NOT_AUTHENTICATED`,
ikke en rå 404.
- **ADR-037 frontend HÅNDKODET, GRUNDIG BROWSERVERIFISERT (inkl. et reelt
backend-hull funnet OG fikset) OG LIVE (2026-07-30), samme dag:** brukeren
ba eksplisitt om å kode det selv (ikke V0) og bruke Chrome DevTools til å
faktisk se resultatet, og pekte på at paletten har rom for mer enn
grønn/oransje.
**Bygget:** ny `components/individual-tournament-detail.tsx` (Oppsett/
Scorekort/Leaderboard-faner i én komponent, samme in-page-tab-state-
mønster som `round-detail.tsx`) + ny `components/tournament-router.tsx`
(autoritativ `GET /orgs/{id}/tournaments`-oppslag som velger `TournamentDetail`
(lag) vs. `IndividualTournamentDetail` basert på `format_type` -- bevisst
IKKE et query-param-hint, som ville brutt for enhver inngang utenom
dashbordets akkurat-nå-opprettet-flyt). `app/tournaments/[id]/page.tsx`
peker nå til routeren. `dashboard.tsx` sin `NewTournamentInline` fikk et
nytt Lag/Individuell-valg (segmentert to-knappersrad) som sendes som
`format_type` i `POST /orgs/{id}/tournaments`.
**Ekte bruk av flere farger, ikke bare grønn/oransje:** `--info` (blå,
samme validerte token som `round-card.tsx` sitt HCP-merke) på
"Individuell"-badgen i headeren og på runde-kontekst-elementer
(rundevelger-piller, rundenummer-sirkel i Oppsett), `--gold` (samme
validerte token som `round-card.tsx` sitt "Personlig rekord"-merke) på
leaderboardets 1.-plass-rad (medaljeikon + gullbakgrunn).
**Scorekortet** gjenbruker `round-scorecard.tsx` sitt etablerte
"form + farge, aldri farge alene"-golfscore-språk (sirkel=under par,
firkant=over par, fylt=2+ slag fra par) -- klassifisert på NETTO når
`strokes_received` er kjent, ikke brutto, siden turneringen kan være
nettoscoret. Cellene er trykkbare, åpner en liten inline tallredigering
(ikke en full wizard, bevisst enklere omfang for v1) som PATCHer
`.../holes/{n}` direkte.
**Ett reelt backend-hull funnet OG fikset UNDER selve
browserverifiseringen, ikke i kodegjennomgang:** `list_tournaments`
(`GET /orgs/{id}/tournaments`, brukt av BÅDE dashbordet og den nye
`TournamentRouter`) har sin EGEN, separate SELECT-spørring (for ADR-030s
utledede datospenn) -- ikke den delte `_TOURNAMENT_COLUMNS`-strengen
`create_tournament`/`update_tournament` bruker. Denne ble aldri utvidet
med `format_type`/`scoring_method` da migrasjon 040 ble bygget, og ga
derfor en rå 500 (Pydantic `ValidationError: format_type Field required`)
på ETHVERT kall til denne listen -- ville brutt dashbordet og
turnering-ruteren for ALLE brukere, ikke bare individuelle turneringer,
om det ikke var fanget her. Rettet med to nye kolonner i SELECT-en.
**Ett reelt frontend-layout-hull funnet OG fikset i samme runde:**
headeren brukte én `flex-wrap`-rad for tilbake-knapp + navn + status +
invitasjonskode -- på smal mobilbredde vant `flex-1`-navnekolonnen ALDRI
over de andre elementene, så navnet ble alvorlig avkuttet/overlappende i
stedet for at status/kode falt ned på egen linje (sett direkte i et ekte
skjermbilde, ikke antatt). Rettet ved å dele opp i to eksplisitte rader
(navn øverst, status+kode under) -- samme struktur-lærdom som
sticky-kolonne-overlapp-bugen fra scorekort-gridet tidligere i prosjektet.
**Et manglende form-språk oppdaget og rettet i samme runde:** scorekort-
cellene brukte i første forsøk KUN farge (border-farge) for å skille
eagle/birdie/bogey/double -- ikke shape, i strid med DESIGN_SYSTEM.md sin
"aldri farge alene"-regel og `round-scorecard.tsx` sin egen etablerte
`ScoreMark`. Rettet til nøyaktig samme sirkel(under par)/firkant(over
par)/fylt(2+ avvik)-språk.
**Browserverifisert grundig, mot en isolert scratch-backend** (samme
mønster som resten av uken -- isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-container, pluss en egen engangs
frontend-container med kildekoden KOPIERT inn, ikke bind-mountet -- en
bind-mountet `next dev` viste seg ustabil her, gjentatte Turbopack-panics
("Next.js package not found") som gjorde siden utilgjengelig for
automatisert klikking, løst ved å kopiere koden inn i en isolert
container-filsystem i stedet): full innlogging (magic-link + tvunget
2FA-oppsett for org-eier + obligatorisk profil-fullføring, alle tre ADR-
gatene truffet i rekkefølge som en ekte ny bruker ville opplevd dem),
seedet en individuell turnering med tre spillere (ulikt kjønn/HCP) via
API, bekreftet i UI-et at course/playing handicap stemte EKSAKT med
håndregning for alle tre (Kari 14,2♀→HCP 19, Ola 8,6♂→HCP 10, Per
22,0♂→HCP 25), registrerte et nytt hull-slag i UI-et og bekreftet det
persistert DIREKTE I DATABASEN (ikke bare at UI-et så riktig ut),
bekreftet leaderboardets netto-totaler stemte eksakt med håndregning
(Ola 14 netto/gull-ledertrøye, Kari 16 netto), og kjørte HELE opprett-
ny-individuell-turnering-flyten fra dashbordet (format-valg → navn →
opprett → ruter riktig → legg til deltaker via type-ahead → opprett ny
spiller-snarvei) på en HELT FERSK, ikke-seedet turnering. Ingen
konsollfeil (`list_console_messages`) gjennom hele økten. Ekte
typesjekket produksjonsbuild (samme `Dockerfile` som deployes) kompilerte
rent til slutt, med begge fiksene inne.
**Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket, `GET /orgs/.../tournaments` (den fikset
ruten) bekreftet nåbar med korrekt `401` gjennom hele produksjonsstacken.
**ADR-037 er dermed live, backend + frontend** (grunnstruktur — de fem
konkrete formatene og Order of Merit fortsatt ikke designet, se
FEATURE_BACKLOG.md).
- **To reelle punkter fra bruker, BEGGE FIKSET OG LIVE, samme dag
(2026-07-30):** rapportert med et vedlagt skjermbilde av
Chip/Bunker/Straffeslag-stepperne som gled over i hverandre på
`/my-rounds/{id}`, pluss en forespørsel om et TeeCup-relatert favikon.
1. **Stepper-overlapp, root cause presist identifisert:** `ScoringWizard`
sin "flere detaljer"-seksjon (`round-detail.tsx`) er fast begrenset
til `max-w-sm` (384px) UANSETT hvor bred selve viewporten er -- men
Chip/Bunker/Straffeslag-gridet brukte et VIEWPORT-basert
`sm:grid-cols-3`-brudd (640px). Enhver skjerm bredere enn 640px
(dvs. de fleste telefoner i liggende modus, nettbrett, og skjermbildet
brukeren delte) trigget dermed 3-kolonne-modus INNI en 384px-bred
kolonne -- hver Stepper har to faste 44px-knapper (tilgjengelighets-
kravet, kan ikke krympes) + verdi + padding, som aldri kan bli
smalere enn ca. 190px, så tre av dem kolliderte alltid. Samme
klasse feil som de tidligere sticky-kolonne- og match-identitets-
boks-overlappene i prosjektet (fast innholdsbredde møter et
viewport-basert, ikke container-basert, brudd). Fikset ved å fjerne
`sm:grid-cols-3` helt -- alltid stablet i én kolonne, som uansett var
den eneste bredden som noensinne hadde plass i en 384px-container.
2. **Favikon var faktisk V0s egen logo, ikke TeeCup:** `icon.svg` og
`icon-light-32x32.png`/`icon-dark-32x32.png` (selve nettleser-fane-
ikonet, styrt av `layout.tsx` sin `metadata.icons`) viste seg -- ved
faktisk å åpne og se på filene, ikke anta -- å fortsatt være en
ubrukt "V0"-logo (sort/hvit v0.app-merke) helt siden V0-eksporten,
aldri erstattet. `apple-icon.png` og selve PWA-ikonsettet
(`public/icons/icon-192.png` m.fl.) var derimot ALLEREDE korrekt
TeeCup-merket (grønt golf-flagg, fra PWA-runden 2026-07-19) -- kun
favikon-stien var glemt. Fikset ved å gjenbruke SAMME etablerte
golf-flagg-design: `icon-light-32x32.png`/`icon-dark-32x32.png`
regenerert (nedskalert fra `icon-512.png` via en engangs
`sharp`-scratch, samme verktøy som PWA-runden brukte) og `icon.svg`
skrevet på nytt som en ren vektor av samme flagg (grønn `#8BC24A`-
bakgrunn, hvit flaggstang+flagg -- appens egen merkevarefarge, ikke
funnet på).
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
som deployes) kompilerte rent BEGGE ganger (én mislykket 404-test mot
kun `--target builder`-imaget, som IKKE har `public/`-mappen kopiert inn
ved siden av `server.js` ennå -- forventet byggestrenge-artefakt, ikke en
reell bug -- rettet ved å bygge og teste det FULLE, faktiske
produksjonsimaget i stedet, som ga korrekt `200`/riktig `content-type`
for alle tre ikonfilene). Stepper-fiksen browserverifisert grundig i en
isolert scratch-økt (ekte innlogging, en fersk `full`-stat_level-runde
seedet via API, veiviseren kjørt gjennom Slag→Putter→Avstand→Detaljer):
bekreftet INGEN overlapp ved 800px viewport (der bugen reprodusertes
presist, matcher brukerens skjermbilde) OG ingen regresjon ved 390px
(alltid stablet der uansett, uendret oppførsel). Ingen konsollfeil.
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt (samme
"redeploy begge"-godkjenning som ADR-037): ingen migrasjon,
`docker compose up -d --build teecup_frontend` (gjenskapte også
`teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
containere boot-et rent, `/health`/`/dashboard` → 200, favikon-filene
bekreftet nåbare over ekte https med riktig `content-type`, `teeoff.no`
upåvirket.
- **Individuelle turneringer (ADR-037): scoring-autorisasjon strammet inn,
BYGGET, SCRATCH-VERIFISERT OG LIVE (2026-07-30), samme dag som frontend-
runden:** direkte oppfølging av det noterte hullet fra byggerunden
tidligere samme dag — ethvert org-medlem kunne skrive score for HVEM SOM
HELST i en individuell turnering, ikke bare sin egen.
Ny `user_is_own_tournament_participant` i `app/team_authz.py` — samme
mønster som `user_is_match_participant` (ADR-023): krever at brukeren ER
spilleren bak `tournament_participant`-raden (via `player.user_id`),
eller er org-eier/admin. Brukt av `individual_tournaments.py` sin
`update_hole` (eneste endepunkt strammet inn). Runde-/deltaker-OPPSETT
(opprett/slett runde, legg til/fjern turnering-/rundedeltaker) forblir
bevisst på vanlig org-medlemsnivå — matcher presedensen fra `session`-
opprettelse og `team_roster`-tilføyelse i tournaments.py, som heller
aldri har vært captain-/admin-gatet.
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-container, alle 40 migrasjoner kjørt
friskt): full regresjon av den eksisterende 52-punkts testsuiten (uendret
grønn — org-eier brukes gjennomgående der, rammes ikke av innstrammingen),
pluss 13 nye målrettede sjekker: et fremmed org-medlem (uten kobling til
noen av deltakerne) NEKTES å score for både en annen deltaker OG en
tredje deltaker (403 `NOT_TOURNAMENT_PARTICIPANT`), en spiller KOBLET til
sin egen `tournament_participant` (via `player.email` + ADR-017s
kontokobling ved innlogging) FÅR score seg selv men NEKTES å score for en
annen, org-eier beholder uendret admin-fallback for begge, og lesing
(`GET .../holes`) er bekreftet uendret tilgjengelig for et vanlig
org-medlem (kun skriving er strammet inn, ikke lesing).
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
migrasjon (ren Python-logikk), `docker compose up -d --build
teecup_api`. Containeren boot-et rent (`Application startup complete`),
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **To notater fra brukeren, BEGGE BYGGET, GRUNDIG BROWSERVERIFISERT OG
LIVE (2026-07-30), samme dag som ADR-037-autorisasjonsfiksen:**
1. **GIR-auto-inferens for "Innspill: Traff":** ny `useEffect` i
`ScoringWizard` (`round-detail.tsx`) -- idet "flere detaljer"-steget
nås, settes `stat.approach = "hit"` automatisk når
`stat.strokes - stat.putts <= hole.par - 2` (samme formel som den
allerede eksisterende GIR-STATISTIKK-inferensen i `round-stats.tsx`
sin `isGir`, nå også koblet til selve REGISTRERINGEN) -- men KUN når
`stat.approach` fortsatt er `null` (rører aldri et allerede satt
manuelt ELLER tidligere auto-satt valg).
2. **Auto-prompt "Fullfør runde":** ny `allHolesEnteredForEveryone`-
beregning + `AllHolesEnteredBanner`-komponent i `RoundDetail` -- viser
en tydelig CTA øverst på Score-fanen ("Alle hull er ført — Klar til å
fullføre runden?") så snart ALLE spillere (eller BEGGE sider for
delt-ball-formater) har `played=true` på alle hull i `holeOrder`,
gatet på `round.setup_complete`. Kaller samme `finishRound()` som den
eksisterende, tidligere passive knappen.
**Browserverifisert grundig i en isolert scratch-nettleserøkt** (fersk
`teecup_scratch`-database + isolert scratch-MinIO + engangs API-
container + en isolert `next dev`-frontend-container med KOPIERT, ikke
bind-mountet, kildekode -- bind-mount ga gjentatte Turbopack-panics i
denne økten, løst ved å `tar`-kopiere kildetreet inn i en isolert
container-filsystem i stedet): GIR-auto-inferens bekreftet BÅDE positivt
(birdie+1-putt -> "Traff" auto-merket umiddelbart, uten klikk, bekreftet
visuelt OG direkte i databasen) og negativt (bogey+1-putt -> "Traff"
korrekt IKKE forhåndsmerket). Auto-prompt-banneret bekreftet å dukke opp
automatisk idet siste hull ble fylt for begge spillerne i en 18-hulls
2-spiller-runde, og "Fullfør runden"-knappen i banneret bekreftet å
fullføre runden korrekt (håndterte den native `confirm()`-dialogen via
Chrome DevTools -- HCP-differensialer beregnet og vist etterpå). Ingen
konsollfeil i noen av rundene.
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
migrasjon (ren frontend), `docker compose up -d --build
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
`/dashboard` → 200, `teeoff.no` upåvirket.
- **Symbolforklaringen fjernet fra scorekortet + to design-notater fanget
opp (2026-07-30), samme dag:** brukeren viste et skjermbilde av
`round-scorecard.tsx` med "Under par (sirkel)/Over par (firkant)/Fylt
symbol..."-forklaringen sirklet inn og ba om at den fjernes — `Legend`-
komponenten (kun ett bruksted) fjernet fullstendig. Samtidig reist:
(1) spilleren bør kunne se historikk/statistikk for NØYAKTIG hullet som
spilles/er spilt (ikke bygget — krever en ny, ikke-triviell "aggreger på
tvers av runder, filtrert til banenavn+hullnummer"-spørring, notert i
FEATURE_BACKLOG.md); (2) manglende lenke scorekort→statistikk, og et
åpent spørsmål om scorekort/statistikk/live-registrering burde slås
sammen til én visning, med mottatte slag vist som prikker. Svarte
direkte (ikke bygget): behold scorekort og statistikk som to separate
sider (bevisst skilt 2026-07-25 nettopp for å unngå én lang/rotete
side) — legg heller til den manglende lenken; behold også selve
live-registrerings-gridet uendret (bygget 2026-07-27 som en AKTIV
data-entry-flate, ikke en lesevisning — et vertikalt front9/back9-delt
format ville svekket registreringsergonomikken). "Prikker for mottatte
slag" vurdert som en god, uavhengig senere polish-oppgave. Full
begrunnelse i FEATURE_BACKLOG.md.
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ekte
typesjekket produksjonsbuild kompilerte rent, `docker compose up -d
--build teecup_frontend` (gjenskapte også `teecup_api` som vanlig
bivirkning). Begge containere boot-et rent, `/health` → 200,
scorekort-siden bekreftet 200, `teeoff.no` upåvirket.
- **De to avgrensede scorekort-oppfølgerne fra samme dag BYGGET,
BROWSERVERIFISERT OG LIVE (2026-07-30):** (1) ny "Se full
rundestatistikk"-lenke nederst på `round-scorecard.tsx` (symmetrisk med
den eksisterende motsatte lenken på statistikksiden); (2) mottatte
slag-hintet (vist FØR et hull er fylt ut) endret fra "−N"-tekst til
prikker (`StrokeDots`, `round-detail.tsx`, i BÅDE `ScorecardGrid` og
`SideScorecardGrid`) — selve tallet bevart i `aria-label` for
tilgjengelighet. Selve spørsmålet om å slå sammen scorekort/statistikk/
live-registrering til én visning er BEVISST IKKE avgjort — bruker
ønsker en fremtidig brukertest først.
**Browserverifisert grundig i en isolert scratch-nettleserøkt** (samme
mønster som resten av uken): en HCP 28-spiller (course handicap 30)
bekreftet å vise nøyaktig to prikker på hull 1-12 i live-
registreringsgridet (riktig ut fra 30-18=12 ekstra slag), den nye
lenken bekreftet klikkbar og navigerte korrekt til `/stats`. Ekte
typesjekket produksjonsbuild kompilerte rent. Ingen konsollfeil (kun en
godartet, urelatert WebSocket-advarsel fra rask sidenavigasjon).
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
også `teecup_api` som vanlig bivirkning). Begge containere boot-et
rent, `/health`/`/dashboard`/scorekort-siden → 200, `teeoff.no`
upåvirket.
- **Åtte nye turneringsformater: BACKEND FERDIG OG SCRATCH-VERIFISERT,
IKKE ENNÅ RULLET UT (2026-07-30):** brukeren ba om å ta fatt på det
lenge noterte "flere turneringsformater"-punktet fra 2026-07-19. Listen
ble utvidet fra fire til åtte (Shamble, Chapman/Pinehurst, Bingo Bango
Bongo, Money Ball/Lone Ranger, Nassau Match Play lagt til; "High-low-
high"/"Try all" presist avklart av bruker med et fullt utregnet
eksempel; "Robbins" droppet). Full plan skrevet og godkjent (plan-
modus) FØR bygging, deretter bygget ETT format om gangen i rekkefølgen
Chapman → Nassau → Københavner → Bingo Bango Bongo → Flaggturnering →
Shamble → Money Ball → High-low-high, motor→migrasjon→API→scratch-
verifisering per format FØR neste startet.
**Arkitektonisk hjem** (bekreftet av bruker "begge, fra start"): flatt-
felt-formater (Københavner/BBB/Flag) → frittstående runder OG den
individuelle org-turnering-modellen (ADR-037), ALDRI org-lagturneringer
(ADR-011s to-lags-modell passer strukturelt ikke). To-siders-formater
(Chapman/Nassau/Shamble/Money Ball/High-low-high) → frittstående runder
OG org-lagturneringer, ALDRI den individuelle org-modellen. Shamble/
Money Ball i frittstående runder: ETT LAG = HELE RUNDENS deltakersett
(bekreftet av bruker, INGEN `round_side`) -- flere lag kobles via
eksisterende `flight_group_id`-leaderboard.
**Ny delt `ENGINE_FORMAT_ALIASES`-mekanisme** i `app/handicap.py` --
Chapman/Shamble/Money Ball/High-low-high aliaserer til
foursome/singles/singles/fourball for HCP-beregning (ingen dupliser
allowance-logikk), løst opp FØRST i `parse_allowance_config`/
`compute_and_store_side_handicaps`/`relative_strokes_for_match`.
**Reelle funn/presiseringer underveis, alle rettet FØR utrulling:**
Nassau kan hindres av org-matchers eksisterende ALREADY_DECIDED-sperre
(ADR-012) hvis overall-matchen avgjøres tidlig -- dokumentert v1-
begrensning, ikke fikset. Københavner-poeng må regnes om for ALLE tre
deltakerne samtidig (eneste scoring_method som bryter "regn om for ÉN
deltaker"-mønsteret). Money Ball-rotasjon er basert på POSISJON i spilt
rekkefølge, ikke rått hullnummer. High-low-high passer ikke inn i den
ternære `HoleResult`-cachen -- egen dedikert leseendepunkt per system
(som Nassau), generisk `status_text` viser en ufarlig statisk "AS"-
plassholder for dette formatet. To reelle "manglende SELECT-kolonne"-
krasj (bbb_sweep_bonus_enabled, shamble_best_n) fanget og rettet under
scratch-testing, før noe nådde en antatt-ferdig tilstand.
**Verifisert grundig:** `handicap_engine.py`-enhetstester 98/98 (opp fra
63 FØR denne runden), full scratch-API-verifisering i begge relevante
systemer per format (isolert `teecup_app_scratch`-rolle + isolert
scratch-MinIO + engangs API-container) -- over 400 sjekker totalt på
tvers av de åtte formatenes testskript, inkl. High-low-high sin
eksakte gjenskaping av brukerens eget håndregnede eksempel ("1-1 etter
hull 1", "2-1 til lag 2"). `test_isolation.sql` 12/12 uendret gjennom
hele runden (migrasjonene 041-050 er rent additive).
**IKKE bygget ennå, bevisst neste steg:** frontend for samtlige åtte
formater (nye format-valg i opprett-runde/opprett-økt, nye
resultatvisninger). **Migrasjoner 041-050 BYGGET OG SCRATCH-VERIFISERT,
IKKE ENNÅ rullet ut mot ekte `teecup_db`** -- venter på eksplisitt
bekreftelse fra bruker. Se FEATURE_BACKLOG.md for full detalj per
format.
Neste steg:
0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet
(2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen
konsoll-feil. Ikke hvert enkelt interaksjonsdetalj (f.eks. gjeste-
kjønnsendringens reaktive utslagsfilter) klikket gjennom stykke for
stykke, men grunnleggende rendring/lasting er bevist, ikke lenger
bare typesjekket.
0b. **Rundeleaderboard — nå FAKTISK nettleser-bekreftet (2026-07-27):**
brutto/netto/poeng-veksling testet direkte i nettleseren, viste
korrekte tall og riktig form/farge-språk.
1. **Ferdig, kun for historikk:** dashbord-redesign (ADR-035) og
venner/kategorisert deling fase 1 (ADR-036) — begge designet
2026-07-25 og siden BYGGET, SCRATCH-VERIFISERT OG RULLET UT LIVE
samme dag (se status over). Venner fase 2 (rundevisibilitet) og
fase 3 (ekte medspillere) er fortsatt ikke bygget.
3. **Frittstående rundeføring + detaljert statistikk — ADR-033, skrevet
2026-07-22 og siden BYGGET/ITERERT KONTINUERLIG hver dag fram til
2026-07-29 (se hele status-loggen over -- dette punktet er nå appens
klart mest utviklede område, ikke lenger "ikke bygget").** Beholdt her
UENDRET som historisk kontekst for selve grunnbeslutningen fra
2026-07-22 (hvorfor/hvordan ADR-033 ble designet) -- ikke som en
påstand om at arbeidet fortsatt gjenstår. Brukeren avklarte 2026-07-22
at dette skulle bli appens HOVEDFOKUS (turneringsoppsett skulle bli
ekstremt enkelt ETTER dette var på plass) — den største enkeltbeslutningen
i prosjektet siden ADR-001, og det har stemt. Fire load-bærende
delbeslutninger avklart eksplisitt
(AskUserQuestion): nytt parallelt eierskapsmønster keyet på `user_id`
(gjenbruker det allerede beviste `plain_connection()`-mønsteret fra
personlig profil/HCP-historikk/sekundær e-post — IKKE en skjult
personlig organisasjon), fast sett navngitte statistikk-felt per hull
(ikke fri slag-for-slag-logg), full WHS HCP-indeksberegning bygges NÅ
(ikke utsatt), shotgun-start blir egen separat ADR-034. De tre
HCP-PDF-ene lest i sin helhet — Score Differential/Course
Handicap/Net Double Bogey-formlene er hentet derfra (kilde, ikke
hukommelse).
**Oppdatert samme dag:** brukeren bekreftet banedata-spørsmålet (LIVE
oppslag mot teeoff, ikke import — Beslutning C) og lastet opp en
FJERDE PDF, den offisielle "WHS Rules of Handicapping 2024"
(USGA/R&A) — lest i sin helhet, løste presist det som manglet: 9-
hulls-minimumsregelen (Rule 2.2, IKKE bare "minst 9 hull" — en
9-hulls-runde krever ALLE 9 av et faktisk ratet sett, en 18-hulls-
runde krever minst 10 av 18), full HCP-indeks-pipeline (Score
Differential, Net Double Bogey, expected-score for uspilte hull, beste
8-av-20, Low Handicap Index, soft/hard cap), OG en ny, presis
9-hulls-Course-Handicap-formel som HALVERER indeksen først (Rule
6.1b) — bevisst holdt atskilt fra det eksisterende front_9/back_9-
øktoppsettet i turnering-flyten (ADR-008), som løser et annet problem
av andre grunner. ADR-033 er dermed fullt kildebelagt, ingen store
åpne HCP-regelspørsmål gjenstår.
**Bygging påbegynt samme dag:** brukeren instruerte at "Expected
Score" (Rule 3.2b, upublisert WHS-formel) erstattes gjennomgående av
WHS sin egen "Net Par"-term for uspilte hull — løser samtidig hele
9-hulls-runde-spørsmålet uten en egen separat formel (Rule 5.1b droppet
bevisst). Hele HCP-indeks-motor-komponenten (Net Double Bogey/Net Par,
Adjusted Gross Score, Score Differential, Handicap Index fra beste-
8-av-20 m/opptrappingstabell for <20 runder, Low Handicap Index, soft/
hard cap, 9-hulls Course Handicap) er BYGGET og TESTET i
`handicap_engine.py` — 41/41 tester (`test_handicap_engine.py`, kjørt
uten pytest da det ikke er installert i miljøet), flere verifisert mot
regelbokens egne tallregneeksempler (Rule 5.2a, Rule 5.1c, Diagram 5.8,
Diagram 3.1b). Ren Python, ingen DB/API/frontend rørt ennå — matcher
ADR-005s "test i isolasjon FØR resten".
**Deretter, samme dag:** full databasemigrasjon skrevet
(`020_personal_rounds.sql`, sju nye tabeller: `round`/
`round_participant`/`round_hole` + fire for en global egendefinert-
bane-katalog) og scratch-verifisert (9 sjekker som
`teecup_app_scratch`, ikke superbruker — CHECK-constraints, XOR
user_id/guest_name, kaskade-sletting, GIR-derivering bekreftet mot
ekte data). INGEN RLS på disse tabellene (Beslutning A). `test_
isolation.sql` fortsatt 12/12.
**Rullet ut mot ekte `teecup_db` 2026-07-22,** bruker bekreftet
eksplisitt: alle sju tabeller bekreftet opprettet, `test_isolation.sql`
fortsatt 12/12 mot ekte database.
**Deretter, samme dag: fullt API-lag bygget** (`app/routers/
rounds.py` — opprett/liste/hent/slett runde, legg til/fjern gjest-
deltaker, PATCH hull-for-hull-stats, fullfør-runde som kjører hele
Adjusted-Gross-Score→Score-Differential-kjeden). Fant og fikset et
reelt hull underveis: `round_participant` manglet rating-tall-
kolonner (kun tee-NAVN var snapshotet, ikke selve Course/Slope/Par) —
ny migrasjon `021_round_participant_rating_snapshot.sql`. 26 scratch-
sjekker + en egen, ekte teeoff-live-oppslag-test (Beslutning C
bekreftet: ingen `course`-rad skrives noe sted). Rullet ut mot ekte
`teecup_db`/`teecup_api` 2026-07-22, `/health`/`/dashboard` → 200,
`teeoff.no` upåvirket. `frontend/next.config.mjs` fikk `/rounds`/
`/personal-courses` lagt til i `rewrites()` proaktivt (ikke deployet
ennå, ingen frontend bruker den før neste runde).
**Notat fra bruker, IKKE designet:** planer om å måle lengde på slag +
opplyse avstand til ulike punkter på banen (golf-GPS/rangefinder-type
funksjonalitet) — krever geografiske/GPS-data ingen kilde har i dag
(verken teeoff eller `personal_course`). Se FEATURE_BACKLOG.md for
full detalj.
**Frontend bygget og rullet ut 2026-07-23, samme rekkefølge-prinsipp
(engine → skjema → API → frontend) fullført:** tre nye,
organisasjonsuavhengige endepunkter lagt til i `rounds.py`
(`GET /rounds/official-search`/`{slug}` — samme mønster som
`courses.py` sitt org-scopede søk, men uten org-kontekst, siden
frittstående runder ikke har noen; `GET /personal-courses/{id}` —
detalj med utslag+kjønn, manglet fra søk-runden) og en fjerde,
nødvendig tilføyelse oppdaget UNDER frontend-designet: `RoundOut` bar
aldri hull-nivå-data i det hele tatt — ny
`GET .../participants/{id}/holes`. Hull-PATCH-endepunktet endret til å
returnere hele den oppdaterte raden i stedet for `{"ok": true}`.
**Reelt kontraktsfunn, bekreftet i scratch FØR frontend stolte på det:**
hull-PATCH er IKKE et ekte delvis-PATCH — den skriver ALLE felt ved
hvert kall, så et utelatt felt (f.eks. putts) nullstilles stille hvis
frontend ikke sender det. Løst ved at `round-detail.tsx` alltid slår
sammen med gjeldende hull-data før hver PATCH, aldri sender et isolert
feltnavn alene — verifisert eksplisitt med en egen scratch-test som
FØRST beviste nullstillings-oppførselen, DERETTER beviste at
merge-mønsteret unngår den.
Nye sider: `/rounds` (liste), `/rounds/new` (bane-kilde teeoff/egen,
søk-før-opprett for egen bane samme idé som org-banene, utslag filtrert
på brukerens registrerte kjønn, dato/starthull/hull-antall), `/rounds/
[id]` (deltaker-faner, hull-navigasjon fra starthull, slag/putt-
tallvelgere i samme stil som `session-scorecard.tsx` sin `StrokePicker`,
kølle/retning/innspill/chip/bunker/straffeslag bak en «flere detaljer»-
utvidelse, GIR utledet og vist klientside — aldri lagret, kun beregnet
fra `approach_result`+slag+putt ved lesing, fullfør-runde med HCP-
differensial-sammendrag). Lenket fra dashbordet som «Egne runder»
(bevisst adskilt navn fra det eksisterende «Mine runder», som gjelder
turnering-deltakelse — samme ord, to ulike konsepter).
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
isolert scratch-MinIO + engangs API-container, samme mønster som hele
økten ellers): 22 sjekker som dekker egendefinert-bane-opprettelse med
to utslag/kjønn, søk, detalj, PATCH-kontrakten (inkl. det bevisste
nullstillings-beviset over), GIR-derivering, gjest-fjerning,
kryss-bruker-autorisasjon (403), og full fullføring med differensial
— PLUSS en egen, separat test av hele teeoff-baserte opprettelsesløpet
mot den ekte kjørende `teeoff_api`-containeren (Borregaard Golfklubb),
som bekreftet course_handicap ble beregnet riktig. Ekte typesjekket
PRODUKSJONSBUILD (`docker build --target builder`, samme steg som
`Dockerfile` faktisk bruker) kjørt og bekreftet — alle nye ruter listet.
**Rullet ut live 2026-07-23**, bruker bekreftet eksplisitt: ingen
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
begge containere boot-et rent, `/health`/`/dashboard`/`/rounds` → 200,
`teeoff.no` upåvirket. **Frittstående rundeføring har dermed backend
OG frontend live** — se ADR-033 i ARCHITECTURE_DECISIONS.md for full
detalj om alle fire lagene (engine/skjema/API/frontend).
**Frontend ERSTATTET med V0-designet versjon, samme dag:** brukeren
påpekte berettiget at de tre skjermene over var hånd-kodet av meg i
stedet for designet i V0, som er prosjektets etablerte mønster for
ALL øvrig frontend. Bekreftet arbeidsmåten uendret (jeg skriver
V0-prompten, brukeren kjører den og sender koden tilbake, jeg
integrerer). Tre prompter skrevet (liste/opprett/hull-registrering,
inkl. eksplisitt tilgjengelighetskrav i hver), tre zip-eksporter
mottatt og diffet mot levende tre (samme "full re-eksport"-mønster som
alltid — kun `round-card.tsx`/`own-rounds.tsx`/`new-round.tsx`/
`round-detail.tsx` + rutene var reelt nye). Samme kjente V0-feil dukket
opp igjen og ble hoppet over (`app/clubs/[id]/page.tsx`, feilnavngitt
slug-parameter — identisk feil som ble rettet under ADR-018). Mine
hånd-bygde komponenter ERSTATTET (ikke supplert), datalag skrevet om
fra V0s mock til ekte fetch — samme mønster som enhver annen skjerm.
Full detalj (inkl. de fire reelle tilpasningene utover ren om-kabling)
i ADR-033. Ekte typesjekket produksjonsbuild kjørt og bekreftet, ingen
ny interaktiv nettleser-test (intet slikt verktøy tilgjengelig,
flagget eksplisitt). **Rullet ut live 2026-07-23**, bruker bekreftet
eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte
også `teecup_api` som vanlig bivirkning), begge containere boot-et
rent, `/health`/`/dashboard`/`/rounds`/`/rounds/new` → 200, `teeoff.no`
upåvirket. V0-zip-ene slettet fra prosjektroten.
**Reell produksjonsbug rapportert av bruker (2 skjermbilder) og
FIKSET samme dag:** `/rounds` var samtidig frontend-listesidens sti OG
backend-APIets ressursprefiks — en verre, begge-veier-variant av
ADR-016s medlemsside-felle. Statisk side vant over rewrite for det
eksakte `/rounds`-treffet (klientens `fetch("/rounds")`/`POST /rounds`
traff aldri backend, fikk Next sin egen HTML tilbake — derav "Klarte
ikke å hente rundene dine"/"Klarte ikke å opprette runden"), mens
rewriten vant over den DYNAMISKE `/rounds/[id]`-siden i motsatt
retning (selve rundedetalj-siden var dermed fullstendig uoppnåelig,
bekreftet direkte med `curl` før fiksen). Skjermbildets andre detalj —
utslagsnavn "55/50/44/32" — ble sjekket direkte mot ekte `teeoff_api`
og bekreftet Å IKKE VÆRE EN BUG (Tjøme Golfklubb sine faktiske
utslagsnavn, lengde i hundremeter). Fikset ved å flytte alle tre
frontend-rutene til et nytt, ikke-overlappende prefiks `/my-rounds/*`
— API-et uendret på `/rounds`. `next.config.mjs` sin advarsel utvidet
med denne nye varianten. Verifisert med `curl` mot ekte produksjon
BÅDE før og etter (anonymt `GET /rounds` → nå korrekt backend-JSON,
anonymt `GET /my-rounds/` → nå korrekt `text/html`, altså
endelig oppnåelig). Rullet ut live 2026-07-23, bruker bekreftet
eksplisitt, kun `teecup_frontend` (+ vanlig `teecup_api`-bivirkning),
`teeoff.no` upåvirket. Full detalj i ADR-033.
4. **Del 1 (fri, ukrevd sekundær-e-post) er nå BYGGET OG LIVE** (2026-07-21,
se status over). **Del 2 (ekte konto-sammenslåing) fortsatt IKKE
designet:** hva skjer hvis den ønskede adressen ALLEREDE tilhører en
annen, eksisterende konto (i dag avvist tydelig med 409 DUPLICATE i
stedet for gjettet på)? Trenger egen, separat designrunde — se
FEATURE_BACKLOG.md for full analyse av hvorfor dette er vesentlig
vanskeligere enn del 1.
5. **Deltaker-tilgang til lag-chat/scorekort OG HCP-historikk er nå BEGGE
BYGGET OG LIVE** (2026-07-21, se status over) — ADR-031s tidligere
noterte "naturlig neste steg"-punkter er dermed alle tettet, unntatt
notifikasjons-/aktivitetsfeed og "Mine runder" for rene påmeldinger
(begge fortsatt IKKE bygget, se FEATURE_BACKLOG.md).
6. ~~Oppfølgingspunkt fra PWA-runden: offline-scoreregistrering er ALDRI
browser-testet i praksis~~ — **ferdig 2026-07-28** (se status-punkt
samme dag, "Turnering-scorekortets offline-flyt (ADR-028) FAKTISK
browserverifisert"). Beholdt her kun for historikk.
7. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for
ADR-020, korrigering-godkjenning fra motpart, video/1-til-1-meldinger
(bevisst utsatt i ADR-025).
8. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/
Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå.
9. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt),
FORVENT en full re-eksport hver gang — diff mot live-treet i et
scratch-område før noe pakkes ut over eksisterende filer, og sjekk om
V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR
integrering.
10. **Resterende hull fra 2026-07-28-gjennomgangen av frittstående runder**
(det viktigste — faktisk HCP — er tettet, se ADR-038 over; offline-kø,
punkt (a), er nå OGSÅ tettet, se status 2026-07-28 over — punktet
beholdes her kun for historikk): (a) ~~offline-kø (ADR-028) er kun
koblet til turnering-scorekortet~~ — ferdig, browserverifisert og
live; (b) ~~ingen Stableford-poengberegning for frittstående runder
(kun rå slag/differensial), inkl. en uløst "plukket opp ballen"-
tilstand~~ — ferdig, scratch-/browserverifisert og live 2026-07-29,
se status over (`play_format='stableford'` + `round_hole.picked_up`,
migrasjon 038); (c) ~~rundedeling/visibility (ADR-036 fase 2, public/private/friends)
fortsatt ikke bygget~~ — ferdig, browser-/scratch-verifisert og live
2026-07-28, se status over (ADR-036 er dermed HELT ferdig, alle tre
faser); (d) ~~flere flighter i én frittstående runde fortsatt kun
drøftet~~ — ferdig, retning 1 (løs gruppering) bygget, scratch-/
browserverifisert og live 2026-07-28, se status over; (e) ~~varsler
koblet til venneforespørsler, ikke til rundehendelser ennå~~ —
ferdig, scratch-verifisert og live 2026-07-28, se status over
(medspiller lagt til/venn ser synlig runde/tilkoblet runde fullført).
Alle fem punktene (a)-(e) i denne listen er dermed ferdig.
11. **Ferdig, kun for historikk:** ekte spillformer (match/skins/fourball/
foursome/greensome/scramble) for frittstående runder — backend
(ADR-039 + minimums-spiller-håndhevelse) OG frontend (sideoppsett,
skins-konfig i `/my-rounds/new`, matchstatus-/skins-tavle-visning,
delt-ball-scorekort/-veiviser, `setup_complete`-gating) er nå BEGGE
bygget, browserverifisert og live (se status 2026-07-28). Mulig
fremtidig finpuss (ikke bedt om ennå): redigere et sidenavn i
etterkant (kun opprett/slett finnes i dag), en tydeligere
skins-poeng-forklaring i UI-et.
12. **Ferdig, kun for historikk:** de tre organisator-oppfølgingspunktene
(flytte spiller mellom lag, individuell rangering per økt, midlertidige
spillere + etter-runde-invitasjon) — alle bygget, scratch-/
browserverifisert og live 2026-07-28, se status over. Ingen nye åpne
spørsmål igjen fra denne runden.
13. **Ferdig, kun for historikk:** PWA-installasjonsoppfordring, flere
flighter i én frittstående runde (retning 1), scramble/greensome-
statistikk over valgt utslag (frittstående runder), og push-varsler
til telefonens OS (Web Push/VAPID) — alle fire bygget, scratch-/
browserverifisert og live 2026-07-28, se status over for full detalj.
Gjenstående, IKKE bygget: samme scramble-utslagsstatistikk for
org-scopede turneringer (bevisst utenfor omfang, se
FEATURE_BACKLOG.md), ekte OS-nivå push-levering ikke bevist med en
reell innvilget tillatelse (automatisert testmiljø hadde
`Notification.permission` forhåndssatt til "denied") — bruker bør
selv teste push på en ekte enhet.