Begge bugs er fikset og live:

Dashbord-hilsen — bruker nå fornavn (me.first_name), ikke fullt navn.
Stroke-scoring-krasj på hulless bane — en bane uten registrerte hull ga en rå 500-krasj ved scoreinnsending; gir nå en ren 400 VALIDATION_FAILED.
Gikk grundig gjennom loggen og fant at nesten alt annet merket "IKKE fikset" faktisk var løst i senere runder (bare stale seksjonsoverskrifter) — rettet to av dem samtidig. Ingen andre genuint åpne små bugs igjen i dokumentasjonen.
This commit is contained in:
Erol Haagenrud 2026-07-26 07:54:00 +02:00
parent 9987c93e18
commit a09fea3caa
6 changed files with 475 additions and 11 deletions

View file

@ -27,7 +27,13 @@
"Bash(curl -s -o /dev/null -w \"https://teecup.teeoff.no/dashboard -> %{http_code}\\\\n\" https://teecup.teeoff.no/dashboard)", "Bash(curl -s -o /dev/null -w \"https://teecup.teeoff.no/dashboard -> %{http_code}\\\\n\" https://teecup.teeoff.no/dashboard)",
"Bash(curl -s -o /dev/null -w \"https://teecup.teeoff.no/my-notifications -> %{http_code}\\\\n\" https://teecup.teeoff.no/my-notifications)", "Bash(curl -s -o /dev/null -w \"https://teecup.teeoff.no/my-notifications -> %{http_code}\\\\n\" https://teecup.teeoff.no/my-notifications)",
"Bash(curl -s -o /dev/null -w \"https://teeoff.no/ -> %{http_code}\\\\n\" https://teeoff.no/)", "Bash(curl -s -o /dev/null -w \"https://teeoff.no/ -> %{http_code}\\\\n\" https://teeoff.no/)",
"Bash(curl -s https://teecup.teeoff.no/notifications)" "Bash(curl -s https://teecup.teeoff.no/notifications)",
"Bash(grep -n \"God dag, {me.display_name}\" /opt/teecup/frontend/components/dashboard.tsx)",
"Bash(grep -n \"@router\\\\.\\\\\\(post\\\\|get\\\\\\)\\(\\\\s*$\\\\|@router\\\\.\\\\\\(post\\\\|get\\\\\\)\\(\\\\\"\" /opt/teecup/app/routers/tournaments.py)",
"Bash(python3 test_holeless_course_crash.py)",
"Bash(sed -i 's/check\\(\"normal course: hole-score submit still succeeds \\(regression\\)\", status == 200\\)/check\\(\"normal course: hole-score submit still succeeds \\(regression\\)\", status == 201\\)/' test_holeless_course_crash.py)",
"Bash(sed -i 's/if status != 200:/if status != 201:/' test_holeless_course_crash.py)",
"Bash(sed -i 's/check\\(\"normal course: second side'\"'\"'s hole-score submit succeeds\", status == 200\\)/check\\(\"normal course: second side'\"'\"'s hole-score submit succeeds\", status == 201\\)/' test_holeless_course_crash.py)"
] ]
} }
} }

View file

