# CLAUDE.md — arbeidsinstruks for TeeCup

Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber.

## Autoritative kilder (les før du gjør noe)
- `ARCHITECTURE_DECISIONS.md` — hva som er bestemt og hvorfor (ADR-001…029). Fasit.
- `FEATURE_BACKLOG.md` — hva som gjenstår, hva som er utsatt, hva som mangler.
- Endres en beslutning: legg til en ny ADR, ikke slett historikk. Hold begge
  filene oppdatert når noe avgjøres.
- **Regelverk for HCP/slagfordeling — tre PDF-er lastet opp av brukeren til
  prosjektroten 2026-07-19** (ikke innsjekket i git, kun lokale filer på
  serveren): `spilletyper-og-spilleformer-2023.pdf`,
  `Live Tourney _ A Guide to Handicap Scoring in Golf for Tournaments.pdf`,
  `SCGA Club Digest.pdf`. Brukeren: disse tre gir til sammen en tydelig
  beskrivelse av hvordan HCP og mottatte/tildelte slag skal beregnes/
  fordeles. Les disse FØR videre arbeid med `handicap_engine.py`,
  `app/handicap.py`, allowance-strategier (ADR-005/014) eller
  slagfordeling (ADR-008) — spesielt relevant for de fire
  turneringsformatene som ennå ikke er designet (Københavner/High-low-high/
  Robbins/Try all, se FEATURE_BACKLOG.md), siden disse dokumentene trolig
  dekker akkurat de reglene som trengs der.

## Sikkerhetsregler (ufravikelige)
- Rør ALDRI `teeoff`-databasen eller den ekte `teecup_db` uten at brukeren
  eksplisitt har bekreftet det i samme økt. Test alltid migrasjoner mot en egen
  scratch-database først, og rydd opp etterpå.
- Vis planen (hvilke kommandoer, mot hvilken database) FØR du kjører noe som
  skriver, migrerer eller sletter. Vent på bekreftelse.
- Hemmeligheter (passord, secrets) bor i `.env` (filrettigheter 600), dekkes av
  `.gitignore`, committes aldri, og skrives aldri i klartekst i chatten eller i
  SQL-filer. Generer dem på serveren (`openssl rand -base64 32`).
- Kjør appen som databaserollen `teecup_app` (NOSUPERUSER, NOBYPASSRLS) — aldri
  som `teeoff_admin`/superuser i runtime.

## Arkitektur-invarianter (ikke bryt uten en ny ADR)
- Tenant = organisasjon. `organization_id` på alle domenetabeller, håndhevet av
  RLS. App-koden setter `app.current_org` med `SET LOCAL` per transaksjon.
- Verifiser at brukeren er medlem av organisasjonen FØR org-konteksten settes.
  RLS stoler blindt på `app.current_org`.
- Egen innlogging (uavhengig av teeoff). Banedata hentes fra teeoff via lesende
  API, ikke delt database.
- v1 = nøyaktig to lag (Ryder Cup-format), håndhevet i app-laget. Match-modellen
  holdes generell (to sider) så knockout/flere lag kan komme senere.
- Handicap-/matchlogikk skal ligge i `handicap_engine.py` (rent, testet, uten
  db/API-avhengigheter). Allowances er konfig, ikke hardkodet.
- Media (bilder/video) skal i objektlagring (MinIO), ikke i Postgres. Postgres
  holder bare metadata + nøkkel.

## Tilgjengelighet — frontend (ufravikelig, gjelder ALT, eksisterende og fremtidig)
- Brukeren instruerte eksplisitt 2026-07-22: ALL frontend — det som
  allerede finnes OG alt som designes med V0 fremover — skal være
  lesbart, forståelig og betjenbart for noen med noe redusert syn UTEN
  briller, så langt det praktisk lar seg gjøre. Dette er en STÅENDE
  forventning til alt fremtidig UI-arbeid, ikke en engangsting for én
  skjerm.
- Praktisk konsekvens: god kontrast, stor nok skrift, store nok
  trykkflater, ikke ikon-only uten tekst-label for viktige handlinger,
  ikke avhengig av finmotorikk/skarpt syn for å bruke appen.
- Gjelder begge retninger: (a) ta dette eksplisitt med som krav når en ny
  V0-prompt skrives eller en V0-eksport gjennomgås, og (b) rett
  opportunistisk opp eksisterende skjermer når de likevel røres i en
  annen runde — ingen egen stor retrofit-runde er igangsatt eller bedt
  om ennå.

## Arbeidsmåte
- Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før
  du går videre.
- Bruk git (remote: brukerens Forgejo). Commit i logiske steg med tydelige
  meldinger.
- Er du usikker på omfang eller en beslutning: spør heller enn å gjette.

## Status (oppdater denne når ting endres)
Ferdig og verifisert:
- Handicap-motor + tester (24/24, R&A-verifisert).
- Skjema `001` + roller `002` + scoring/blind draw `003`. Isolasjon bevist med
  `test_isolation.sql` (RLS-oppførsel, ikke bare at skjemaet kjører).
- 002 hadde en reell bug (psql interpolerer ikke `:'var'` inne i `DO $$...$$`)
  — permanent fikset, verifisert mot scratch to ganger.
- API-et kjørt for ekte (ikke bare syntaks-sjekket) i en engangs Docker-
  container mot en scratch-database, RLS bevist gjennom hele
  asyncpg-pool-stacken (ikke bare i rå SQL).
- Oppsett-endepunktene er bygget og verifisert: `app/routers/players.py`
  (spillerpool), `tournaments.py` (turnering/lag/roster/økter, ADR-011
  to-lags-grense håndhevet med `FOR UPDATE`-lås), `matches.py` (matcher/
  deltakere/blind draw-lås, ADR-013-synlighet push-down i SQL). Delt
  feiloversettelse i `app/errors.py`, delte synlighetsspørringer i
  `app/blind_draw.py`. `main.py` er nå bare app-factory + `include_router`.
- Scoring-runden er bygget og verifisert for ekte mot scratch-db (18-hulls
  bane med `tee_rating`, 4 spillere for fourball-testing): `app/handicap.py`
  (ADR-014 fire brytere via `parse_allowance_config`, handicap beregnes i
  `compute_and_store_side_handicaps` rett etter deltaker-innsetting —
  singles/fourball per spiller umiddelbart, foursome/greensome/scramble kun
  når siden er komplett), `app/routers/scoring.py` (`hole-scores`/
  `hole-results`-upsert, `scorecard`-GET, matchstatus-recompute med
  `FOR UPDATE`-lås mot race og SAMMENHENGENDE-prefiks-regel for uferdige
  hull). `app/team_authz.py` skilt ut fra `matches.py` (delt med
  `scoring.py`). Alle 10 planlagte tester bestått, inkl. fourball
  better-ball-aggregering (MIN av to nettoer, venter til begge partnere har
  registrert), poeng-caching ved tidlig avgjort match, og ADR-014-bryteren
  `use_handicap=false`.
  **Fant og fikset underveis:** `tournaments.py` sin `SessionCreate` manglet
  `scoring_mode` helt (økter kunne aldri opprettes i `hole_result`-modus via
  API-et) — lagt til.
  **Bevisst utelatt/kjente begrensninger:** en side som aldri når forventet
  deltakerantall (no-show) får aldri beregnet handicap og matchen kan da
  aldri avgjøres — ingen manuell overstyring bygget. Score-skriving er
  upsert (ingen avvisning ved duplikat) — ingen audit-trail på rettelser.
  Kapteins-only autorisasjon fortsatt ikke bygget (FEATURE_BACKLOG ❓); bar
  er «rostret på laget».
- **Match-lås ved avgjørelse (2026-07-16):** `submit_hole_score`/
  `submit_hole_result` avviser nå 409 hvis `match.points_side_a IS NOT NULL`
  (matchen er avgjort) — FØR upserten kjøres, både for nye hull og
  korrigering av allerede talte hull. Tetter en reell bug: uten dette kunne
  «spøkelses-hull» lagt inn etter avgjørelse endre en allerede cachet margin
  ved neste omregning. Automatisk, ingen ny autorisasjon involvert.