@ -233,6 +233,17 @@ ikke to lag i det hele tatt) passer IKKE inn i denne ADR-ens to-lags-modell —
en fremtidig, egen ADR trengs når/hvis dette tas fatt på, samme mønster som en fremtidig, egen ADR trengs når/hvis dette tas fatt på, samme mønster som
knockout-punktet over. Kun notert her ennå, ikke designet eller bygget. knockout-punktet over. Kun notert her ennå, ikke designet eller bygget.
**Merk (2026-07-26), presisert og utvidet:** brukeren bekreftet at ønsket
er STØRRE enn Københavner som ett format blant flere — TeeCup skal etter
hvert støtte ekte INDIVIDUELLE turneringer (helt uten lag) som eget
generelt tilfelle, disse skal kunne gå over FLERE RUNDER, og det skal
være mulig å sette opp et Order of Merit (sesong-sammenlagt på tvers av
flere separate arrangementer). Full analyse, inkl. en reell strukturell
kollisjon med ADR-033 sitt bevisst org-uavhengige rundesystem, i
FEATURE_BACKLOG.md ("Utvidelse 2026-07-26: individuelle turneringer,
flerrunde-turneringer, og Order of Merit"). Ren notat-runde, ingen ADR
skrevet ennå.
--- ---
## ADR-012 — To scoring-moduser per økt ## ADR-012 — To scoring-moduser per økt
@ -3060,6 +3071,156 @@ FEATURE_BACKLOG.md for full detalj.
--- ---
## ADR-037: Individuelle turneringer, flerrunde-turneringer — grunnstruktur
**Kontekst:** reist 2026-07-26, som følge av en avklaringsrunde om et
ønsket turnering-leaderboard (se FEATURE_BACKLOG.md sin "Utvidelse
2026-07-26"-seksjon for hele forhistorien). TeeCup skal etter hvert
kunne arrangere turneringer for INDIVIDUELLE spillere (f.eks.
«Københavner» — se FEATURE_BACKLOG.md sitt eget punkt om flere
turneringsformater), ikke bare dagens Ryder Cup-lagformat (ADR-011),
og disse skal kunne gå over FLERE RUNDER med sammenlagt resultat. Et
fremtidig Order of Merit (sesong-sammenlagt på tvers av flere
turneringer, brukerens eksempel: "klubbdager") er identifisert som et
beslektet, men separat, SISTE steg — se eget avsnitt nederst, ikke
designet i denne runden.
Denne ADR-en dekker KUN grunnstrukturen (datamodell-plassering,
turnering-type, flerrunde-støtte, poengmodell). Selve de fem konkrete
formatene (Københavner, High-low-high, Robbins, Try all,
Flaggturnering) fra FEATURE_BACKLOG.md designes hver for seg OVENPÅ
denne grunnstrukturen, ikke i denne runden.
**Viktig presisering underveis, som endrer en tidligere antakelse
(2026-07-25-runden om "flere flighter i én frittstående runde"):** i en
formell, org-arrangert individuell turnering er "flight" KUN en
tee-tid-/spilletempo-gruppering (som i dag for lag-turneringer) — IKKE
en leaderboard-grense. Leaderboardet spenner alltid HELE feltet,
uavhengig av hvem som spilte sammen. Dette er strukturelt ulikt den ad
hoc "flere flighter i en frittstående runde"-ideen (der leaderboardet
bevisst skal avgrenses til det man selv satte opp) — de to
"flight"-begrepene ligner i UI, men er IKKE samme konsept. Holdes
bevisst adskilt, ikke forent, selv om forrige runde antydet det motsatte.
### Beslutning A — Ny, parallell org-scopet datamodell (IKKE gjenbruk av `round`)
En individuell/flerrunde-turnering får sin EGEN, RLS-beskyttede
tabellstruktur under `organization_id` — IKKE en utvidelse av de
frittstående rundetabellene (`round`/`round_participant`/`round_hole`,
ADR-033).
**Begrunnelse:** ADR-033 Beslutning A var et BEVISST valg om at
frittstående runder er 100 % org-uavhengige (`plain_connection()`,
ingen RLS i det hele tatt — autorisasjon håndheves med
`WHERE owner_user_id = $1` i app-laget). Å gi `round` en valgfri
`organization_id`/`tournament_id` ville krevd HYBRID RLS (håndhevet kun
når organisasjon er satt) — en helt ny klasse sikkerhetslogikk som
IKKE finnes noe sted ellers i systemet i dag (`organization_id på ALLE
domenetabeller, håndhevet av RLS` er en ufravikelig invariant i
CLAUDE.md — en betinget/nullbar variant ville vært det FØRSTE
unntaket). Dette prosjektet har allerede én dokumentert hendelse
(RLS-tomstreng-bugen, se CLAUDE.md-status 2026-07-16) som kom av
nettopp denne typen RLS-finesse — vi unngår bevisst å introdusere en
ny variant av samme risikoklasse. Noe skjema dupliseres (hull-for-hull-
registrering ligner mye på `round_hole`), men den delte regnelogikken
(`handicap_engine.py`) er allerede rammeverk-uavhengig og gjenbrukes
uendret uansett hvilken tabell dataene ligger i.
### Beslutning B — Samme `tournament`-tabell, ny type-diskriminator
`tournament` får en ny kolonne, f.eks. `format_type` (`'team'` |
`'individual'`, default `'team'` for bakoverkompatibilitet med alle
eksisterende rader).
**Begrunnelse:** en individuell turnering trenger fortsatt navn, status,
datoer, synlighet (ADR-018), invitasjonskode (ADR-020), org-eierskap —
ALT dette er allerede bygget og fungerer uendret på `tournament`-raden,
uavhengig av format. Å lage en helt ny toppnivå-entitet ville betydd å
gjenoppbygge synlighet/join-kode/landingssider fra bunnen for et andre
system — ren duplisering uten reell gevinst. Lag-tabellene
(`team`/`team_roster`) blir ganske enkelt ubrukte for
`format_type = 'individual'` — håndheves i app-laget (samme mønster som
ADR-011s to-lags-grense), ikke med en tung DB-constraint på tvers av
tabeller.
### Beslutning C — Flerrunde via en ny, økt-lignende tabell
Individuelle turneringer kan ha FLERE runder fra start, via en ny
`tournament_round`-tabell (org+tournament-scopet, samme rolle som
`session` har for lag-turneringer i dag — dato/bane/hullomfang per
runde). Sammenlagt resultat på tvers av rundene i turneringen summeres
ved lesing, SAMME "summer på ekte deltaker-id, aldri på en løs
side-label"-prinsipp som det eksisterende lag-leaderboardet allerede
bruker (`fetch_leaderboard`, `app/routers/tournaments.py`) for å unngå
å blande sammen feil rader.
**Bane-referanse:** `tournament_round.course_id` peker til den
EKSISTERENDE org-scopede `course`-tabellen (samme som `session` bruker
i dag) — INGEN snapshot-mekanisme som i frittstående runder
(ADR-033s `course_name_snapshot`). Organisasjonens egen banedata er
allerede stabil, admin-kontrollert data; snapshot-behovet i ADR-033 kom
av at frittstående runder leser LIVE fra en ekstern, ikke-org-kontrollert
kilde (teeoff) — samme begrunnelse gjelder ikke her.
**Deltaker-identitet:** en ny `tournament_participant`-tabell (org+
turnering-scopet, samme rolle som `team_roster` — refererer til
EKSISTERENDE `player`-tabellen, fryser `handicap_index_snapshot` ved
uttak, samme reproduserbarhetsprinsipp som ADR-007) — IKKE en ny,
frittstående gjeste-modell slik `round_participant` har. En turnering
har alltid en organisator som administrerer en kjent spillerpool
(samme som lag-turneringer i dag), ulikt en privatpersons frittstående
runde.
### Beslutning D — Rå slag lagres, poeng caches per format (samme mønster som match-play)
Hver `tournament_round`-deltaker sin hull-for-hull-registrering lagrer
RÅ BRUTTO SLAGTALL per hull (samme grunnform som `round_hole.score`) —
scorekortet trenger dette uansett, og det gir handlefrihet til å vise
flere ulike visninger (brutto/netto/stableford/Københavner-poeng) av
SAMME underliggende data.
I TILLEGG caches et FERDIG UTREGNET poengtall per format — samme
etablerte mønster som `match.status_text`/`points_side_a/b` i dag:
motoren (`handicap_engine.py`, ny funksjon per scoringsmetode) regner
poeng ved HVER hull-innsending, resultatet lagres i en egen kolonne
(f.eks. på en ny `tournament_round_score`-rad, én per deltaker per
runde) — rask leaderboard-lesing uten å måtte regne ut alt på nytt for
hele feltet ved hver visning, konsistent med hvordan matchstatus
allerede caches og regnes på nytt ved hver innsending
(`recompute_and_cache_match_state`).
`tournament.scoring_method` (eller ev. per `tournament_round`, hvis et
fremtidig format trenger å blande metoder — ikke avklart, default: fast
per turnering) avgjør HVILKEN motorfunksjon som brukes til å regne
poeng fra de rå slagene — ny CHECK-verdi + ny ren funksjon i
`handicap_engine.py` per format som legges til (`stroke_play_gross`,
`stroke_play_net`, `stableford`, `copenhagen_points`, ...). Hvert nytt
format fra FEATURE_BACKLOG.md sin liste blir dermed i hovedsak: én ny
motorfunksjon (testet isolert, samme "test i isolasjon FØR resten"-
prinsipp som ADR-005) + én ny CHECK-verdi, ikke en skjemaendring.
### Konsekvens — hva denne ADR-en IKKE avgjør ennå
- **De fem konkrete formatene** (Københavner, High-low-high, Robbins,
Try all, Flaggturnering) — hver trenger sin egen, mindre design-runde
oppå denne grunnstrukturen (poengformel, evt. spesielle
paringsregler for High-low-high/Robbins som IKKE er rent individuelle
poeng-per-hull).
- **Order of Merit** (sesong-sammenlagt på tvers av FLERE separate
turneringer). Bekreftet av brukeren som et beslektet, men SISTE steg
— forutsetter at individuelle turneringer med et poengresultat
finnes først. Krever sannsynligvis et helt nytt overordnet konsept
(en "sesong"/"serie", org-scopet, RLS som ellers) som grupperer flere
`tournament`-rader og akkumulerer poeng — IKKE designet i denne
runden.
- **Migrasjon/kode** — denne ADR-en er ren struktur-beslutning, ingen
migrasjon er skrevet ennå. Neste steg er å legge frem et konkret
migrasjonsutkast (nye tabeller + `tournament.format_type`-kolonne) til
gjennomgang før noe kjøres, samme "vis planen FØR noe skrives"-mønster
som ellers i prosjektet.
---
## Åpne spørsmål (ikke besluttet ennå) ## Åpne spørsmål (ikke besluttet ennå)
Disse må avklares før eller under de relevante fasene: Disse må avklares før eller under de relevante fasene:
@ -3080,6 +3241,17 @@ Disse må avklares før eller under de relevante fasene:
5. **Individuell-vs-delt-ball i `hole_score`:** Håndheves i app-laget, ikke av 5. **Individuell-vs-delt-ball i `hole_score`:** Håndheves i app-laget, ikke av
databasen (CHECK når ikke opp til `session.format`). Motoren/API-et må passe databasen (CHECK når ikke opp til `session.format`). Motoren/API-et må passe
på at f.eks. et foursome ikke får per-spiller-scorer. på at f.eks. et foursome ikke får per-spiller-scorer.
6. **Individuelle turneringer, flerrunde-turneringer og Order of Merit**
(reist 2026-07-26). **Grunnstruktur for de to første AVKLART samme
dag, se ADR-037:** ny, parallell org-scopet datamodell (IKKE en
utvidelse av ADR-033s `round`-tabeller), samme `tournament`-tabell
med ny `format_type`-diskriminator, flerrunde via ny
`tournament_round`-tabell, rå slag lagres + poeng caches per format
(samme mønster som match-play). **Fortsatt åpent:** de fem konkrete
formatenes egne poengregler (Københavner m.fl.), og Order of Merit
(sesong-sammenlagt på tvers av flere turneringer) — bekreftet som et
beslektet, men SISTE steg, ikke designet ennå. Ingen migrasjon
skrevet — ADR-037 er ren struktur-beslutning.
--- ---

127
CLAUDE.md
View file

@ -62,6 +62,24 @@ Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber.
annen runde — ingen egen stor retrofit-runde er igangsatt eller bedt annen runde — ingen egen stor retrofit-runde er igangsatt eller bedt
om ennå. om ennå.
## Navneformat (ufravikelig, gjelder ALT, eksisterende og fremtidig)
- Brukeren instruerte eksplisitt 2026-07-26: når TeeCup kommuniserer
DIREKTE TIL brukeren (adresserer dem, "snakker til" dem) — f.eks. en
dashbord-hilsen ("God morgen Erol") eller en e-post/varsel rettet til
mottakeren selv — skal KUN FORNAVN brukes, aldri fullt navn.
- Til vanlig (lister, roster, chat-forfatter, "fjern X"-bekreftelser,
administrasjonsvisninger, alt som IKKE er direkte adressering) brukes
fortsatt fullt navn, eller initial(er)+etternavn, eller annet som er
nødvendig for å skille personer fra hverandre — ikke fornavn alene der.
- Samme STÅENDE forventning som tilgjengelighetsregelen over: gjelder
fremtidig arbeid direkte, og rettes opportunistisk når en skjerm
likevel røres — ingen egen retrofit-runde igangsatt.
- **✅ FIKSET 2026-07-26** (samme dag, i "fiks alle kjente små bugs"-
runden): dashbordets hilsen brukte fullt navn — rettet til
`me.first_name ?? me.display_name` (`/auth/me` eksponerte allerede
`first_name` separat, ingen backend-endring nødvendig). Se
CLAUDE.md-status lenger ned for full detalj.
## Arbeidsmåte ## Arbeidsmåte
- Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før - Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før
du går videre. du går videre.
@ -2855,6 +2873,115 @@ Ferdig og verifisert:
FastAPI (ikke bare at Next.js svarte): anonymt `GET /notifications` FastAPI (ikke bare at Next.js svarte): anonymt `GET /notifications`
over ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. 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.
Neste steg: Neste steg:
0. **Venter på brukerens bekreftelse for utrulling (2026-07-26):** 0. **Venter på brukerens bekreftelse for utrulling (2026-07-26):**
varsler (in-app varslingssenter) + leaderboard-BACKENDEN for varsler (in-app varslingssenter) + leaderboard-BACKENDEN for

View file

@ -574,6 +574,101 @@
handicap-/slagfordelingslogikken for disse fire formatene, se CLAUDE.md handicap-/slagfordelingslogikken for disse fire formatene, se CLAUDE.md
sin "Autoritative kilder"-seksjon. sin "Autoritative kilder"-seksjon.
### Utvidelse 2026-07-26: individuelle turneringer, flerrunde-turneringer, og Order of Merit
Reist av brukeren som svar på et spørsmål om et individuelt
turnering-leaderboard — svaret avdekket at ønsket er STØRRE enn bare
Københavner som ett format blant flere: TeeCup skal etter hvert kunne
arrangere ekte INDIVIDUELLE turneringer (ikke bare lagturneringer), disse
skal kunne gå over FLERE RUNDER, og det skal være mulig å sette opp et
**Order of Merit** — sesong-sammenlagt poeng/rangering på tvers av flere
separate arrangementer (brukerens eget eksempel: "klubbdager" som gjentas
gjennom en hel sesong, med en løpende sammenlagt-tabell). **Ren notat-
runde, ingen kode skrevet, ingen ADR skrevet ennå** — brukeren ba
eksplisitt om at dette kun noteres nå.
Tre distinkte, men beslektede strukturelle spørsmål — bevisst holdt fra
hverandre siden de har ulik arkitektonisk tyngde:
1. **Individuelle turneringer (ingen lag i det hele tatt).** ADR-011
låser v1 til NØYAKTIG to lag — en individuell turnering (et flatt felt
av spillere, som Københavner) bryter denne forutsetningen helt, ikke
bare "trenger flere enn to lag". Dette er allerede notert i ADR-011
sitt eget "Merk (2026-07-19)"-avsnitt via Københavner-eksemplet, men
brukerens presisering nå gjør det tydelig at individuelle turneringer
er et EGET, generelt tilfelle — ikke bare én formatvariant blant
fem. Trenger en egen ADR som avklarer om dette blir en helt egen
turnering-TYPE (parallell til dagens to-lags-type, med sin egen
`match`/scoring-modell) eller en utvidelse av eksisterende modell.
2. **Flerrunde-turneringer (samme arrangement, flere runder/dager,
sammenlagt resultat).** Dagens lagturneringer har ALLEREDE flere
`session`-er (f.eks. en Ryder Cup-helg med økter fredag/lørdag/søndag)
— men poengsummeringen er PER MATCH innad i hver økt, ikke en
sammenlagt SLAGSUM på tvers av runder slik en individuell
flerrunde-turnering (f.eks. en 3-dagers slagspillturnering) ville
trengt. **Reell strukturell kollisjon å avklare:** frittstående runder
(ADR-033 sin `round`/`round_participant`/`round_hole`, Beslutning A)
er BEVISST bygget helt UTENFOR organisasjon/RLS-systemet
(`plain_connection()`, eid av `user_id`, ingen `organization_id` i
det hele tatt) — mens turneringer er strengt org-scopet og RLS-
beskyttet. En org-arrangert flerrunde-individuell-turnering trenger
runder som lever INNENFOR en organisasjons/turnerings-kontekst — dette
er IKKE det samme systemet som de personlige rundene, selv om
datamodellen (hull-for-hull-registrering) sannsynligvis ligner mye.
Må avklares eksplisitt: gjenbruke `round`-tabellene (utvidet med en
valgfri turnering-/org-kobling), eller bygge en parallell, org-scopet
rundemodell? Dette er trolig den vanskeligste enkeltbeslutningen av de
tre.
3. **Order of Merit (sesong-sammenlagt på tvers av FLERE separate
turneringer/arrangementer).** Krever et HELT NYTT overordnet konsept
som ikke finnes i skjemaet i dag — noe a la en "sesong" eller "serie"
som grupperer flere separate turnering-rader og akkumulerer poeng per
spiller på tvers av dem, med sin egen løpende sammenlagt-rangering.
Forutsetter sannsynligvis at (1) og (2) over er løst først (det er
individuelle arrangementer som skal telle inn i et Order of Merit,
ikke lag-baserte Ryder Cup-turneringer) — naturlig SISTE steg av de
tre, ikke noe som kan designes isolert.
**Ingenting av dette er designet eller bygget** — kun fanget presist her
slik at retningen er dokumentert før noe glemmes. Se også ADR-011 sitt
eget notat (samme sak, kortere) og "Åpne spørsmål"-listen i
ARCHITECTURE_DECISIONS.md.
### Oppdatering 2026-07-26, samme dag: grunnstruktur for (1) og (2) AVKLART — se ADR-037
Brukeren ba om å starte ADR-runden på strukturspørsmålet direkte. Fire
load-bærende beslutninger avklart eksplisitt (AskUserQuestion), full
begrunnelse i **ADR-037** (ARCHITECTURE_DECISIONS.md):
- **Ny, parallell org-scopet datamodell** — IKKE en utvidelse av
ADR-033s `round`-tabeller (unngår hybrid/betinget RLS, bevarer et
tidligere bevisst valg).
- **Samme `tournament`-tabell**, ny `format_type`-diskriminator
(`'team'`/`'individual'`) — gjenbruker synlighet/join-kode/status
helt uendret.
- **Flerrunde fra start**: ny `tournament_round`-tabell (økt-lignende),
sammenlagt resultat summert ved lesing på ekte deltaker-id (samme
prinsipp som det eksisterende lag-leaderboardet).
- **Rå slag lagres OG poeng caches per format** — samme mønster som
`match.points_side_a/b` i dag. Nye formater (Københavner m.fl.) blir
dermed i hovedsak: én ny motorfunksjon i `handicap_engine.py` + én ny
CHECK-verdi, ikke en skjemaendring.
**Viktig presisering som oppsto underveis:** "flight" i en formell
org-turnering er KUN en tee-tid-gruppering, IKKE en leaderboard-grense
(leaderboardet spenner alltid hele feltet) — ULIKT den ad hoc
"flere flighter i en frittstående runde"-ideen (der leaderboardet
bevisst er avgrenset til det man selv satte opp). De to holdes bevisst
ADSKILT nå, ikke forent slik forrige runde antydet — se egen seksjon
lenger ned i denne filen ("Frittstående runder: flere flighter...").
**Fortsatt IKKE avgjort:** de fem konkrete formatenes egne poengregler
(punkt utenfor denne strukturrunden), og (3) Order of Merit — bekreftet
som naturlig SISTE steg, ikke designet. **Ingen migrasjon skrevet**
ADR-037 er ren struktur-beslutning, neste steg er et konkret
migrasjonsutkast til gjennomgang.
--- ---
## Varsler: push til telefon + in-app varslingssenter — 📋 DESIGNET 2026-07-25, IKKE bygget ## Varsler: push til telefon + in-app varslingssenter — 📋 DESIGNET 2026-07-25, IKKE bygget
@ -1355,16 +1450,23 @@ en reell skjemamigrasjon (`014_tee_gender_to_rating.sql`).
IDENTISK bruttoscore (5-5) på begge sider ga et ikke-delt resultat IDENTISK bruttoscore (5-5) på begge sider ga et ikke-delt resultat
("a" vant, ikke "halved") — direkte bevis på at handicap nå faktisk ("a" vant, ikke "halved") — direkte bevis på at handicap nå faktisk
brukes. Ingen migrasjon (ren Python-logikk-fiks). brukes. Ingen migrasjon (ren Python-logikk-fiks).
**Ekte, urelatert bug funnet UNDER samme scratch-test, IKKE fikset **✅ FIKSET 2026-07-26** (i en egen "fiks alle kjente små bugs"-runde,
ennå:** et stroke-modus scoreinnsending på en bane UTEN registrerte lenge etter oppdagelsen over): et stroke-modus scoreinnsending på en
hull (`hole`-tabellen tom) krasjer med en rå 500 bane UTEN registrerte hull (`hole`-tabellen tom) krasjet med en rå 500
(`IndexError: list index out of range` i `handicap_engine.py` sin (`IndexError: list index out of range` i `handicap_engine.py` sin
`allocate_over_played_holes`, kalt fra `scoring.py` sin `allocate_over_played_holes`, kalt fra `scoring.py` sin
`_compute_hole_results`) i stedet for en tydelig `VALIDATION_FAILED`. `_compute_hole_results`) i stedet for en tydelig `VALIDATION_FAILED`.
Samme klasse feil som den allerede kjente/fikset Fikset akkurat som foreslått da bugen ble oppdaget: eksplisitt sjekk
manglende-handicap-indeks-krasjen fra blind draw-runden (2026-07-18) — (`len(all_18_si) != 18`) rett før `allocate_over_played_holes` kalles,
bør fikses likt (eksplisitt sjekk FØR beregning, ikke en try/except ikke en try/except rundt symptomet — samme mønster som den allerede
rundt symptomet). Ikke fikset i denne runden, kun oppdaget og notert. kjente/fikset manglende-handicap-indeks-krasjen fra blind draw-runden
(2026-07-18). Scratch-verifisert presist (5/5 sjekker): en bane UTEN
hull gir nå ren `400 VALIDATION_FAILED` ved hull-scoreinnsending i
stedet for 500, OG en regresjonssjekk bekreftet at en NORMAL bane
(med alle 18 hull) fortsatt scorer helt uendret (begge sider, full
scorekort-henting). `test_isolation.sql` 12/12 uendret (ren
Python-logikk-fiks, ingen migrasjon). Rullet ut sammen med
dashbord-hilsen-fiksen under, se CLAUDE.md-status.
**Designspørsmålene for punkt 2 er avklart** (bruker valgte det anbefalte **Designspørsmålene for punkt 2 er avklart** (bruker valgte det anbefalte
alternativet på begge, se ADR-029 Beslutning B/C): helautomatisk tee-valg alternativet på begge, se ADR-029 Beslutning B/C): helautomatisk tee-valg
@ -1381,7 +1483,7 @@ sammenslåingen.
--- ---
## Rapporterte UI-/UX-hull (2026-07-19) — notert, IKKE fikset ennå ## Rapporterte UI-/UX-hull (2026-07-19) — ✅ ALLE FIRE FIKSET (siste 2026-07-21)
Fire punkter rapportert av brukeren fra faktisk bruk av `teecup.teeoff.no`. Fire punkter rapportert av brukeren fra faktisk bruk av `teecup.teeoff.no`.
Root cause funnet ved kodegjennomgang for de tre første (ikke bare gjettet); Root cause funnet ved kodegjennomgang for de tre første (ikke bare gjettet);
@ -2084,7 +2186,7 @@ første er tryggere (ingen delvis fullført tilstand ved feil midtveis).
--- ---
## Frittstående runder: flere flighter i én "vanlig" runde — 📋 NOTERT 2026-07-25, IKKE bygget ## Frittstående runder: flere flighter i én "vanlig" runde — 📋 NOTERT OG PRESISERT 2026-07-25/26, IKKE besluttet eller bygget
Reist av brukeren samme runde: eksempel gitt — "jeg går i den første Reist av brukeren samme runde: eksempel gitt — "jeg går i den første
flighten sammen med tre andre, mens fire venner går i flighten bak." flighten sammen med tre andre, mens fire venner går i flighten bak."
@ -2115,6 +2217,53 @@ spørsmål. Henger dessuten sammen med det pågående ADR-036-arbeidet
ADR-036 er bygget, siden "hvem er i min flight" og "hvem er min venn/ ADR-036 er bygget, siden "hvem er i min flight" og "hvem er min venn/
medspiller" er beslektede, men ikke identiske spørsmål. medspiller" er beslektede, men ikke identiske spørsmål.
### Presisert 2026-07-26 (fortsatt IKKE besluttet, kun tydeligere)
Brukeren presiserte tre konkrete ting ved oppfølging:
1. **Oppsettflyten er ÉN handling utført av ÉN person.** "Når jeg setter
opp en vanlig runde kan jeg sette opp for bare meg, for inntil 3 andre
i samme flight, eller for flere flighter." Det er brukeren selv som
tar ansvar for å sette opp ALLE flightene (også vennenes, i eksemplet)
— ikke at hver flight settes opp uavhengig av sine egne deltakere.
Speiler dagens gjest-mønster (eieren legger til gjester), bare
utvidet til å dekke flere adskilte flight-grupper i samme handling.
2. **Fremtidig, ikke motstridende:** "det er selvfølgelig de i flightene
bak som må føre sin egen score" — bekrefter at ansvaret for å SETTE
OPP og ansvaret for å REGISTRERE SCORE er to forskjellige ting, og at
det andre (scoreregistrering per flight) forventes løst av ekte
medspillere med konto (ADR-036 fase 3), ikke av oppsetteren manuelt.
3. **Leaderboard-omfanget er PRESIST det som ble satt opp SAMMEN, ikke
"alle som spilte samme bane samme dag":** brukerens eget eksempel —
setter Alice opp en flight med fire, og vennene deres setter opp en
HELT ANNEN flight med fire (uavhengig av Alices oppsett), skal disse
to leaderboardene IKKE slås sammen. Kun flightene som ble satt opp
SAMMEN i én handling deler leaderboard.
**Konsekvens for de to retningene:** punkt 3 er et sterkt signal FOR
retning 1 (løs gruppering av separate `round`-rader, bundet sammen av et
tynt "satt opp sammen"-konsept som samtidig er leaderboardets naturlige
omfang) — retning 2 (én `round` som beholder) ville krevd en ekstra
mekanisme for AKKURAT denne avgrensningen uansett, siden "alle som
spilte samme bane samme dag" aldri var riktig omfang i utgangspunktet.
**Fortsatt IKKE en endelig beslutning** — brukeren utforsket/presiserte
forståelse, bekreftet ikke eksplisitt en byggeretning.
**Brukerens egen observasjon, viktig å fange presist:** "jeg ser veldig
godt at jeg opererer i grenseland mellom frittstående runde og
turneringer her." Dette pekte først mot en mulig forening med
"Individuelle turneringer, flerrunde-turneringer og Order of Merit" —
**men presisert og IKKE lenger antatt samme konsept, se ADR-037
(2026-07-26):** i en formell org-turnering er "flight" KUN en
tee-tid-gruppering, og leaderboardet spenner alltid HELE feltet
uavhengig av hvem som spilte sammen. Her, i den ad hoc frittstående
runden, skal leaderboardet AVGRENSES til nøyaktig det som ble satt opp
sammen. De to "flight"-begrepene ligner i UI (flere grupper spiller
samme dag), men er strukturelt ulike leaderboard-omfang — holdes derfor
bevisst ADSKILT (to separate, mindre systemer), ikke forent til ett.
Denne seksjonens egen retning (løs gruppering av separate `round`-rader,
se over) står fortsatt som anbefaling, uendret av presiseringen.
--- ---
## Frittstående rundeføring + detaljert statistikk (uten turnering/organisasjon) — ✅ BACKEND + FRONTEND LIVE, løpende oppfølging t.o.m. 2026-07-25 (se ADR-033 for full detalj per dag) ## Frittstående rundeføring + detaljert statistikk (uten turnering/organisasjon) — ✅ BACKEND + FRONTEND LIVE, løpende oppfølging t.o.m. 2026-07-25 (se ADR-033 for full detalj per dag)

View file

@ -131,6 +131,15 @@ async def _compute_hole_results(conn, match_id: str, match) -> list[HoleResult]:
match["course_id"], match["course_id"],
) )
all_18_si = [r["stroke_index"] for r in stroke_index_rows] all_18_si = [r["stroke_index"] for r in stroke_index_rows]
# Uten alle 18 hull (f.eks. en egendefinert bane der hull aldri ble lagt
# til) kan slagfordelingen (allocate_over_played_holes) ikke gjøres --
# avvis tydelig FØR beregning i stedet for å krasje rått på tom liste.
if len(all_18_si) != 18:
raise app_error(
400,
"VALIDATION_FAILED",
"Banen mangler registrerte hull -- kan ikke beregne slagfordeling for stroke-modus.",
)
strokes_per_hole: dict[str, dict[int, int]] = { strokes_per_hole: dict[str, dict[int, int]] = {
unit: dict(zip(played, allocate_over_played_holes(total, all_18_si, played))) unit: dict(zip(played, allocate_over_played_holes(total, all_18_si, played)))

View file

@ -68,6 +68,7 @@ type Me = {
id: string id: string
email: string email: string
display_name: string display_name: string
first_name: string | null
preferred_locale: string preferred_locale: string
profile_complete: boolean profile_complete: boolean
handicap_index: number | null handicap_index: number | null
@ -374,7 +375,7 @@ export function Dashboard() {
<main className="mx-auto w-full max-w-3xl flex-1 px-5 pb-16 pt-6 sm:pt-8"> <main className="mx-auto w-full max-w-3xl flex-1 px-5 pb-16 pt-6 sm:pt-8">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h1 className="text-2xl font-extrabold tracking-tight text-foreground text-balance"> <h1 className="text-2xl font-extrabold tracking-tight text-foreground text-balance">
God dag, {me.display_name} God dag, {me.first_name ?? me.display_name}
</h1> </h1>
<p className="text-base leading-relaxed text-muted-foreground text-pretty"> <p className="text-base leading-relaxed text-muted-foreground text-pretty">
Klar for en ny runde? Her er golfen din ett sted. Klar for en ny runde? Her er golfen din ett sted.