- **Ekte autentisering bygget og verifisert (2026-07-16):** `X-Debug-User-Id`-
  stubben er HELT fjernet (ingen fallback). Magic-link + JWT-sesjon i
  `app/routers/auth.py` + `app/auth.py` (`request-link`/`verify-link`/
  `logout`/`me`), ny migrasjon `004_auth.sql` (`magic_link_token`-tabell +
  unik e-post-indeks på `app_user`). Token = `secrets.token_urlsafe(32)`, kun
  SHA-256-hash lagres, atomisk forbruk (`UPDATE ... RETURNING`, ikke
  les-sjekk-skriv), generisk respons uansett om e-posten finnes (unngår
  enumerering), gamle uforbrukte lenker ugyldiggjøres når en ny utstedes,
  `app_user` opprettes FØRST ved vellykket verifisering (ikke ved
  forespørsel). Sesjons-JWT (PyJWT, `algorithms=["HS256"]` eksplisitt) i
  HttpOnly/SameSite=Lax/dynamisk-Secure-cookie, 30 dager, med et ekte
  eksistens-oppslag mot `app_user` på hver forespørsel (faktisk
  tilbakekalling — en slettet bruker kan ikke ri ut sesjonen). Alle 12
  planlagte tester bestått.
  **Fant og fikset underveis:** `ON CONFLICT (email)` matchet ikke den nye
  PARTIELLE unike indeksen uten eksplisitt `WHERE email IS NOT NULL` (samme
  klasse feil som `hole_score`s partielle indekser i scoring-runden).
  **Fant, IKKE fikset i denne runden (egen runde rett etterpå — se under):**
  `organization`-tabellens RLS-policy kastet en 500 i stedet for skjemaets
  lovede "trygg standard: se ingenting" ved tomstreng-GUC.
- **RLS-tomstreng-bug FIKSET (2026-07-16):** ny migrasjon
  `005_rls_null_guard.sql` — delt `STABLE` SQL-funksjon `app_current_org()`
  gjør `NULLIF(current_setting('app.current_org', true), '')::uuid` i stedet
  for det rå uttrykket, brukt av alle 15 RLS-policyer (`ALTER POLICY`,
  14 `org_isolation` + `org_self`). Verifisert med 3 nye regresjonstester i
  `test_isolation.sql` (Test 10-12) OG ved faktisk å gjenskape original-
  buggen mot en ekte container (pool-størrelse 1, varm opp med
  `org_connection()`, deretter `/auth/me` på samme gjenbrukte tilkobling —
  gikk fra 500 til 200).
  **Viktig presisering fra denne runden:** fiksen gjør IKKE at `/auth/me` kan
  joine `organization` direkte via `plain_connection()` — det var en feilaktig
  antakelse i forrige runde. `org_self` krever fortsatt en MATCHENDE
  `app.current_org` for å vise en rad (riktig RLS-design, ikke noe fiksen
  skulle endre), og en bruker kan tilhøre flere organisasjoner samtidig, så
  det finnes ingen ÉN kontekst å sette for en tverr-org-spørring. `/auth/me`
  slår derfor opp hvert org-navn ett om gangen via `org_connection()` (N+1,
  N = antall org-er brukeren tilhører) — dette er riktig løsning, ikke en
  omvei.
- **Organisasjon-bootstrap bygget og verifisert (2026-07-16):** nytt
  `POST /orgs` (`app/routers/organizations.py`) — det ENESTE stedet i API-et
  som setter inn en `organization`-rad. Fant under statusgjennomgang at dette
  manglet helt (alle tidligere org-er var seedet med superbruker-SQL). Ingen
  ny migrasjon. Selvrefererende RLS-bootstrap bekreftet å fungere: generer
  org-ens uuid i Python, sett `app.current_org` til nøyaktig den via
  eksisterende `org_connection()`, sett inn `organization`-raden med samme
  id — `org_self`s implisitte `WITH CHECK` blir da trivielt sann, ingen
  privilegert tilkobling nødvendig (i motsetning til hva 002s kommentar
  antydet). Verifisert med 5 tester inkl. en negativ kontroll (mismatchende
  id avvist med `insufficient_privilege`) og full kryss-org-isolasjon mellom
  to uavhengig opprettede organisasjoner.
- **Ekte SMTP-utsending bygget og verifisert (2026-07-16):** ny `app/email.py`
  (`send_magic_link_email`, `smtplib` via `asyncio.to_thread`, håndterer
  både implisitt TLS/port 465 og STARTTLS dynamisk). Brukeren la egne
  SMTP-credentials i `.env` (`TEECUP_SMTP_*`, `TEECUP_FROM_EMAIL` — ADR-009,
  ikke delt med teeoff); jeg leste kun nøkkelnavnene for å bekrefte de
  fantes, aldri verdiene. `app/config.py` sin `SMTP_CONFIGURED` er valgfri
  (ikke `_required`) — dev-only logging (`TEECUP_DEV_LOG_MAGIC_LINKS`)
  fortsatt fungerer uendret når SMTP ikke er satt opp. Driftsfeil i
  utsendingen lekker aldri til klientresponsen (bevarer anti-enumerering).
  **Verifisert med faktisk levering:** sendte én ekte test-e-post til en
  adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i
  prosjektet er bevist ved ekte levering, ikke bare curl/scratch.
- **Containerisert og LIVE på `teecup.teeoff.no` (2026-07-16):** ekte
  `teecup_db` opprettet (migrasjoner 001→005 kjørt permanent, `test_isolation.sql`
  består), `Dockerfile` + `docker-compose.yml` (tjeneste `teecup_api`, joiner
  det eksisterende `teeoff_default`-nettverket), Caddy-blokk lagt til i
  `/opt/teeoff/deploy/Caddyfile`. Ekte innlogging (magic-link → e-post →
  JWT-sesjon med `Secure`-cookie) verifisert ende-til-ende mot den live
  stacken. `teeoff.no` upåvirket gjennom hele prosessen.
  **To reelle hendelser underveis, begge løst:**
  1. **Caddy plukket ikke opp filendringen** — `teeoff_caddy` sin
     `Caddyfile`-mount er en ENKELTFIL-bind-mount, låst til inoden som fantes
     da containeren sist startet. Min fil-redigering (atomisk rename) laget
     en ny inode på samme sti, så containeren fortsatte å lese den GAMLE
     filen uansett hvor mange ganger `caddy validate`/`caddy reload`/admin-API
     `/load` ble kjørt (alle validerte/lastet den uendrede gamle filen, derav
     ingen feilmelding). Løst med en full `docker restart teeoff_caddy`
     (brukeren bekreftet — avvek fra planens "kun graceful reload, ingen
     omstart"-løfte, noen sekunders nedetid for `teeoff.no`).
  2. **Alvorlig nettverksalias-kollisjon** (funnet RETT ETTER omstarten, da
     ekte teeoff-trafikk som `/api/facilities?...` med ekte klubb-slugs dukket
     opp i `teecup_api` sin logg): `docker-compose.yml` sin service-nøkkel var
     `api:` — SAMME nøkkel som teeoffs eget `api`-servicenavn
     (`docker-compose.prod.yml`). Docker Compose registrerer nettverksalias
     basert på service-NAVNET (ikke bare `container_name`) på delte nettverk,
     så BEGGE containerne fikk alias `api` på `teeoff_default` — Caddys
     `reverse_proxy api:8000` i teeoff sin egen config kunne da tilfeldig
     treffe enten ekte `teeoff_api` eller `teecup_api`. **Rettet umiddelbart**
     (stoppet `teecup_api` først for å hindre videre feilruting av ekte
     teeoff-trafikk, ga service-nøkkelen navnet `teecup_api` i stedet,
     gjenopprettet — bekreftet med `docker network inspect` at alias `api` nå
     KUN peker på ekte `teeoff_api`).
  **Mindre driftslærdom:** (a) jeg eksponerte ved et uhell
  `TEECUP_SMTP_PASS`/`TEECUP_FROM_EMAIL` i eget debug-output mens jeg
  feilsøkte en `.env`-korrupsjon (manglende linjeskift fra min egen
  `>>`-tilføyelse) — brukeren roterte passordet som forsiktighetsregel; (b)
  Docker leser IKKE `.env` på nytt for en allerede kjørende container —
  `docker compose up -d --force-recreate` kreves etter enhver `.env`-endring
  som skal tas i bruk; (c) et `#`-tegn i et upassordet `.env`-passord kuttes
  som en kommentar av Compose sin parser — anførselstegn (fortrinnsvis enkle)
  løser dette.
- **Dato/klokkeslett + feilkode-kontrakt + i18n-forberedelse (2026-07-17,
  ADR-015):** reist av brukeren rett før frontend-arbeidet. Ny migrasjon
  `006_scheduling_and_locale.sql` (alt additivt): `tournament.end_date`,
  `session.scheduled_at`/`tee_interval_minutes`/`start_hole`,
  `match.tee_time_override`, `app_user.preferred_locale`,
  `magic_link_token.locale`. `match.tee_time` er UTLEDET i Python
  (`scheduled_at + (sequence-1)*tee_interval_minutes`, override vinner hvis
  satt) — aldri lagret per match. Full retrofit av ALLE 39 daværende
  `HTTPException(..., detail="norsk streng")`-steder på tvers av 6 filer til
  en delt `app_error(status_code, code, message)`-factory
  (`app/errors.py`) — responsformen er nå konsekvent
  `{"detail":{"code":...,"message":...}}` i hele API-et, verifisert med et
  siste `grep -rn 'detail="' app/` som ga NULL treff. i18n: `locale`
  (`nb`/`en`) sendes av klienten ved `request-link`, styrer e-postmalen
  (ekte engelsk mal lagt inn i `app/email.py`, ikke bare rørlegging) OG
  settes som en HELT NY brukers `preferred_locale` — en eksisterende bruker
  som logger inn på et annet språk får IKKE sin lagrede preferanse
  overskrevet.
  **Fant og fikset underveis:** `tournament.start_date` har ligget i
  skjemaet siden migrasjon 001, men var ALDRI koblet til
  `TournamentCreate`/`Tournament`-modellene — funnet som en naturlig
  bivirkning av å legge til `end_date`.
  **Verifisert grundig mot fersk `teecup_scratch`** (001→006,
  `test_isolation.sql` fortsatt 12/12): feilkode-form bekreftet på tvers av
  5 filer (`NOT_FOUND`/`LIMIT_REACHED` tournaments.py, `DUPLICATE` roster,
  `NOT_AUTHENTICATED` uten cookie, `NOT_ROSTERED_ON_TEAM` matches.py),
  tee_time-beregning bekreftet (10 min intervall → 08:00/08:10/08:20,
  override vinner, `scheduled_at=null` gir `tee_time:null` ikke feil), full
  i18n-runde bekreftet (ny bruker `locale:"en"` → `preferred_locale:"en"`;
  påfølgende `request-link` for samme bruker med `locale:"nb"` skiftet
  e-postmalen men IKKE den lagrede preferansen, bekreftet via `/auth/me`).
  **Kjørt mot ekte `teecup_db` 2026-07-17** (bruker bekreftet eksplisitt i
  samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12
  (ruller alltid tilbake, ingen domenerader berørt).
  **Mindre driftslærdom, funnet OG rettet samme runde:** et `.env`-filter
  (`grep -v -i 'pass|secret|key'`) jeg brukte for å lese ikke-sensitive
  nøkler fanget ikke opp `TEECUP_DATABASE_URL`, som bar `teecup_app`-
  passordet innebygd i selve URL-en (i tillegg til at det SAMME passordet
  allerede lå rent i `TEECUP_APP_PASSWORD` — duplisert, ikke bare skjult) —
  passordet ble dermed synlig i et verktøyresultat. Flagget til brukeren
  umiddelbart (samme mønster som SMTP-passord-hendelsen over).
- **`.env`/tilkobling ryddet opp (2026-07-17), samme runde:** roten til
  hendelsen over var at `TEECUP_DATABASE_URL` var én sammensatt DSN-streng
  med passordet URL-kodet inni — usynlig for navnebaserte secret-filtre.
  Erstattet med fem separate, rent navngitte felt:
  `TEECUP_DB_HOST`/`TEECUP_DB_PORT`/`TEECUP_DB_NAME`/`TEECUP_DB_USER`/
  `TEECUP_DB_PASS` (sistnevnte omdøpt fra `TEECUP_APP_PASSWORD`, kun
  nøkkelnavnet — verdien aldri lest eller skrevet av meg). `app/config.py`
  og `app/db.py` bygger nå `asyncpg`-poolen fra disse fem separate feltene
  (`host=`/`port=`/`user=`/`password=`/`database=`) i stedet for én DSN —
  fjerner også URL-prosentkoding-problemet fra SMTP-passord-hendelsen sin
  klasse av feil. `docker-compose.yml` sin `environment:`-liste oppdatert
  tilsvarende. **Krevde et fullt image-rebuild, ikke bare
  `--force-recreate`:** `Dockerfile` sin `COPY app/ app/` bakes inn i
  imaget ved build-tid (ingen bind-mount i prod, i motsetning til
  scratch-verifiseringens engangscontainere) — en ren `--force-recreate`
  gjenbrukte det GAMLE imaget og krasjet umiddelbart på den nå fjernede
  `TEECUP_DATABASE_URL`. Rettet med `docker compose up -d --build
  --force-recreate`. **Verifisert ende-til-ende mot den live stacken:**
  containeren boot-et rent (`Application startup complete` i loggen, som
  krever en vellykket `init_pool()` — asyncpg ville kastet og forhindret
  akkurat den logglinjen ved feil tilkoblingsparametre), `/auth/me` over
  ekte https ga et rent `401 NOT_AUTHENTICATED` (ikke 500/502), `teeoff.no`
  upåvirket (`200` gjennom hele omstarten, kun `teecup_api` restartet — ikke
  delt Caddy-instans, ikke samme risikoklasse som Caddy-hendelsen fra
  containeriseringsrunden).

- **Frontend startet, innlogging LIVE (2026-07-17, ADR-016):** første
  frontend-skjerm i prosjektet. Designet i V0 (Next.js + Tailwind +
  shadcn/ui), hentet inn som `frontend/` — merkevare-form/farge fra
  Teeoff-logoen, IKKE navn/logo (egne, separate produkter, se ADR-009).
  **Kvalitetsrunde før bruk:** V0s fargetokens var OKLCH-TILNÆRMINGER, ikke
  eksakte — regnet ut presise verdier fra `#8bc24a`/`#ff5722` og rettet alle
  6 forekomster i `globals.css`. Fjernet `@vercel/analytics` (unødvendig på
  egen-hostet infra), fjernet `typescript: { ignoreBuildErrors: true }`
  (ekte typesjekk kjører nå), fjernet dødt `pnpm.overrides`-felt.
  `frontend/.gitignore` manglet `.pnpm-store/` — årsaken til at brukerens
  VSCode viste 10 000 "ukjente" filer ved første commit-forsøk; rettet.
  **Kablet mot ekte API:** `next.config.mjs` sin `rewrites()` proxyer
  `/auth/*`/`/orgs/*`/`/health` server-side til `teecup_api` — same-origin,
  ingen CORS, cookie uendret (full begrunnelse i ADR-016). Login-skjermet
  sender ekte `POST /auth/request-link`; ny `/verify`-side mottar
  `?token=...` fra e-postlenken (auto-verifiserer) eller viser et manuelt
  "lim inn koden"-felt. `app/email.py` fikk en ny `PUBLIC_BASE_URL`-
  innstilling og sender nå en EKTE klikkbar lenke (koden beholdes som
  fallback).
  **Reell fallgruve funnet og fikset ved containerisering:** Next.js sin
  `rewrites()` løses ved BUILD-tid for `output: "standalone"`, ikke ved
  container-oppstart — en runtime `-e TEECUP_API_ORIGIN=...` ble stille
  ignorert (proxy-kall feilet med `ECONNREFUSED` mot `localhost:8000`).
  Løst med en Docker build-time `ARG TEECUP_API_ORIGIN` i
  `frontend/Dockerfile`, satt via `docker-compose.yml` sin `build.args`.
  **Rullet ut live:** ny `teecup_frontend`-tjeneste i `docker-compose.yml`.
  Caddy (`teecup.teeoff.no`, i det SEPARATE `teeoff`-repoet,
  `/opt/teeoff/deploy/Caddyfile`) endret fra å peke direkte på `teecup_api`
  til å peke på `teecup_frontend` — samme stale-inode-oppførsel som
  containeriseringsrunden (graceful `reload` plukket IKKE opp endringen,
  `/` fortsatte å gi `teecup_api` sin egen 404 i stedet for innloggingssiden
  til reload faktisk skjedde). Løst likt: full `docker restart
  teeoff_caddy`, brukeren bekreftet eksplisitt på forhånd. `teeoff.no`
  upåvirket gjennom hele omstarten.
  **Verifisert med FAKTISK e-postlevering:** ekte magic-link sendt til
  brukerens egen adresse over `https://teecup.teeoff.no`, ekte e-post
  mottatt med en ekte klikkbar lenke, åpnet i nettleser, landet på en
  fungerende `/verify`-side, sesjon opprettet — brukeren bekreftet innlogget
  status. Første gang en hel bruker-vendt flyt er bevist ende-til-ende i
  produksjon, ikke bare API-et isolert.
  **Merk for neste økt:** `deploy/Caddyfile`-endringen ligger uncommitted i
  det SEPARATE `/opt/teeoff`-repoet, ikke i `teecup`-repoet — lett å glemme
  siden denne økten ellers kun har jobbet i `/opt/teecup`.
- **Dashboard-skjerm LIVE (2026-07-18):** andre V0-skjerm — organisasjon-
  bytter/-opprettelse + turneringsliste (`/dashboard`), samme mønster som
  login-runden. **Reell integrasjonsfelle unngått:** V0s eksport denne gangen
  var en FULL re-eksport av hele prosjektet (inkl. `login-form.tsx`,
  `next.config.mjs`, `package.json`), ikke bare de nye filene — en naiv
  utpakking ville stille reversert `rewrites()`-proxyen, `output:
  "standalone"`, den ekte fetch-kablingen i login-skjemaet, og alle
  V0-uavhengige opprydninger fra forrige runde. Løst ved å pakke ut til et
  scratch-område FØRST, diffe mot live-treet fil for fil, og kun ta inn det
  som faktisk var nytt (`dashboard.tsx`, `tournament-card.tsx`,
  `tournament-status-badge.tsx`, `wordmark.tsx`, `badge.tsx`,
  `dropdown-menu.tsx`, `app/dashboard/page.tsx`) — `next.config.mjs`,
  `package.json`, `globals.css`, Docker-filene ble bevisst IKKE overskrevet.
  `login-form.tsx` fikk en kirurgisk patch (kun Wordmark flyttet til egen
  fil, som V0 selv hadde gjort — all egen fetch-/feilhåndteringslogikk
  urørt).
  **`dashboard.tsx` sitt datalag skrevet om fra bunnen** (V0 leverte kun
  mock `useState`): henter `/auth/me` for organisasjonsmedlemskap,
  `/orgs/{id}/tournaments` per valgt org, `POST /orgs`/`POST
  /orgs/{id}/tournaments` for opprettelse, `POST /auth/logout` for utlogging
  — presentasjonskomponentene (kort, bytter, tomme tilstander) beholdt
  uendret fra V0. `/verify`-siden oppdatert til å sende brukeren videre til
  `/dashboard` etter vellykket innlogging (fantes ingen dit å gå før nå).
  **Verifisert:** ekte typesjekket build, redeploy av kun `teecup_frontend`
  (ingen Caddy-endring nødvendig denne gangen — ADR-016s mønster holder),
  `teecup.teeoff.no/dashboard` → 200, `teeoff.no` upåvirket.
  **Skrive-flyten bekreftet med EKTE data samme dag (brukeren testet selv,
  ikke meg):** organisasjon "Tjøme Gents" og turnering "De Gamle er Eldst"
  opprettet via UI-et mot den ekte `teecup_db` — statusmerket viste riktig
  "Utkast", "Ingen datoer satt" håndtert korrekt (ingen krasj på manglende
  dato), ett-org-visningen viste riktig uten unødvendig bytter-UI. Første
  gang en HEL skrive-flyt (ikke bare lesing) er bevist ende-til-ende fra
  frontend mot ekte produksjonsdata.
- **Lag/roster-skjerm LIVE (2026-07-18):** tredje V0-skjerm
  (`/tournaments/[id]`), samme re-eksport-mønster som dashboard-runden —
  diffet mot live-treet, tok kun inn `tournament-detail.tsx` og et
  `Link`-basert `tournament-card.tsx` (navigasjon fra dashbordet).
  **URL-design bevisst avvikende fra V0s forslag:** V0s genererte side leste
  aldri `params.id` og hadde ingen organization_id i det hele tatt — holdt
  derfor V0s flate `/tournaments/[id]`-struktur (i stedet for en nøstet
  `/orgs/[orgId]/tournaments/[id]`, som ville krevd manuell ombygging ved
  HVER fremtidig V0-reeksport) og la `org`+`name` til som søkeparametre i
  `tournament-card.tsx` sin lenke — API-et krever organization_id på alle
  team-/roster-kall (RLS).
  **Reelt hull funnet FØR integrering, ikke etter:** V0-skjermen bygger inn
  "fjern spiller"/"gjør til kaptein"-handlinger, men backend hadde KUN
  GET/POST på `team_roster` — ingen DELETE eller PATCH. Spurte bruker
  eksplisitt (samme mønster som andre scope-avklaringer denne økten) — svar:
  bygg de to endepunktene nå. Lagt til i `app/routers/tournaments.py`:
  `PATCH .../roster/{roster_id}` (bevisst enkel — setter/fjerner
  `is_captain` på NØYAKTIG denne raden, håndhever IKKE "kun én kaptein per
  lag", siden kaptein fortsatt bare er et merke, ikke en egen
  autorisasjonsrolle) og `DELETE .../roster/{roster_id}` (204, idempotent
  `NOT_FOUND` ved dobbel sletting — ikke krasj).
  **`tournament-detail.tsx` sitt datalag skrevet om fra V0s mock:** henter
  lag + roster (roster-radene bærer allerede `display_name`/
  `handicap_index_snapshot` fra APIet, så V0s separate `poolById`-oppslag
  ble fjernet som overflødig) og organisasjonens spillerpool
  (`GET /orgs/{id}/players`, brukt til type-ahead ved "legg til spiller").
  Alle fem mutasjonene (opprett lag, legg til eksisterende spiller, opprett
  ny spiller inline + rostre, endre kaptein, fjern fra roster) kablet mot
  ekte endepunkter — presentasjonskomponentene (kort, type-ahead,
  fargevelger, bekreft-fjerning) beholdt uendret fra V0.
  **Verifisert:** de to nye endepunktene testet mot en fersk
  `teecup_scratch` (PATCH setter kaptein + riktig `NOT_FOUND` på ugyldig id,
  DELETE gir 204 + idempotent `NOT_FOUND` ved gjentak, `test_isolation.sql`
  fortsatt 12/12), ekte typesjekket frontend-build (5 ruter), redeploy av
  BEGGE containere (backend-endepunktene er nye), `teecup.teeoff.no/dashboard`
  → 200, `teeoff.no` upåvirket. Selve skrive-flyten på `/tournaments/[id]`
  (opprett lag/roster) ikke testet med ekte data i denne runden — venter på
  brukeren, samme mønster som dashboard-rundens skrive-test.
- **ADR-017 + migrasjon 007 (2026-07-18):** brukeren reiste selvregistrering
  rett etter at roster-skrive-flyten var bekreftet. Full ADR skrevet
  (5 beslutninger: offentlig påmelding uten innlogging, e-post som
  sammenkoblingsnøkkel mot forhåndsopprettede spillere, egen
  `tournament_registration`-tabell atskilt fra `team_roster` med
  konfigurerbar godkjenning/kapasitet/venteliste, utvidet spillerprofil +
  obligatorisk samtykke, og `public_tournament_org()`). Migrasjon
  `007_registration_and_player_fields.sql` skrevet og scratch-verifisert
  (001→007 kjører rent, `test_isolation.sql` fortsatt 12/12).
  **Reelt arkitekturproblem løst underveis, ikke bare skjema:** et
  offentlig (uautentisert) påmeldingskall kjenner en turnering-id, men
  ingen org-kontekst — og uten `app.current_org` slipper RLS ingen rader
  gjennom, heller ikke oppslaget for å FINNE riktig org. Løst med en snever
  `SECURITY DEFINER`-funksjon (`public_tournament_org`) som KUN eksponerer
  uuid→uuid-koblingen. **Verifisert presist, ikke bare "kjørte uten feil":**
  kalt funksjonen som `teecup_app`-rollen med ingen `app.current_org` satt
  — ga korrekt org-id for en kjent turnering, `NULL` (ikke feil) for en
  ukjent — OG et RÅTT `SELECT` på `tournament` på SAMME tilkobling/rolle ga
  fortsatt 0 rader, som beviser RLS ikke er brutt generelt, bare dette ene
  smale unntaket eksisterer.
  **Kjørt mot ekte `teecup_db` 2026-07-18** (bruker bekreftet eksplisitt i
  samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12.
- **Registrerings-API LIVE (2026-07-18), samme dag:** ny
  `app/routers/registration.py` — `GET /public/tournaments/{id}` og
  `POST /public/tournaments/{id}/register`, begge UTEN `get_current_user`
  eller `get_authorized_org` (helt uautentisert, egen `/public`-prefiks,
  bevisst atskilt fra `/orgs/...` i koden). Bruker `public_tournament_org()`
  (migrasjon 007) til å slå opp org-kontekst FØR RLS kan håndheve noe.
  `app/routers/players.py` utvidet med alle sju nye ADR-017-feltene
  (`mobile`/`email`/`birth_date`/`nickname`/`country`/`club`/
  `club_member_number`). `frontend/next.config.mjs` sin `rewrites()`
  utvidet med `/public/*` (ADR-016s konsekvens: enhver ny API-prefiks MÅ
  inn her).
  **Ny brukers-oppdaget notat fanget FØR bygging, ikke etter:** brukeren
  krevde eksplisitt at synlighet (offentlig/kun org/kun turnering-
  deltakere) må være et VALG for fremtidige landingssider — notert grundig
  i `FEATURE_BACKLOG.md` (koblet til samme åpne spørsmål for "Banter
  Board"-feeden) FØR dette registrerings-API-et ble bygget, akkurat for at
  det ikke skal gå i glemmeboken til landingsside-runden.
  **Verifisert grundig mot fersk `teecup_scratch`** (001→007,
  `test_isolation.sql` 12/12): hele registreringsløpet testet reelt —
  samtykke-avvisning (400), duplikat-avvisning (409 `DUPLICATE`),
  e-post-matching mot en organisator-forhåndsopprettet spiller (BEKREFTET:
  ingen duplikatrad, mobil fylt inn via `COALESCE`, `display_name` IKKE
  overskrevet), kapasitet+`waitlist`-policy (→ `waitlisted`),
  kapasitet+`closed`-policy (→ 409 `LIMIT_REACHED`), `registration_
  requires_approval` (→ `pending`), utløpt frist (→ 409
  `REGISTRATION_CLOSED`), og `confirmed_count` i `GET`-responsen talt
  riktig (kun `confirmed`+`pending`, ikke `waitlisted`/avviste).
  **Rullet ut live:** begge containere redeployet, `teecup.teeoff.no/
  dashboard` og `/health` fortsatt 200, `teeoff.no` upåvirket, det
  offentlige endepunktet bekreftet nåbart over ekte https (ukjent
  turnering-id ga korrekt `404`/`NOT_FOUND`, ikke-destruktiv sjekk — selve
  påmeldingsflyten med ekte data ikke testet mot prod i denne runden).
  **E-post-basert kontosammenkobling LIVE, samme dag:** ny migrasjon
  `008_link_player_by_email.sql` — `link_player_by_email(user_id, email)`,
  samme `SECURITY DEFINER`-mønster som `public_tournament_org()` (007),
  denne gangen for en tverr-org UPDATE i stedet for et lese-oppslag.
  `verify_magic_link` (`app/routers/auth.py`) kaller den på HVER
  innlogging (idempotent — funksjonens `WHERE user_id IS NULL` gjør
  gjentatte kall til en no-op), ikke bare ved førstegangsopprettelse.
  **Verifisert presist:** to separate org-er, hver med sin egen
  organisator-opprettede "Kari"-rad (samme e-post, ulik store/små
  bokstaver for å teste case-insensitivitet også) — ved Karis FØRSTE
  innlogging ble BEGGE radene koblet til kontoen hennes, i to org-er hun
  aldri har vært medlem av. Eksplisitt bekreftet: `organization_membership`
  har NULL rader for henne etterpå — ren identitetskobling, ingen
  privilegie-eskalering (å ha `player.user_id` satt gir ingen ny tilgang
  gjennom `get_authorized_org`, som fortsatt krever ekte org-medlemskap
  uavhengig av dette). Andre innlogging idempotent, ingen feil.
  `test_isolation.sql` fortsatt 12/12. **Kjørt mot ekte `teecup_db`
  2026-07-18**, bruker bekreftet eksplisitt, backend redeployet, live
  sjekker OK, `teeoff.no` upåvirket.
  **ADR-017s backend er dermed komplett** (registrering + kontokobling).
  Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere
  ADR-runde (se `FEATURE_BACKLOG.md`).
- **ADR-018 + migrasjon 009: landingssider, backend LIVE (2026-07-18),
  samme dag:** trenivås synlighet (`tournament.visibility`:
  `public`/`org`/`participants`, default `org` — trygg standard) +
  `organization.public_profile`. **Reell presisering funnet underveis, ikke
  antatt på forhånd:** RLS (`org_isolation`) beskytter kun TENANT-grenser
  (org A ser aldri org B), IKKE innholds-synlighet innenfor riktig
  org-kontekst — det eksisterende `GET /public/tournaments/{id}` (ADR-017)
  leste allerede fullt innhold uten synlighetssjekk, fordi RLS er fornøyd
  så snart org-konteksten er satt, uansett hvem som spør. `visibility`
  håndheves derfor eksplisitt i `app/routers/registration.py`, på BÅDE
  lesing og registrering (ADR-018 Beslutning D: kan du ikke se turneringen,
  kan du heller ikke melde deg på den — bekreftet med bruker FØR bygging).
  Ny `get_current_user_optional` i `app/auth.py` (som `get_current_user`,
  men returnerer `None` i stedet for 401 — offentlige endepunkter skal
  fungere for anonyme lesere også). Ny "deltaker"-autorisasjonsvei: en
  innlogget bruker med `player.user_id` koblet (ADR-017) OG en
  `tournament_registration`- eller `team_roster`-rad for NØYAKTIG den
  turneringen får se `participants`-synlige turneringer, uansett
  org-medlemskap.
  **Ekte migrasjonsfeil funnet OG rettet UNDER scratch-verifisering, ikke
  antatt riktig:** migrasjonen feilet først ("column slug already exists")
  — `organization.slug` har ligget i skjemaet siden migrasjon 001
  ("f.eks. subdomene/URL-vennlig", allerede med en plain `UNIQUE`), noe jeg
  hadde oversett fullstendig og forsøkt å legge til på nytt. Rettet ved å
  fjerne den doble `ADD COLUMN` + den overflødige partielle unik-indeksen
  (001 sin plain `UNIQUE` dekker "unik når satt" allerede, siden Postgres
  behandler NULL som distinkt), beholde kun de nye `CHECK`-constraintene.
  Kjørte rent på ny etter fiksen. **Lærdom:** grep alltid eksisterende
  skjema for feltnavn FØR en ny migrasjon skrives, ikke bare stol på
  hukommelsen om hva som "sikkert" ikke finnes fra før.
  Ny tabell `tournament_sponsor` (navn+lenke aktivt, `logo_key` inert til
  MinIO-runden — samme med `tournament.hero_image_key`). Tredje
  `SECURITY DEFINER`-bro i prosjektet: `public_org_by_slug()` (etter
  `public_tournament_org` 007, `link_player_by_email` 008) — returnerer
  `NULL` for BÅDE "finnes ikke" og "finnes, men er privat", samme
  anti-enumerering som magic-link.
  **Fylte også et implisitt hull oppdaget underveis:** ADR-en beskrev
  hvordan synlighet skulle håndheves, men ingen tidligere runde hadde bygget
  noen vei for organisator til faktisk å SETTE disse feltene. Lagt til:
  `PATCH /orgs/{id}/tournaments/{id}` (visibility/description/
  registrerings-innstillinger, ekte PATCH-semantikk via Pydantic sin
  `exclude_unset` — et utelatt felt nullstilles IKKE), `PATCH /orgs/{id}`
  (slug/public_profile), full sponsor-CRUD. `_fetch_sessions()` trukket ut
  som delt hjelpefunksjon i `tournaments.py` (delt mellom den innloggede
  og den nye offentlige `GET /public/tournaments/{id}/sessions` — blind
  draw-hemmelighold, ADR-013, arves automatisk, ikke reimplementert).
  **Verifisert grundig mot fersk `teecup_scratch`** (001→009,
  `test_isolation.sql` 12/12): hele synlighetsmatrisen testet med ekte
  HTTP-kall — anonym avvist på `org`-synlig turnering (både lesing OG
  registrering), `PATCH` til `public` + beskrivelse + sponsor fungerte,
  anonym lesing fungerte deretter, `participants`-synlighet bekreftet
  reell chicken-and-egg-konsekvens av Beslutning D (ingen kan selv-
  registrere seg til en `participants`-synlig turnering, kun organisator
  kan legge til direkte — korrekt, ikke en bug), en organisator-rostret
  spiller som logget inn fikk tilgang, en tilfeldig innlogget FREMMED
  (ikke deltaker) ble fortsatt avvist, org-landingsside viste KUN
  `public`-synlige turneringer, `CHECK`-constraint (`public_profile`
  krever `slug`) avvist korrekt, ugyldig slug-format avvist av Pydantic,
  sponsor-sletting fungerte.
  **Kjørt mot ekte `teecup_db` 2026-07-18**, bruker bekreftet eksplisitt,
  backend redeployet, live sjekker OK, `teeoff.no` upåvirket. Ingen
  frontend-endring nødvendig for selve API-tilgangen (det brede
  `/public/:path*`-mønsteret fra ADR-016 dekker allerede `/public/orgs/*`).
  **Gjenstår:** selve landingsside-SKJERMENE i frontend (V0), og
  MinIO/bildeopplasting — bevisst utsatt, egen runde.
- **Offentlig turnering-landingsside LIVE (2026-07-18), samme dag:** fjerde
  V0-skjerm (`components/public-tournament.tsx`, ny rute `/t/[id]`) —
  banner (bevisst permanent farge-/gradient-utseende, ikke en "bilde
  kommer"-plassholder), presenterende tekst, status (datoer + "X av Y
  plasser"), program, sponsorer (navn+lenke), og et påmeldingsskjema med
  navn+e-post synlig først og resten bak en "flere detaljer"-utvidelse —
  tre distinkte bekreftelsestilstander (bekreftet/venteliste/godkjenning
  venter), ikke én generisk "takk".
  **Ingen ny reeksport-kollisjon denne gangen** — kun étt genuint nytt
  filnavn (`public-tournament.tsx`), resten var kjent V0-revert av
  allerede-tilpassede filer, samme mønster som før.
  **V0 opprettet komponenten, men ingen rute** — la selv til `app/t/[id]/
  page.tsx` (bevisst en FLAT `/t/[id]`-sti, ikke nøstet under `/orgs/...`
  som den innloggede turnering-detalj-siden, siden det offentlige API-et
  kun trenger turnering-id, ikke org-id).
  **Datalaget skrevet om fra V0s mock:** ekte `fetch` mot
  `GET /public/tournaments/{id}` + `/sessions`, ekte `POST .../register`.
  Håndterer 403 (`NOT_VISIBLE`, ADR-018) og 404 med en egen tilgang-avvist-
  tilstand V0 ikke hadde bedt om (fantes ikke i prompten, men trengs for at
  siden faktisk skal fungere for `org`/`participants`-synlige turneringer).
  Mappet `status:"waitlisted"` fra API-et til komponentens `"waitlist"`, og
  skjemaets norske kjønnsvalg ("kvinne"/"mann"/"annet") til API-ets
  `^[mfx]$`-mønster — to reelle navnekollisjoner mellom V0s UI-språk og
  API-kontrakten, ikke bare et rett-frem felt-for-felt-uttrekk.
  **Reell driftsfeil funnet OG rettet under scratch-test, ikke i
  produksjon:** `pnpm install` (uten `--ignore-scripts`) i dev-server-
  testoppsettet feilet stille med tom logg og exit 1 — corepack hadde
  hentet en NY pnpm-versjon (11.13.1 → 11.14.0) siden sist, som gjør
  "ignored builds"-varselet til en hard feil i stedet for bare en
  advarsel. Rettet ved å bruke samme `--ignore-scripts`-flagg som
  `frontend/Dockerfile` allerede bruker (upåvirket av denne — bekreftet
  ved at selve prod-buildet fortsatt gikk rent). **Lærdom:** `Dockerfile`
  sin pinning av *kode* er ikke det samme som å pinne *verktøyene rundt*
  (corepack henter alltid siste pnpm) — verdt å huske neste gang et
  scratch-dev-server-oppsett plutselig feiler uten åpenbar grunn.
  **Verifisert grundig mot fersk `teecup_scratch` + en ekte kjørende
  frontend-dev-server** (ikke bare `next build`): ekte turnering med
  beskrivelse, kapasitet, sponsor og økt opprettet via API-et, hentet
  gjennom frontend-proxyen (ikke direkte mot backend) og bekreftet
  byte-for-byte riktig — inkludert en ekte `POST`-registrering som økte
  `confirmed_count` fra 0 til 1.
  **Rullet ut live**, kun `teecup_frontend` (ingen backend-endring denne
  runden), `teeoff.no` upåvirket.
  **Gjenstår:** org-landingssiden (egen V0-prompt), Open Graph-metadata for
  deling, MinIO/bilder.
- **Offentlig klubb-landingsside LIVE (2026-07-18), samme dag:** femte og
  siste V0-skjerm i ADR-018 (`components/public-club.tsx`, ny rute
  `/clubs/[slug]`). Samme banner-språk som turnering-siden, liste over
  klubbens turneringer (gjenbrukte eksisterende `TournamentCard`), egen tom-
  tilstand.
  **Reell delt-komponent-kollisjon løst, ikke duplisert bort:**
  `TournamentCard` var bygget for KUN den innloggede konteksten (krevde
  `orgId`, lenket til `/tournaments/{id}?org=...`). I stedet for en egen
  kopi av kortet for den offentlige siden, gjort `orgId` valgfri —
  satt (dashbordet) gir innlogget lenke, utelatt (klubbsiden) gir
  `/t/{id}` i stedet. Samme kort, to kontekster, ingen duplisering.
  Bekreftet dashbordets egen bruk uendret/upåvirket etterpå.
  **V0 opprettet denne gangen selv en rute** (`app/clubs/[id]/page.tsx`,
  uten noen props sendt inn i det hele tatt) — men navnga parameteren
  `[id]` selv om den faktisk er en SLUG (`public_org_by_slug()`, ADR-018).
  Skrev selv en ny, riktig `app/clubs/[slug]/page.tsx` i stedet for å bruke
  V0s (feilnavngitte og prop-løse) versjon.
  **Verifisert mot fersk `teecup_scratch` + ekte kjørende frontend-dev-
  server:** org med slug+`public_profile`, én turnering satt `public`, én
  latt stå på default `org` — klubbsiden viste GJENNOM frontend-proxyen
  kun den ene offentlige turneringen, den org-private var korrekt
  usynlig (samme filtermønster som API-et selv, bekreftet fra
  klientsiden også). Ukjent slug ga korrekt 404.
  **Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket.
  **ADR-018s planlagte skjermer er dermed komplette.** Gjenstår: Open
  Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere
  runder.
- **Open Graph-metadata LIVE (2026-07-18), samme dag — ADR-018 dermed
  helt ferdig:** `generateMetadata()` lagt til på `/t/[id]` og
  `/clubs/[slug]` (ekte tittel/beskrivelse fra API-et, `og:site_name`,
  trygg fallback-tittel ved ukjent id/slug — feil her skal ALDRI hindre
  selve siden i å laste).
  **Reell driftsfeil funnet FØR den nådde produksjon, ikke etter:**
  `generateMetadata()` kjører server-side ved REQUEST-tid, ikke i
  nettleseren — går derfor IKKE gjennom `next.config.mjs` sin
  `rewrites()` (som kun gjelder nettleser-trafikk inn til Next.js-
  serveren). Måtte derfor lese `TEECUP_API_ORIGIN` direkte, men den
  variabelen fantes KUN i `Dockerfile` sitt builder-steg — `ENV` satt i
  ett `FROM`-steg arves ikke til et senere. Rettet ved å sette samme
  `ARG`/`ENV` på nytt i runner-steget også.
  **Verifisert presist at fiksen faktisk virker, ikke bare at bygget gikk
  gjennom:** bygget det EKTE produksjonsimaget (ikke dev-server) pekt mot
  en scratch-backend med en ekte offentlig turnering+org, hentet den
  faktiske server-rendrede HTML-en og bekreftet ekte `<title>`/`og:title`/
  `og:description` — ikke bare at TypeScript kompilerte. Ukjent
  turnering-id ga korrekt trygg fallback-tittel.
  **Bevisst utenfor omfang:** `og:image` — ingen ekte bilde finnes ennå
  (MinIO-runden). La til `metadataBase` i `app/layout.tsx` nå likevel, som
  forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en
  egen fiks da.
  **Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket.
- **MinIO-runden LIVE (2026-07-18), samme dag — ADR-018 dermed HELT
  ferdig, ingenting utsatt igjen:** ny `teecup-minio`-tjeneste (persistent
  volum, genererte credentials i `.env`), backend-endepunkter for ekte
  bildeopplasting til `tournament.hero_image_key`/`tournament_sponsor.
  logo_key` (som lå inerte siden migrasjon 009), offentlige API-svar bygger
  nå fulle URL-er, `og:image` koblet på i `/t/[id]` sin `generateMetadata`.
  **Sent, men viktig presisert krav underveis:** brukeren avbrøt en
  verktøyskall midtveis for å presisere at ALLE bilder skal konverteres
  til AVIF for plassbesparelse. Snudde HELE opplastingsarkitekturen på
  dette: droppet den opprinnelige planen om presignerte URL-er (nettleser
  laster opp DIREKTE til MinIO) til fordel for ekte multipart-opplasting
  GJENNOM API-et, som konverterer til AVIF (Pillow + pillow-avif-plugin)
  FØR lagring. Dette forenklet arkitekturen betydelig: kun ÉN MinIO-klient
  trengs nå (før: to separate klient-oppsett for henholdsvis internt
  admin-arbeid og offentlig presignering), og Caddy sin nye rute trenger
  ikke lenger bevare Host-headeren presist (relevant kun for SigV4-
  signaturverifisering av presignerte URL-er, ikke for anonym public-read).
  **To reelle feil funnet UNDER scratch-verifisering, aldri i produksjon:**
  (1) `pillow-avif-plugin` krever ingen ekstra systempakker i
  `python:3.12-slim` -- verifisert med et frittstående encode/decode-
  rundtrip FØR det ble tatt i bruk i selve API-et. (2) MinIO validerer
  `Host`-headeren STRENGT og avviser understrek som ugyldig vertsnavn --
  et tjenestenavn med understrek (`teecup_minio`, konsistent med
  `teecup_api`/`teecup_frontend`) feilet umiddelbart ved oppstart
  ("Invalid Request (invalid hostname)"). Bekreftet presist ved å teste
  samme oppsett med bindestrek i stedet (`teecup-minio`) -- fungerte
  umiddelbart. Alle referanser rettet til bindestrek FØR noe ble forsøkt
  mot ekte infrastruktur.
  **Caddy:** ny `/teecup-media/*`-rute på det EKSISTERENDE
  `teecup.teeoff.no`-blokket (IKKE et nytt subdomene -- en tidlig sjekk
  avdekket at `media.teecup.teeoff.no` fantes som en wildcard DNS-post,
  men pekte til en IPv6-adresse denne serveren ikke har i det hele tatt;
  path-prefiks på et allerede fungerende domene unngikk hele den DNS-
  avhengigheten). Samme stale-inode-oppførsel som alle tidligere Caddy-
  runder -- full `docker restart teeoff_caddy`, brukeren bekreftet
  eksplisitt. `teeoff.no` upåvirket.
  **Verifisert grundig, i flere lag:** frittstående AVIF-encode/decode-
  test, full scratch-kjede (fersk Postgres + en ISOLERT scratch-MinIO-
  container) med et ekte opplastet bilde -- bekreftet konvertert til
  gyldig AVIF (800×400, 406 bytes for et helfarget testbilde), bekreftet
  lagret med riktig nøkkel, bekreftet lesbart ANONYMT direkte mot MinIO
  (uten Caddy, isolerer bucket-policyen), bekreftet `hero_image_url`/
  `logo_url` bygget riktig i det offentlige API-svaret. Alle tre
  valideringsveier testet (ugyldig content-type, korrupt bildeinnhold,
  for stor fil >8MB) -- alle ga korrekt `VALIDATION_FAILED`. Etter
  Caddy-omstart: et ekte anonymt kall mot `/teecup-media/...` på
  produksjonsdomenet ga en ekte MinIO S3-XML-feilrespons (`NoSuchKey` for
  en scratch-nøkkel som naturligvis ikke finnes i prod) -- beviser ruten
  treffer MinIO selv, ikke frontend sin egen 404-side.
  `test_isolation.sql` fortsatt 12/12 gjennom hele runden.
  **Bevisst utenfor omfang:** ingen faktisk opplasting av et EKTE bilde
  til en EKTE, live turnering i denne runden (ville krevd å skrive
  test-data i brukerens ekte konto uten å bli spurt) -- tilbudt, ikke
  utført. Selve dra-og-slipp-opplastingsskjermen i frontend (V0) er
  fortsatt ikke bygget, som avtalt fra starten av runden.

- **Program-skjerm (økter/tidsplan), bygget og SCRATCH-verifisert, IKKE
  live ennå (2026-07-18):** sjette V0-skjerm, første i "bygg i rekkefølgen
  ting brukes"-serien (etter lag/roster: program → blind draw → scorekort →
  leaderboard). `components/tournament-program.tsx`, ny rute
  `/tournaments/[id]/program`. Tidslinje over økter + opprett-skjema
  (format, hullomfang, scoringsmodus, poeng, klokkeslett+intervall,
  starthull, kollapsbar "avansert"-seksjon med ADR-014s fire brytere).
  **Reelt blokkerende hull funnet FØR integrering:** `SessionCreate.
  course_id` er påkrevd, men INGEN endepunkt kunne noensinne produsere en —
  ADR-004s teeoff-integrasjon er fortsatt bare vedtatt (ingen utgående
  HTTP-klient finnes). Spurte bruker eksplisitt — svar: bygg enkel
  course-CRUD nå. Ny `app/routers/courses.py` (`GET`/`POST /orgs/{id}/
  courses`, kun `source='custom'`). Program-skjemaet fikk et
  type-ahead-felt for bane (samme mønster som spiller-type-ahead i
  roster-skjermen).
  **Reell korrekthetsfeil rettet FØR integrering:** V0-promptet mitt ba om
  ett generisk "Scramble"-valg, men skjemaets `CHECK`-constraint og
  `handicap_engine.py` sin `Format`-enum krever `scramble_2`/`scramble_4`
  som distinkte verdier -- ren `"scramble"` avvises med 400. Rettet i
  frontend-mappingen til to segment-knapper.
  **`allowance_override`-JSON-formen verifisert eksakt** mot
  `app/handicap.py` sin `parse_allowance_config`/`_strategy_from_json`
  (`{type: "combined"|"per_player", percentage: 0..1}`, ikke en flat
  prosent) -- frontend velger riktig `type` ut fra om formatet er
  side-enhet eller spiller-enhet, konverterer 0–100-skjemafelt til 0–1.
  **Scratch-infrastruktur denne runden, bevisst forskjellig fra tidligere:**
  brukte en ISOLERT `teecup_app_scratch`-rolle (`GRANT teecup_app TO
  teecup_app_scratch`) i stedet for den ekte `teecup_app`-rollen -- den er
  nå cluster-global og produksjonskritisk (delt Postgres-instans med
  `teecup_db`), så et eldre plandokuments "drop teecup_app-rolle"-
  opprydning (skrevet FØR go-live) er utdatert og ble bevisst IKKE fulgt.
  Egen isolert scratch-MinIO-container også (app-oppstart krever en
  nåbar MinIO for `ensure_bucket()`).
  **Verifisert:** courses opprettet+listet, kryss-org-isolasjon bekreftet,
  økt med klokkeslett, økt med `scramble_4`+full `allowance_override`-
  rundtur, gammel `"scramble"`-verdi avvist (400), `test_isolation.sql`
  12/12, ekte typesjekket PRODUKSJONSBUILD (samme `Dockerfile` som faktisk
  deployes) kjørt og bekreftet.
  **Diffet V0-eksporten mot live-treet FØR noe ble tatt inn** (samme mønster
  som alle tidligere runder): kun tre reelt nye filer, resten forventede
  full-reverts, ikke rørt. Fjernet V0s dev-only forhåndsvisnings-toggle;
  lagt til fanerad ("Lag og spillere" / "Program") i BEGGE skjermene siden
  V0 ikke visste om den andre når den ble generert i egen prompt.
  **Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: `docker
  compose up -d --build teecup_api teecup_frontend` (kun disse to, `teecup-
  minio` urørt). Verifisert: begge containere boot-et rent (`Application
  startup complete`, Next.js `Ready`), `teecup.teeoff.no/health` og
  `/dashboard` → 200, `teeoff.no` upåvirket (200).
- **ADR-004 (teeoff-banedata) kartlagt, IKKE bygget (2026-07-18):** brukeren
  påpekte rett etter program-skjerm-rullingen at ADR-004s teeoff-
  integrasjon fortsatt bare er vedtatt, ikke bygget (kun `source='custom'`
  finnes). Kartlagt `/opt/teeoff/backend/main.py` (annet repo, kun lest):
  `GET /api/facilities/{slug}` (offentlig, INGEN auth/API-nøkkel) returnerer
  allerede alt teecup trenger — `courses[].holes[]` (`par`, `hcp_index`
  = stroke index), `courses[].tees[]` (`name`, `cr_men`/`slope_men`,
  `cr_women`/`slope_women` — kjønnsdelt WHS-rating). CORS-lista på teeoff-
  siden ekskluderer teecups origin, men er IRRELEVANT for et server-til-
  server-kall (kun nettleser-fetch rammes av CORS). Ingen dokumentasjon
  (README/OpenAPI) finnes for dette API-et i teeoff-repoet — kartleggingen
  over er basert på å lese koden direkte. Stabil identifikator: `facilities.
  slug` (f.eks. `borregaard-golfklubb`) — selve banen har kun en intern
  serial-id, ingen egen slug, så `course.external_course_ref` må bære
  facility-slug + bane-id sammen.
  **Oppdatering samme dag: brukeren ba meg ta fatt på dette NÅ** (bevisst
  sidesprang fra "bygg i rekkefølgen ting brukes"-planen — blind draw-
  skjermen er fortsatt neste steg i den planen ETTERPÅ, ikke droppet).
  Design besluttet og skrevet som **ADR-019** (se ARCHITECTURE_DECISIONS.md):
  import (kopi) ved organisators eksplisitte valg, ikke live oppslag ved
  hver bruk — fryser data på importtidspunktet, samme reproduserbarhets-
  prinsipp som handicap-snapshotten (ADR-007). Server-til-server-kall
  (`teecup_api` → `http://teeoff_api:8000`, internt Docker-nettverk, ingen
  auth trengs). Ny migrasjon `010` (unik `external_course_ref` per org,
  hindrer dupliserte importer). Se ADR-019 for alle fem delbeslutningene.
  **Bygget, scratch-verifisert MOT EKTE `teeoff_api`** (ikke en simulert
  respons — ekte HTTP-kall til den kjørende produksjonscontaineren, kun
  lesing): søkte opp "Borregaard" i teeoff sine 174 publiserte anlegg,
  hentet Borregaard Golfklubb sin hovedbane (18 hull, 4 tee-farger ×
  kjønn), importerte den til en scratch-org — alle 18 hull med riktig
  par/stroke-index (1-18, unik), alle 8 tee/tee_rating-rader med riktig
  full_18 course/slope-rating verifisert direkte i databasen, opprettet
  deretter en ekte økt med den importerte banen som `course_id` (beviser
  hele veien til handicap-motoren fungerer, ikke bare selve importen).
  Reimport av samme bane korrekt avvist (409 DUPLICATE, migrasjon 010 sin
  indeks). Kryss-org-isolasjon bekreftet, ukjent teeoff-slug ga korrekt 404,
  `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild av
  frontend-utvidelsen (bane-søk i program-skjemaet: søk anlegg → velg bane →
  importer, samme UI-mønster som spiller-/bane-type-ahead ellers i appen).
  **Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: migrasjon 010
  kjørt mot ekte `teecup_db` (kun én ny partiell unik-indeks, ingen
  eksisterende rader rørt), begge containere bygget+redeployet, live
  sjekker OK, `teeoff.no` upåvirket. **"Bygg i rekkefølgen ting brukes"-
  planen gjenopptas nå** — blind draw-skjermen er neste steg.
  **Reell produksjonsbug funnet OG fikset samme dag, rapportert av bruker
  som faktisk brukte funksjonen:** brukeren klikket seg korrekt via
  dashbord → turnering → Program-fane (bekreftet med skjermbilde + full
  klikk-sti, ikke gjettet), åpnet "Hent bane fra teeoff", søkte "Tjøme", og
  fikk feilmeldingen "Mangler organisasjon i lenken" — URL-en hadde da
  MISTET `?org=...&name=...`. Root-cause: `OfficialCourseSearch` sitt eget
  søke-`<form onSubmit={runSearch}>` var rendret INNI `CreateSessionCard`
  sitt eksisterende `<form onSubmit={handleSubmit}>` -- nestede
  `<form>`-elementer er ugyldig HTML. Nettleseren slår sammen de to
  skjemaene i den faktiske DOM-en, så "Søk"-knappen submittet i praksis det
  YTRE økt-skjemaet som en ekte native side-navigasjon (GET til gjeldende
  sti, ingen navngitte felt => tom spørrestreng) -- dette vasket bort
  org-parameteren og landet brukeren på siden sin egen org-guard. **Fikset**
  ved å fjerne det indre `<form>`-elementet helt (vanlig `<div>` +
  Enter-tast-håndtering på inputet + `type="button"` i stedet for
  `type="submit"` på søkeknappen) -- gjør nestede skjemaer strukturelt
  umulig fremover for denne komponenten. Ekte typesjekket
  produksjonsbuild kjørt på nytt, kun `teecup_frontend` redeployet (ingen
  backend-endring). **Lærdom for fremtidige skjermer:** en ny
  søk-/underskjema-widget som skal plasseres INNI et eksisterende skjema
  (slik som denne bane-søk-widgeten ligger inni økt-opprett-skjemaet) må
  ALDRI være et eget `<form>` -- bruk `<div>` + 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ånd­skrevet `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å `<option>`-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 `<option>`-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 `<table>` med
  `<colgroup>` 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. **IKKE rullet ut ennå** — venter på
  brukerens bekreftelse. Frontend bevisst IKKE hånd-kodet denne gangen
  (V0-prompt skrevet i FEATURE_BACKLOG.md i stedet, matcher etablert
  mønster/tidligere korrigering).

Neste steg:
1. **Klart for bygging, venter på brukerens go-ahead:** dashbord-redesign
   (ADR-035) + venner/kategorisert deling (ADR-036), begge designet
   2026-07-25 (se status over og FEATURE_BACKLOG.md). Retningen er
   avklart (organisasjon blir usynlig/automatisk, dashbordet får syv
   konkrete blokker, venner bygges i tre uavhengige faser) — men INGEN
   av delene er bekreftet klar til bygging ennå. Naturlig neste steg:
   send V0-prompten for dashbordet (se FEATURE_BACKLOG.md) til v0.app,
   ELLER start på venner-kjernen (fase 1 av ADR-036), etter brukerens
   valg.
3. **Frittstående rundeføring + detaljert statistikk — ADR-033 skrevet
   2026-07-22, IKKE bygget.** Brukeren avklarte 2026-07-22 at dette skal
   bli appens HOVEDFOKUS (turneringsoppsett skal bli ekstremt enkelt
   ETTER dette er på plass) — den største enkeltbeslutningen i prosjektet
   siden ADR-001. 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/<uuid>` → 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, eksplisitt notert på brukerens
   forespørsel: offline-scoreregistrering er ALDRI browser-testet i
   praksis (kun kodegjennomgang + build-verifisering, se ADR-028). Bruker
   bør selv åpne et scorekort, skru på Chrome DevTools sin Offline-bryter,
   registrere et par slag, skru nettet på igjen, og bekrefte at de faktisk
   synkes — først da er flyten reelt bevist.
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.
