# TeeCup — CHANGELOG

> Dette er den kronologiske arbeidsloggen for prosjektet — hva som er
> bygget, når, hvorfor, hva som ble funnet og fikset underveis, og hvordan
> hvert steg ble verifisert FØR utrulling. Det er IKKE noe du trenger å
> lese i sin helhet i hver økt.
>
> **Les CLAUDE.md først** — den inneholder de ufravikelige reglene
> (sikkerhet, arkitektur-invarianter, tilgjengelighet, navneformat,
> autoritative kilder) som alltid gjelder, uavhengig av historikk.
>
> **Bruk DENNE filen når:**
> - du feilsøker en regresjon og trenger å vite hvordan/hvorfor noe ble
>   bygget som det ble (grep etter filnavn, ADR-nummer, eller feature-navn
>   under),
> - du skal gjøre en større endring i et område og vil sjekke om det
>   finnes tidligere fallgruver dokumentert her (f.eks. Caddy sin
>   stale-inode-oppførsel, MinIO sin understrek-i-hostnavn-feil,
>   RLS-tomstreng-bugen, service worker sin nettverk-først-var-egentlig-
>   cache-først-bug),
> - brukeren spør "har vi gjort dette før" eller "hvorfor er det sånn her".
>
> For ARKITEKTUR-BESLUTNINGER (hvorfor noe er designet som det er, ADR-
> nummerert), se `ARCHITECTURE_DECISIONS.md`. For GJENSTÅENDE arbeid, se
> `FEATURE_BACKLOG.md` (og "Neste steg" nederst i denne filen for en
> ferskere, mer detaljert punktliste). For dagens visuelle designsystem,
> se `DESIGN_SYSTEM.md`.

## 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. **Rullet ut live 2026-07-25**, bruker
  bekreftet eksplisitt: migrasjon 025 kjørt mot ekte `teecup_db` (begge
  tabeller bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
  `docker compose up -d --build teecup_api teecup_frontend`. Begge
  containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket. Verifisert presist at ruten faktisk når FastAPI (ikke bare
  at Next.js svarte): anonymt `GET /people/search`/`GET /friends` over
  ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. Frontend
  bevisst IKKE hånd-kodet denne gangen (V0-prompt skrevet i
  FEATURE_BACKLOG.md i stedet, matcher etablert mønster/tidligere
  korrigering) — venter på at brukeren kjører den i v0.app.

- **ADR-036 fase 1 FRONTEND BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):**
  zip 19 mottatt og integrert — kun `components/friends.tsx` og
  `app/friends/page.tsx` var reelt nye fra V0 (samme full-reeksport-
  mønster som alltid, resten forventede reverts av allerede tilpassede
  filer, hoppet over). **V0s egen rute (`/friends`) BEVISST IKKE brukt**
  — flyttet til `/my-friends`, siden `/friends` nå er API-prefikset
  (samme kollisjonsklasse som `/rounds` → `/my-rounds` tidligere, unngått
  fra start denne gangen i stedet for oppdaget i produksjon).
  Datalag skrevet fullstendig om fra V0s mock til ekte fetch mot
  `/people/search`+`/friends`-endepunktene — TS-typene speiler Pydantic-
  modellene i `app/routers/friends.py` felt-for-felt. Kategori-koder
  (`spouse`, `golf_friends`, osv.) mappet mot V0s norske visningsnavn via
  en delt `CATEGORY_OPTIONS`-liste i SAMME rekkefølge som backend sin
  `Category`-type. Søkefeltet håndhever samme 2-tegns-minimum som
  backend (viser en forklarende tekst i stedet for å bare returnere
  tomt). "Send forespørsel"/"Godta"/"Avslå"/"Kanseller"/"Fjern venn"
  trigger alle en full refetch av `GET /friends` etterpå (samme
  refetch-etter-mutasjon-mønster som resten av appen) — kun kategori-
  avkrysning er lokalt optimistisk (matcher PUT-endepunktets
  erstatt-hele-settet-kontrakt).
  **Dashbordets "Venner"-blokk koblet til ekte data i samme runde:**
  root-komponenten henter nå `GET /friends`, viser ekte avatar-initialer
  + antall venner + ventende forespørsler (samme visuelle design som
  opprinnelig i zip 18, som ble bevisst forenklet til en inert tom-
  tilstand forrige runde siden backend ikke fantes ennå) — begge
  knappene ("Se venner"/"Søk etter venner") lenker nå til `/my-friends`.
  Ekte typesjekket produksjonsbuild kompilerte rent, `/my-friends` listet
  blant 22 ruter. **Rullet ut live 2026-07-25**, bruker bekreftet
  eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt).
  Begge containere boot-et rent, `/health`/`/dashboard`/`/my-friends` →
  200, `teeoff.no` upåvirket. **ADR-036 fase 1 (venner-kjernen) er
  dermed helt ferdig, backend + frontend, live.**

- **Notat 2026-07-25: scramble-statistikk (utslag brukt per spiller) —
  IKKE bygget, kun fanget opp.** Brukeren ba om at scramble-turneringer
  skal føre statistikk over hvor mange ganger hver spillers utslag ble
  valgt av laget. Dette er reelt NY datamodell — dagens `hole_score`/
  `match_hole_result` for scramble er en delt rad per side, ingen
  kobling til HVILKEN spiller sitt utslag ble brukt. Trolig samme
  problemstilling for greensome. Krysset mot det allerede eksisterende
  "Scramble-grensesnitt"-punktet i ARCHITECTURE_DECISIONS.md sin "Åpne
  spørsmål"-seksjon, full detalj i FEATURE_BACKLOG.md.

- **Bug fikset: `display_name` synkroniserte aldri med for-/etternavn
  (2026-07-25), rapportert av bruker med skjermbilde av dashbordet.**
  `app_user.display_name` settes i dag KUN fra e-postens lokaldel ved
  kontoopprettelse (`verify_magic_link`) — ADR-031s profil-fullføring la
  til atskilte `first_name`/`last_name`-felt, men rørte aldri
  `display_name`. Konsekvens: en bruker med fullstendig utfylt profil
  viste fortsatt e-post-avledet plassholdernavn overalt `display_name`
  brukes (org-medlemslister, invitasjons-e-post, dashbord-hilsen — ikke
  bare dashbordet). **Fikset** i `app/routers/auth.py` sin
  `update_profile`: synkroniserer nå `display_name` automatisk til
  `"{first_name} {last_name}"` hver gang et av de to feltene endres via
  `PATCH /auth/profile` — KUN når begge er satt etterpå (unngår et
  halvferdig navn ved delvis utfylling). Scratch-verifisert (7/7 sjekker:
  fersk konto får fortsatt e-post-plassholder, delvis utfylling (kun
  fornavn) rører IKKE `display_name` ennå, komplett for-/etternavn
  synkroniserer korrekt, urelaterte PATCH-er (f.eks. HCP) lar
  `display_name` stå urørt, en SENERE navneendring re-synkroniserer på
  nytt).
  **Rullet ut mot ekte systemer 2026-07-25**, bruker bekreftet
  eksplisitt (valgte "kjør begge deler"): `teecup_api` redeployet, samt
  en engangs data-rettelse kjørt direkte mot ekte `teecup_db`
  (`UPDATE app_user SET display_name = ... WHERE first_name/last_name
  utfylt OG display_name avvek`) for å rette allerede-utfylte kontoer
  som ikke ville blitt rettet av seg selv (krever en NY navneendring for
  å trigge synk-koden). Bekreftet FØR kjøring med en dry-run `SELECT`
  (2 kontoer berørt: `erolhaagenrud@gmail.com` → "Tore Morell",
  `hei@erol.no` → "Erol Haagenrud"), kjørt med `RETURNING` for å
  bekrefte nøyaktig hvilke rader som ble endret, `test_isolation.sql`
  fortsatt 12/12 etterpå. Ingen migrasjon (ren datarettelse + kodefiks).

- **Varsler (push til telefon + in-app varslingssenter) — DESIGNET
  2026-07-25, IKKE bygget.** Brukeren spurte om dagens PWA kan varsle
  telefonens eget system (f.eks. ny venneforespørsel), og ba om en
  in-app-fallback (bjelle-indikator + uleste-side) med et V0-prompt klart
  "i tilfelle". Svar: ekte push er teknisk mulig (ADR-028s PWA-fundament),
  men iOS Safari krever PWA-en installert til hjemskjermen for at Web
  Push skal fungere i det hele tatt, pluss ny infrastruktur (VAPID,
  `push_subscription`-tabell, `pywebpush`-utsending, eksplisitt
  tillatelse) — anbefalt som EGEN, senere runde, ikke første steg.
  Anbefalte i stedet et in-app varslingssenter først (ny
  `notification`-tabell, triggerpunkter ved venneforespørsel sendt/
  akseptert, ny `/my-notifications`-rute — ikke `/notifications`, samme
  kollisjonsklasse unngått som `/rounds`/`/friends`). V0-prompt skrevet
  i FEATURE_BACKLOG.md, ikke sendt ennå. Ingen kode skrevet — ren
  design-/dokumentasjonsrunde.

- **Oppfølging samme dag: e-post-fallback for varsler + PWA-
  installasjon vurdert, IKKE bygget.** Varsel-V0-prompten sendt til
  bruker. Bruker foreslo en betinget e-post-fallback ved venneforespørsel
  (kun hvis mottaker har samtykket til e-post fra TeeCup) — sjekket at
  INGEN generell kommunikasjons-samtykke-flagg finnes i dag (kun
  ADR-017s turnering-registrerings-samtykke, noe annet), foreslo nytt
  opt-in `app_user.notification_emails_enabled` + en sjette
  `send_friend_request_email()`-funksjon i `app/email.py` (samme mønster
  som de fem eksisterende). Bruker spurte også hvordan få brukere til å
  installere PWA-en "nærmest umiddelbart" — vurdert grundig: Android kan
  fange `beforeinstallprompt` og vise egen timing, iOS Safari har INGEN
  programmatisk installasjonsvei (kun instruksjonsoverlegg mulig, hard
  Apple-begrensning) — anbefalte å vise oppfordringen rett etter
  obligatorisk profil-fullføring (universelt sjekkpunkt alle nye brukere
  allerede går gjennom) fremfor bokstavelig "umiddelbart". Begge kun
  vurdert/skissert i FEATURE_BACKLOG.md, ingen kode skrevet, intet
  V0-prompt for PWA-delen ennå.

- **To nye drøftingspunkter, IKKE besluttet eller bygget (2026-07-25):**
  bruker lastet opp to skjermbilder av Golf GameBooks leaderboard-løsning
  (egen "Leaderboards"-fane, rangert liste, trykk-ut til fullt
  scorekort) og spurte om (1) et tilsvarende leaderboard for
  runder/turneringer, usikker på plassering, og (2) om TeeCup burde få
  1-2 nye designfarger. Drøftet grundig i FEATURE_BACKLOG.md, ikke
  konkludert: fant at "runder" kan få dette NÅ (flere-deltakere-støtte
  finnes allerede, ingen ADR-036-avhengighet) mens "turneringer" sitt
  EKSISTERENDE leaderboard er lag-poeng (Ryder Cup-matchplay), strukturelt
  noe annet enn Golf GameBooks individuelle rangering — åpent
  avklaringsspørsmål. For fargespørsmålet: fant at en blåtone ALLEREDE
  finnes i `--chart-3` (bare ikke løftet til en kjerne-designtoken) —
  anbefalte å gjenbruke DEN i stedet for å finne på en helt ny, og
  anbefalte mot flere enn én ekstra farge. Ingen kode skrevet, ingen
  V0-prompt — ren refleksjonsrunde på brukerens eksplisitte instruks.

- **In-app varslingssenter + rundeleaderboard-backend BYGGET OG SCRATCH-
  VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** to ting i samme runde.
  (1) Brukeren lastet opp zip 20 — resultatet av varsel-V0-prompten fra
  2026-07-25 (bjelle-ikon i dashbord-header, egen varslingsside). Bygget
  matchende backend: migrasjon `026_notifications.sql` (tabell
  `notification`, `plain_connection()`-mønster som resten av bruker-eid
  data), `app/routers/notifications.py` (fire endepunkter + delt
  `create_notification()`-hjelpefunksjon), to trigger-punkter i
  `friends.py` (venneforespørsel sendt/akseptert — de eneste hendelsene
  som faktisk finnes i dag). Frontend integrert kirurgisk (kun de nye
  filene + et uttrekk fra V0s dashbord-eksport, ikke en full revert):
  `components/notifications.tsx` skrevet om fra V0s mock-scenario til
  ekte fetch, ny rute `/my-notifications` (IKKE `/notifications` — samme
  kollisjonsklasse unngått fra start som `/rounds`/`/friends`),
  bjelle-komponenten portert inn i den LIVE `dashboard.tsx` sin header,
  koblet til et ekte `GET /notifications/unread-count`-kall.
  (2) Brukeren ba samtidig om et leaderboard for frittstående runder
  (opptil 13+ spillere mulig, flere flighter). Bygget ny
  `GET /rounds/{round_id}/leaderboard` i `app/routers/rounds.py` — brutto
  OG netto score-til-par + "thru"-antall PER deltaker, sortert stigende
  på brutto. Netto bruker samme `allocate_strokes_by_index`-algoritme som
  resten av appen (`strokes_received` i `list_holes`), denne gangen
  summert over KUN de faktisk spilte hullene.
  **Scratch-verifisert grundig, 39/39 sjekker i to testløp** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container, samme mønster som hele prosjektet): full varsel-syklus begge
  retninger + `mark-all-read` + kryss-bruker-isolasjon (bruker B kan ikke
  markere bruker A sitt varsel som lest via id-gjetting), full
  leaderboard-runde (3 deltakere, ulik fremdrift/score, riktig rangering/
  thru/to-par for alle), kryss-bruker-autorisasjon (403), ukjent
  runde-id (404) — OG en dedikert netto-kryssjekk der resultatet ble
  sammenlignet mot en HELT UAVHENGIG beregning via
  `handicap_engine.allocate_strokes_by_index` kalt direkte fra
  testskriptet (ikke bare "endepunktet svarte 200") — stemte eksakt,
  inkl. et bevisst vekslende stroke-index-mønster og kun 9 av 18 hull
  spilt, for å teste allokeringen over et REELT delvis spilt sett, ikke
  et trivielt sammenfallende tilfelle. `test_isolation.sql` fortsatt
  12/12. Ekte typesjekket produksjonsbuild av frontend kompilerte rent,
  `/my-notifications` listet blant rutene.
  **V0-prompt for selve leaderboard-VISNINGEN skrevet og sendt til
  bruker** (se FEATURE_BACKLOG.md for hele prompten) — rangering med
  delt plassering, brutto/netto-veksling, "thru X"/"Ferdig"-tilstander,
  kompakt mini-variant til rundens detaljside, alltid form+farge for
  over/under par. Ikke kjørt i v0.app ennå — leaderboardets FRONTEND
  kommer i en senere runde.
  **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon
  026 kjørt mot ekte `teecup_db` (tabell `notification` bekreftet,
  `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
  --build teecup_api teecup_frontend`. Begge containere boot-et rent,
  `/health`/`/dashboard`/`/my-notifications` → 200, `teeoff.no`
  upåvirket. Verifisert presist at `/notifications`-ruten faktisk når
  FastAPI (ikke bare at Next.js svarte): anonymt `GET /notifications`
  over ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.

- **Navneformat-regel + turnering-scope avklart, IKKE bygget (2026-07-26):**
  brukeren instruerte at direkte adressering (hilsener, e-post/varsler
  rettet til mottakeren) alltid skal bruke KUN fornavn, mens vanlige
  visninger (lister, roster, chat) fortsatt bruker fullt navn/initialer —
  lagt til som ny stående regel (se egen seksjon over). Kjent brudd
  notert: dashbord-hilsenen bruker i dag fullt navn, ikke rettet ennå.
  Deretter bedt om analyse av .md-filene for prioritering — landet på å
  avklare turnering-leaderboard-spørsmålet fra 2026-07-25 først. Svaret
  avdekket et vesentlig STØRRE ønske enn antatt: TeeCup skal etter hvert
  støtte ekte INDIVIDUELLE turneringer (ikke bare lag), disse skal kunne
  gå over flere runder, og det skal være mulig med et Order of Merit
  (sesong-sammenlagt på tvers av flere arrangementer, brukerens eksempel:
  "klubbdager" gjennom en sesong). Grundig notert i FEATURE_BACKLOG.md og
  ARCHITECTURE_DECISIONS.md (ADR-011s eget notat + ny "Åpne spørsmål"-
  post 6) — inkl. en reell strukturell kollisjon identifisert: en
  flerrunde-individuell-turnering vil kollidere med ADR-033 sitt bevisst
  org-UAVHENGIGE frittstående rundesystem, må avklares eksplisitt før
  bygging. **Ren notat-runde, ingen ADR skrevet, ingen kode** — brukeren
  ba eksplisitt kun om at dette dokumenteres nå.
- **Flere flighter i én frittstående runde — presisert videre, fortsatt
  IKKE besluttet (2026-07-26):** oppfølgende avklaring samme runde.
  Brukeren presiserte: oppsett av flere flighter er ÉN handling utført av
  én person (samme gjest-mønster som i dag, bare flere flight-grupper),
  scoreregistrering per flight er et SENERE, separat ansvar (forventet
  løst av ekte medspillere, ADR-036 fase 3) — og viktigst: leaderboardet
  skal KUN dekke flightene som ble satt opp SAMMEN i én handling, ikke
  "alle som spilte samme bane samme dag". Det siste peker sterkt mot
  retning 1 (løs gruppering av separate `round`-rader) fra forrige
  runde, men er ikke formelt bekreftet som byggeretning. Brukeren
  påpekte selv at dette ligger i grenselandet mot "individuelle
  turneringer"-punktet rett over — notert eksplisitt som en mulig
  forening av de to, ikke to separate systemer. Se FEATURE_BACKLOG.md
  for full detalj. Ren notat-runde, ingen kode, ingen ADR.

- **ADR-037 skrevet: grunnstruktur for individuelle/flerrunde-turneringer
  (2026-07-26), samme dag som de to foregående notat-rundene.** Bruker ba
  om å starte ADR-runden på strukturspørsmålet direkte (ikke bare
  notere). Fire load-bærende beslutninger avklart eksplisitt
  (AskUserQuestion), i rekkefølge: (1) ny, PARALLELL org-scopet
  datamodell for individuelle turneringer — IKKE en utvidelse av
  ADR-033s frittstående `round`-tabeller, bevisst for å unngå hybrid/
  betinget RLS (samme risikoklasse som den tidligere RLS-tomstreng-
  bugen) og for å bevare ADR-033 Beslutning A urørt; (2) SAMME
  `tournament`-tabell med en ny `format_type`-diskriminator
  (`'team'`/`'individual'`), ikke en helt ny toppnivå-entitet —
  gjenbruker synlighet/join-kode/status (ADR-018/020) uendret; (3)
  flerrunde-støtte fra START via en ny `tournament_round`-tabell
  (økt-lignende), sammenlagt resultat summert ved lesing på ekte
  deltaker-id (samme prinsipp som dagens lag-leaderboard); (4) rå
  brutto slagtall lagres OG et ferdig utregnet poengtall CACHES per
  scoringsmetode — samme etablerte mønster som `match.status_text`/
  `points_side_a/b` — nye formater (Københavner m.fl.) blir dermed i
  hovedsak én ny motorfunksjon + én ny CHECK-verdi, ikke en
  skjemaendring.
  **Viktig presisering som oppsto underveis, endrer forrige rundes
  antakelse:** "flight" i en formell org-turnering er KUN en
  tee-tid-gruppering — leaderboardet spenner alltid HELE feltet.
  Dette er strukturelt ULIKT den ad hoc "flere flighter i en
  frittstående runde"-ideen (der leaderboardet bevisst avgrenses til
  det man selv satte opp) — de to holdes derfor bevisst ADSKILT, ikke
  forent slik forrige runde antydet. Begge berørte FEATURE_BACKLOG.md-
  seksjoner oppdatert med denne presiseringen.
  **Fortsatt IKKE avgjort:** de fem konkrete formatenes egne poengregler
  (Københavner m.fl., egne mindre design-runder oppå denne strukturen),
  og Order of Merit (sesong-sammenlagt, bekreftet som naturlig SISTE
  steg). **Ingen migrasjon eller kode skrevet** — ADR-037 er ren
  struktur-beslutning; neste steg er et konkret migrasjonsutkast
  (nye tabeller + `tournament.format_type`) lagt frem til gjennomgang
  før noe kjøres.

- **"Fiks alle kjente små bugs"-runde, BEGGE FIKSET OG SCRATCH-VERIFISERT
  (2026-07-26), samme dag som ADR-037:** brukeren ba eksplisitt om å
  rydde opp i alle dokumenterte, fortsatt åpne små bugs. Systematisk
  gjennomgang av CLAUDE.md/FEATURE_BACKLOG.md (søk på "IKKE fikset"/
  "urelatert bug" m.fl.) fant at nesten alt tidligere flagget som
  "ikke fikset" faktisk ble fikset i en SENERE runde lenger ned i loggen
  (stale seksjonsoverskrifter, ikke reelt åpne bugs) — kun to genuint
  fortsatt åpne:
  1. **Dashbord-hilsen brukte fullt navn** (nettopp etablert
     navneformat-regel, se egen seksjon over) — `me.first_name ??
     me.display_name` i stedet for `me.display_name` alene.
     `/auth/me` eksponerte allerede `first_name`, ingen backend-endring.
  2. **Stroke-modus-scoreinnsending på en bane UTEN registrerte hull
     krasjet rått (500 IndexError)** i stedet for en ren
     `VALIDATION_FAILED` — `scoring.py` sin `_compute_hole_results`
     kalte `allocate_over_played_holes` med en TOM stroke-indeks-liste
     når `hole`-tabellen var tom for banen (f.eks. en egendefinert bane
     der noen glemte å legge til de 18 hullene). Fikset med en eksplisitt
     `len(all_18_si) != 18`-sjekk RETT FØR beregningen — samme mønster
     som den allerede kjente/fikset manglende-handicap-indeks-krasjen
     fra blind draw-runden (2026-07-18).
  **Scratch-verifisert grundig (5/5 sjekker)** for backend-fiksen
  (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
  API-container, samme mønster som ellers i prosjektet): en bane UTEN
  hull ga korrekt `400 VALIDATION_FAILED` ved scoreinnsending (ikke
  500), OG en egen regresjonssjekk bekreftet at en NORMAL bane (alle 18
  hull) fortsatt scorer helt uendret (begge sider, full
  scorekort-henting via `GET .../scorecard`). `test_isolation.sql`
  12/12 uendret — ren Python-logikk-fiks, ingen migrasjon. Ekte
  typesjekket produksjonsbuild av frontend kompilerte rent (dashbord-
  fiksen). To stale FEATURE_BACKLOG.md-seksjonsoverskrifter rettet
  samtidig ("Rapporterte UI-/UX-hull" og selve bug-notatet), siden de
  fortsatt sa "IKKE fikset" lenge etter at innholdet faktisk var
  fikset.
  **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
  begge containere boot-et rent (`Application startup complete`, Next.js
  `Ready`), `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.

- **ADR-036 fase 3 (delvis): søk+legg til ekte medspiller på en
  frittstående runde, BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE
  ENNÅ RULLET UT:** brukeren rapporterte at "+ Gjest"-skjemaet ikke
  søkte etter spillere når man skrev et navn — bekreftet reelt (rent
  tekstfelt, `/people/search` var aldri koblet på). Spurte om
  omfang før bygging: brukeren ville ha "full tilgang nå" (medspilleren
  kan selv registrere score), ikke bare rask utfylling — et bevisst
  større valg enn det anbefalte minimum.
  **Backend:** `POST /rounds/{id}/participants` tar nå ENTEN `user_id`
  (funnet via tiered `/people/search`, samme algoritme som vennesøket)
  ELLER `guest_name` (uendret) — kjønn/HCP hentes automatisk fra den
  valgte personens egen profil. Ny migrasjon `027_round_participant_
  user_unique.sql` (partiell unik indeks). Ny `_get_accessible_round_
  or_404` (eier ELLER lenket medspiller) for lesing/hull-scoring/
  fullføring — `_get_owned_round_or_404` (strengt eier-only) beholdt
  for rediger/slett/legg til/fjern/endre stat_level. `RoundOut` sine
  `owner_*`-felt omdøpt til `my_*` og gjort VIEWER-relative (regnes nå
  fra den spørrende brukerens egen deltaker-rad).
  **Reell latent bug funnet OG fikset i SAMME runde, før den nådde
  produksjon:** leaderboard-endepunktet (bygget tidligere samme dag,
  se over) ville vist et TOMT navn for enhver lenket medspiller, siden
  dets `display_name`-utledning kun sjekket `is_owner`, ikke om
  `user_id` var satt i det hele tatt. Fanget under scratch-testing av
  DENNE runden, fikset før utrulling av noen av delene.
  **Frontend:** "+ Gjest" omdøpt til "+ Medspiller" i `round-detail.tsx`,
  nytt søk-som-du-skriver-felt (avatar-initialer, hjemmeklubb) med
  "Legg til uten konto"-fallback til det gamle tekstfeltet. Viewer-
  relativ "Deg"-visning (ny `/auth/me`-bruk for viewerId) — samme fiks
  portert til `round-stats.tsx`/`round-scorecard.tsx`, som hadde
  identisk latent bug (ville vist eieren som "Deg" for en medspiller
  som så på). Rediger/slett/legg-til/fjern-knappene skjules nå for en
  ikke-eier-viewer.
  **Scratch-verifisert grundig, 39/39 sjekker i to testløp:** søk-og-
  legg-til med auto-utfylt kjønn/HCP, avvist duplikat/selv-tillegg/
  ukjent bruker/ufullstendig profil, lenket medspiller kan lese runden
  + registrere score for BÅDE egen OG andres rad (whole-flight-
  regelen bekreftet), men nektes å forvalte runden (alle
  forvaltningskall 403), lenket medspiller KAN fullføre runden,
  `/rounds`-listen viser nå runden for en lenket medspiller med DERES
  EGEN fremdrift (ikke eierens), urelatert bruker fortsatt 403/ikke i
  listen, leaderboard-navn stemmer for alle tre deltakertyper.
  `test_isolation.sql` 12/12 uendret. Ekte typesjekket produksjonsbuild
  kompilerte rent.
  **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon
  027 kjørt mot ekte `teecup_db` (unik indeks bekreftet,
  `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
  --build teecup_api teecup_frontend`. Begge containere boot-et rent,
  `/health`/`/dashboard`/`/my-rounds` → 200, `teeoff.no` upåvirket.

- **Sanntid for frittstående runder, BYGGET OG SCRATCH-VERIFISERT
  (2026-07-26), samme dag som medspiller-søket over:** brukeren spurte om
  spillere ser i sanntid at en annen har registrert en score — svaret var
  nei (kun engangs-henting ved lasting), og brukeren ba om at det bygges,
  gjenbruk av det etablerte "noe endret seg, hent på nytt"-WebSocket-
  mønsteret fra ADR-027 ("Følg live" for turneringer).
  `app/realtime.py` utvidet (fortsatt rutefri, se moduldoc) med en andre,
  parallell kringkastings-registry for runder (`live_sockets_for_round`/
  `broadcast_round_update`, delt `_broadcast()`-hjelpefunksjon for å unngå
  duplisert kringkastingslogikk). Nytt `@router.websocket("/ws/rounds/
  {round_id}/live")` i `rounds.py` — ALDRI anonym tilgang (ulikt
  turnering-live), krever eier ELLER lenket medspiller
  (`_get_accessible_round_or_404`, samme sjekk som resten av
  medspiller-utvidelsen). Kringkasting lagt inn i `update_hole`,
  `complete_round`, `add_participant`, `remove_guest_participant`,
  `update_round` og `delete_round` — alt som endrer noe de andre på
  skjermen bør få vite om. Ingen ny Caddy-endring nødvendig (`/ws/*` er
  allerede en wildcard-rute fra ADR-025).
  **Reelt funn under scratch-testing, ikke en bug, men verdt å dokumentere:**
  Starlette avviser en WebSocket FØR `.accept()` alltid som en bar HTTP
  403 under selve håndtrykket — de tre distinkte lukkekodene (4401/4403/
  4404) jeg satte når til `websocket.close()` server-side, men skiller seg
  IKKE fra hverandre i klientens håndtrykk-avvisning (alle tre ga HTTP 403
  i en ekte WS-klienttest, ikke bare curl). Selve sikkerheten (tilkobling
  korrekt avvist i alle tre tilfeller) er upåvirket — samme underliggende
  Starlette-oppførsel gjelder trolig også den eksisterende turnering-live-
  ruten (ADR-027), bare ikke tidligere testet med en ekte WS-klient på
  dette presisjonsnivået.
  **Frontend:** `round-detail.tsx` åpner en WebSocket ved montering,
  refetcher runden ved signal og henter aktiv spillers hull DIREKTE på
  nytt (ingen mellomsteg med tom stat som ville blinket for spilleren som
  selv nettopp registrerte et slag) — andre spilleres hull-cache droppes
  i stedet, hentes friskt ved neste fanebytte. Bruker en ref for å unngå
  at socket-en kobles til/fra ved hvert fanebytte. `round-stats.tsx`/
  `round-scorecard.tsx` (rene lesevisninger) fikk en enklere
  `refreshKey`-basert variant, samme mønster som `public-live.tsx` fra
  ADR-027.
  **Scratch-verifisert grundig, 10/10 sjekker, ekte WebSocket-klient (ikke
  bare REST):** ekte kringkasting bekreftet begge veier — eierens åpne
  socket mottok et signal da medspilleren registrerte et slag via REST,
  OG medspillerens åpne socket mottok et signal da eieren fullførte runden
  — ikke bare at REST-svarene så riktige ut. Alle tre avvisningstilfellene
  (uautentisert, urelatert fremmed, ukjent runde-id) korrekt avvist.
  `test_isolation.sql` 12/12 uendret (ingen migrasjon). Ekte typesjekket
  produksjonsbuild kompilerte rent.
  **Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
  Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket. Verifisert presist at `/ws/rounds/*`-ruten faktisk når
  FastAPI: et ekte WS-håndtrykk-forsøk mot en ukjent runde-id over
  produksjons-https ga FastAPI sin egen JSON-`{"detail":"Not Found"}`,
  ikke Next.js sin HTML-404 — samme verifiseringsmønster som ADR-027s
  tournament-live-rute.
- **HCP i medspiller-søk + rediger utslag/HCP per deltaker, BYGGET OG
  SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren viste et
  skjermbilde av "+ Medspiller"-søket og påpekte at spillerens HCP burde
  vises der (kun navn+klubb vistes), og ba samtidig om å kunne endre
  utslagssted og HCP for medspillere -- "det er ikke sikkert de spiller fra
  samme utslagssted som meg, og det er ikke sikkert HCP er riktig", eksplisitt
  presisert som en endring KUN for denne runden/turneringen, ikke spillerens
  faktiske profil.
  **Reelt hull bekreftet ved kodegjennomgang FØR bygging:** ALLE deltakere
  på en runde har i dag brukt SAMME `round.tee_name_snapshot` -- ingen
  per-deltaker-tee har noensinne eksistert (rating-tallene har vært per
  deltaker siden migrasjon 021, men selve utslags-NAVNET var aldri
  eksplisitt per rad).
  **Bygget:** `PersonMatch` (`app/routers/friends.py`, delt av vennesøk OG
  medspiller-søk) fikk `handicap_index: float | None`. Ny migrasjon
  `028_round_participant_tee.sql` -- `round_participant.tee_name_snapshot`,
  backfylt fra rundens eksisterende tee for alle eksisterende rader. `_create_
  participant` skriver nå tee-navnet eksplisitt; `ParticipantCreate` (add_
  participant) fikk et valgfritt `tee_name` som faller tilbake til rundens
  tee hvis utelatt. Ny `GET /rounds/{id}/tee-options` (gjenbruker
  `_ResolvedCourse` sin rating-dict via en ny `tee_options()`-metode) --
  brukt av "rediger spiller"-panelet til å liste banens faktiske utslag.
  `ParticipantUpdate` skrevet om til vanlig `exclude_unset`-PATCH-semantikk
  (var tidligere kun `stat_level`, alltid påkrevd) -- nye valgfrie
  `tee_name`/`handicap_index`-felt trigger en full re-beregning av
  rating-snapshottene + `course_handicap_snapshot` for AKKURAT den
  deltakeren, uten å røre spillerens egen `app_user.handicap_index`.
  Eksplisitt `null` på `handicap_index` fjerner HCP-sporing for denne
  deltakeren i denne runden (samme mønster som profil-PATCH). **Bevisst
  avvist etter at runden er fullført** (409 `ALREADY_COMPLETED`, samme
  presedens som `RoundUpdate` sitt bane-bytte -- differensialen er da
  allerede beregnet fra det gamle grunnlaget); `stat_level` alene er
  fortsatt tillatt uansett fullført-status. `RoundUpdate` sitt eksisterende
  bane-bytte (ADR-033-oppfølging 2026-07-24) nullstiller nå eksplisitt ALLE
  deltakeres `tee_name_snapshot` til rundens nye utslag (et bane-bytte gjør
  individuelle tee-valg fra den gamle banen meningsløse).
  **Frontend (`round-detail.tsx`):** søkeresultatene i "+ Medspiller" viser
  nå HCP ved siden av hjemmeklubb. Ny "Utslag: X · HCP: Y"-rad under
  statistikknivå-velgeren for aktiv spiller, med en "Rediger for denne
  runden"-lenke (kun eier, kun før fullført) som åpner en ny
  `EditParticipantPanel` -- utslagssted som en `<select>` fylt fra det nye
  tee-options-endepunktet (filtrert på spillerens kjønn), HCP som fritekst,
  forklarende "endrer ikke profilen"-tekst. Gjelder likt for eierens egen
  rad som for medspillere (ingen spesialtilfelle).
  **Scratch-verifisert, 27/27 sjekker** (isolert `teecup_app_scratch`-
  rolle + isolert scratch-MinIO + engangs API-container): HCP i søk, legg
  til medspiller med eksplisitt AVVIKENDE utslag fra eieren, gjest uten
  eksplisitt tee faller korrekt tilbake til rundens tee, utslag uten rating
  for kjønn avvist (400) både ved tilføyelse og redigering, tee-options
  lister riktige utslag, rediger tee+HCP for medspiller lykkes og
  course_handicap regnes om, **medspillerens egen profil-HCP forblir
  UENDRET** (den kritiske sjekken -- bekrefter runde-scoping), delvis PATCH
  (kun stat_level) lar tee/HCP stå urørt, eksplisitt `null` fjerner HCP
  round-scoped, lenket medspiller (ikke eier) nektes å redigere deltakere
  (403, forvaltning fortsatt eier-only), tom PATCH avvist (400), redigering
  blokkert etter fullføring (409) mens stat_level fortsatt tillates. Full
  regresjonskjøring av forrige rundes 35-punkts medspiller-søk-testsuite
  (`test_round_coplayer.py`) mot samme friske scratch-database, alle 35
  fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket
  produksjonsbuild kompilerte rent.
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  migrasjon 028 kjørt mot ekte `teecup_db` (kolonne bekreftet `NOT NULL`,
  2 eksisterende rader backfylt korrekt, `test_isolation.sql` fortsatt
  12/12), deretter `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent (`Application startup
  complete`, Next.js `Ready`), `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket.
- **V0-prompt for rundeleaderboardet rettet (2026-07-26), FØR den ble
  kjørt i v0.app:** samme runde som over, brukeren ba eksplisitt om å få
  leaderboardet for frittstående runder på plass. Backend
  (`GET /rounds/{id}/leaderboard`) var allerede live siden tidligere samme
  dag; V0-prompten (skrevet samme dag, se FEATURE_BACKLOG.md) hadde
  derimot en utdatert antakelse -- den ba om et "Deg"-merke basert på at
  kun eieren noensinne ser sin egen runde, som ikke lenger stemmer etter
  ADR-036 fase 3 (medspillere kan nå også se runden). Rettet til to
  uavhengige merker: "Eier" (fra API-ets `is_owner`) og "Deg" (fra en
  `participant_id`-prop komponenten mottar utenfra, ikke fra selve
  leaderboard-dataen). Prompten er ikke sendt til v0.app ennå.
- **Mottatte slag i hull-headeren + gjeste-e-post/kjønn/navn redigerbart,
  BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren
  viste et skjermbilde av det nettopp rullede "Rediger for denne runden"-
  tillegget og ba om to ting: (1) hvor mange slag aktiv spiller MOTTAR på
  det aktive hullet vist i selve hull-headeren (eksempel "Hull 7 - Par 4 -
  Hcp 5 - -1"), og (2) for en midlertidig spiller (gjest) skal Navn/Kjønn/
  E-post også være redigerbart, ikke bare utslag/HCP. Ba samtidig om et
  V0-prompt for å designe om selve spillerlisten (utslag/HCP/rediger inn i
  selve spillerknappen, listet vertikalt i stedet for dagens horisontale
  scroll-rad).
  **Punkt 1 bygget direkte** (triviell, ren frontend, ingen backend-endring
  -- `strokes_received` var allerede hentet fra `GET .../holes` fra før,
  bare aldri vist FØR scoring): `round-detail.tsx` sin hull-header viser nå
  `· −N` (ekte minustegn, golfvis fortegn) når aktiv spiller mottar minst
  ett slag på hullet, ellers uendret.
  **Punkt 2 krevde ny backend:** ny migrasjon `029_round_participant_
  guest_email.sql` (`round_participant.guest_email`, nullable). `Participant
  Create` fikk et valgfritt `guest_email: EmailStr | None` (avvist sammen
  med `user_id`). `ParticipantUpdate` fikk `guest_name`/`gender`/
  `guest_email` -- KUN gyldig for en gjest (`user_id IS NULL`), avvist
  (400) på en lenket deltaker. `gender`-endring inngår nå i samme "rating_
  changed"-bunt som utslag/HCP (påvirker gyldig rating), avvist etter
  fullføring (409, samme presedens); `guest_name` alene har ingen rating-
  implikasjon og forblir redigerbar selv etter fullføring (ren metadata).
  **Frontend for punkt 2 IKKE bygget ennå** -- overlatt til V0-prompten
  (se FEATURE_BACKLOG.md), siden brukeren eksplisitt ba om det og den
  eksisterende hånd-bygde `EditParticipantPanel` uansett skal erstattes av
  den nye vertikale spillerlisten.
  **Scratch-verifisert, 22/22 nye sjekker** (gjest med e-post ved
  opprettelse, avvist sammen med user_id, navn+kjønn+e-post-redigering
  lykkes og rating regnes om for nytt kjønn, eksplisitt null fjerner
  e-post, kjønnsendring til urepresentert rating avvist med full rollback,
  alle tre gjeste-feltene avvist på en LENKET deltaker mens utslag/HCP
  fortsatt fungerer uendret der, kjønn/utslag/HCP-endring blokkert etter
  fullføring mens navneendring fortsatt tillates). Full regresjonskjøring
  av samme dags 27+35-punkts testsuiter mot samme friske scratch-database,
  alle 62 fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket
  produksjonsbuild kompilerte rent (inkl. punkt 1).
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  migrasjon 029 kjørt mot ekte `teecup_db` (kolonne bekreftet,
  `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
  --build teecup_api teecup_frontend`. Begge containere boot-et rent,
  `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **Spillerliste-redesign HÅNDKODET (2026-07-26), IKKE ENNÅ RULLET UT:**
  brukeren gikk tom for V0-credits rett etter at prompten over ble sendt.
  Spurt eksplisitt (AskUserQuestion): bygg direkte, eller vent på fornyede
  credits — svar: bygg direkte. Bygget nøyaktig etter samme prompt/data-
  kontrakt som allerede var skrevet (se FEATURE_BACKLOG.md for full
  detalj), i samme Tailwind/shadcn-stil som resten av `round-detail.tsx`.
  Ny `PlayerList` (erstatter `PlayerTabs`) — vertikal liste av spillerkort,
  hvert kort en stor "velg som aktiv spiller"-knapp + separate Rediger-/
  fjern-knapper (unngår nestede interaktive elementer), "Deg"/"Eier" som
  `Badge`-komponenter. `EditParticipantPanel` utvidet med statistikknivå
  (den frittstående `StatLevelPicker` er nå død kode, fjernet) og — kun
  for gjester — Navn/Kjønn/E-post, med reaktivt utslagsfilter ved
  kjønnsendring. "+ Medspiller"-flyten flyttet inn i selve `PlayerList`.
  **Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, pluss
  en engangs `next dev`-container mot scratch-backend (ekte runde med en
  gjest med avvikende utslag/kjønn/e-post) — siden ga 200, ingen Next.js-
  feilside. **Ærlig begrensning:** siden er en klient-komponent (data
  hentes etter hydrering), så server-HTML-en viser kun last-skjelettet —
  INGEN ekte nettleser-interaksjonstest utført (intet nettleserverktøy
  tilgjengelig).
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen ny migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket. **Bruker bør selv klikke gjennom flyten**
  (spesielt gjeste-kjønnsendringens reaktive utslagsfilter og "velg vs.
  rediger"-trykkflatene) før full tillit.
- **Tildelte slag + jevn korthøyde + rundeleaderboard, HÅNDKODET, IKKE ENNÅ
  RULLET UT (2026-07-26):** to oppfølgingspunkter samme dag, full detalj i
  FEATURE_BACKLOG.md. (1) Spillerkortet manglet "tildelte slag" (course
  handicap for runden) og hadde variabel høyde — rettet: `Player` fikk
  `courseHandicap`, kortet fikk fast `min-h-[68px]` med begge tekstlinjer
  trunkert til én linje (ikke wrap), alle kort like høye uansett innhold.
  (2) Brukeren spurte om jeg "med designerbrillene på" trodde jeg kunne få
  rundeleaderboardet til å se like profesjonelt ut som V0 — svarte ja
  (gjenbruk av etablerte mønstre, ikke fri utforskning) og bygget det: ny
  `components/round-leaderboard.tsx` + rute `/my-rounds/[id]/leaderboard`,
  gjenbruker `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`
  (som `ToParMark`), samme WS-sanntid-mønster som `round-stats.tsx`, delt
  plassering ("T-N"), brutto/netto-veksling, mini-variant på rundens egen
  side. Rangeringslogikken VERIFISERT UAVHENGIG i et frittstående
  Node-script (19/19, inkl. tie-håndtering og uspilte spillere), pluss en
  fersk scratch-runde med ekte API-kall som bekreftet leaderboard-JSON-en
  stemte. Ekte typesjekket produksjonsbuild kompilerte rent. Samme ærlige
  begrensning som over: ingen ekte nettleser-interaksjonstest.
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Hullscorer i leaderboardet + lenke fra rundelisten, BYGGET, IKKE ENNÅ
  RULLET UT (2026-07-26):** brukeren presiserte rett etter forrige rulling
  tre krav: leaderboardet skal ha en egen visning (allerede tilfellet, se
  over), med lenke fra scoreføringssiden (allerede der, `RoundLeaderboardMini`)
  OG fra rundelisten (`/my-rounds`, manglet), og skal vise hullscorer
  (manglet helt -- kun aggregerte tall fantes).
  **Backend:** `LeaderboardEntryOut` (`GET /rounds/{id}/leaderboard`) fikk
  et nytt `holes: list[LeaderboardHoleOut]`-felt (hole_number/par/
  stroke_index/played/score) -- data var ALLEREDE hentet per deltaker for
  å beregne total_score/net_score_to_par, bare aldri eksponert rått før nå.
  Ren tilføyelse, ingen migrasjon.
  **Frontend:** `round-leaderboard.tsx` sine rader er nå klikkbare
  (utvider/kollapser, default kollapset -- samme "trekkspill starter
  lukket"-konvensjon som `round-stats.tsx`) og viser en horisontal strip
  med per-hull-merker (`HoleMark`/`HoleStrip`, egen lokal variant av
  `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`, tilpasset en
  kompakt flerspiller-liste i stedet for én tabell). `round-card.tsx`
  (brukt av BÅDE `/my-rounds`-listen og dashbordets "Kommende runder") fikk
  en ny "Se leaderboard"-lenke for runder med mer enn én deltaker --
  krevde å gjøre om kortets ytre element fra selve lenken til en `<div>`
  med lenken som ETT av to barn (unngår nestet `<a>`, samme feilklasse som
  tidligere nestet-form-/knapp-feller i prosjektet).
  **Verifisert:** backend-endringen bekreftet mot en fersk scratch-runde
  (ekte API-kall, `holes`-arrayet inneholder riktige 18 rader per deltaker
  inkl. korrekt `played`/`score` for både spilte og uspilte hull), full
  regresjon av forrige rundes 35-punkts co-player-testsuite fortsatt grønn,
  `test_isolation.sql` 12/12 (uendret, ingen migrasjon). Ekte typesjekket
  produksjonsbuild kompilerte rent (ingen nye ruter, kun endrede
  komponenter). Samme engangs `next dev`-container-sjekk som tidligere --
  `/my-rounds`, `/my-rounds/[id]` og `/my-rounds/[id]/leaderboard` ga alle
  200, ingen Next.js-feilside.
  **Samme ærlige begrensning som tidligere håndkodede runder:** ingen ekte
  nettleser-interaksjonstest (klikk for å utvide en rad, se hull-stripen,
  se "Se leaderboard"-lenken i rundelisten) er utført.
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Leaderboard-oppfølging: poeng-modus + minimalistiske rader, BYGGET, IKKE
  ENNÅ RULLET UT (2026-07-26):** brukeren påpekte at brutto/netto ble
  presentert forskjellig (bryteren byttet BÅDE hvilket tall som vises OG
  hele rangeringsrekkefølgen -- forvirrende hopp), og spurte om et eget
  stableford-alternativ burde vært med. Foreslo og fikk bekreftet: gi alle
  tre modiene (nå: Brutto/Netto/**Poeng**) IDENTISK radform (rangering,
  navn, ETT tall, ferdig) i stedet for å prøve å vise flere tall samtidig.
  Samtidig ba brukeren om å fjerne alt fra den kollapsede raden utover
  navn+tall (Deg/Eier/pokal/hull-spilt-tekst) og fjerne selve
  "kort"-følelsen -- radene skal flyte sømløst sammen, kun ekspandere ved
  klikk.
  **Backend:** `LeaderboardHoleOut` fikk `strokes_received` (samme
  allerede-beregnede allokering som `net_score_to_par`, nå eksponert per
  hull også). `LeaderboardEntryOut` fikk `total_points` (stableford-total,
  samme formel/betingelse som `net_score_to_par` -- null uten HCP). Ingen
  migrasjon.
  **Frontend (`round-leaderboard.tsx`):** `Mode` utvidet til tre verdier,
  poeng rangeres SYNKENDE (motsatt av til-par). Ny `PointsMark`/`ValueMark`
  (poeng har ingen retning, kun fylt/uthevet for lederen). Listen er nå ÉN
  sammenhengende `<ul>` (`divide-y`, kun ytterkanten avrundet/rammet) i
  stedet for separate kort med mellomrom mellom hver rad -- ingen
  per-rad-bakgrunn utenom en svak tone på en UTVIDET rad. Kollapset rad:
  kun rangering+navn+tall+pil. Deg/Eier/pokal/fremdrift FLYTTET (ikke
  fjernet) inn i den utvidbare seksjonen sammen med hull-stripen.
  **Verifisert:** rangeringslogikken for poeng-modus (synkende sortering,
  delt plassering, uten-HCP sortert nederst) UAVHENGIG testet i Node
  (10/10). Selve stableford-formelen verifisert mot en fersk scratch-runde
  med HÅNDREGNET forventet resultat (course handicap 9 over 18 hull →
  slag KUN på de 9 laveste stroke-index-hullene, ikke jevnt fordelt --
  fanget en feilaktig antakelse i testens FØRSTE versjon, rettet før
  den ble rapportert som bestått) -- 11/11, inkl. kryssjekk av
  `total_points`/`net_score_to_par` mot uavhengig rekalkulering fra de
  samme rå hull-dataene API-et returnerte. Full regresjon (35-punkts
  co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12. Ekte
  typesjekket produksjonsbuild kompilerte rent. Samme engangs `next
  dev`-container-sjekk som tidligere -- alle tre rutene ga 200.
  **Samme ærlige begrensning:** ingen ekte nettleser-interaksjonstest av
  det faktiske "sømløse rader"-uttrykket eller poeng-modusen.
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Hullscorer i leaderboardet: netto/poeng under brutto, BYGGET, IKKE ENNÅ
  RULLET UT (2026-07-26):** brukeren viste to referansebilder fra en
  konkurrentapp (Golf GameBook) sin leaderboard-visning -- brutto fremhevet
  (fylt/farget merke) på én rad, netto/stableford i vanlig tekst RETT under,
  eksplisitt presisert at det skal se likt STRUKTURELT ut men IKKE være en
  kopi visuelt. Ren frontend-endring, ingen backend-endring (all data --
  `strokes_received` per hull -- var allerede lagt til forrige runde samme
  dag).
  **`HoleMark`** (`round-leaderboard.tsx`) fikk en tredje, umerket tekstlinje
  RETT under det fremhevede brutto-merket, som viser netto eller
  stableford-poeng for AKKURAT det hullet -- kun i netto-/poeng-modus (ingen
  ny linje i brutto-modus, ingenting nytt å vise der). Nye lokale
  `netForHole()`/`pointsForHole()`-hjelpefunksjoner (samme formel som
  backend sin `total_points`, men per hull -- bruker `strokes_received`
  direkte fra API-et, regner ALDRI ut egen slagfordeling). `HoleStrip` fikk
  en liten "Slag · Netto"/"Slag · Poeng"-bildetekst over stripen når en
  sekundærrad faktisk vises.
  **Bevisst IKKE en kopi:** beholder TeeCups egne farger/former (sirkel/
  firkant, grønn/oransje) fra `ScoreMark`-språket som allerede var etablert
  -- endret ikke fargevalg for å etterligne referansebildets blåtoner, kun
  gjenskapte det STRUKTURELLE prinsippet (fremhevet brutto øverst, rolig
  sekundærtall under).
  **Verifisert:** netto/poeng-per-hull-formelen testet UAVHENGIG i Node
  (9/9, inkl. et gulv-på-0-tilfelle og manglende-HCP/uspilt-hull), samme
  tall som det håndregnede scratch-scenarioet fra forrige runde samme dag
  (hull med slag: netto 3/poeng 3, hull uten slag: netto 5/poeng 1). Ekte
  typesjekket produksjonsbuild kompilerte rent. Samme engangs `next
  dev`-container-sjekk som tidligere -- leaderboard-ruten ga 200.
  **Samme ærlige begrensning som resten av de håndkodede rundene:** ingen
  ekte nettleser-interaksjonstest av selve det visuelle uttrykket.
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Scoringsflyt: samlebånd-fremdrift + golf-term-taltastatur, BYGGET, IKKE
  ENNÅ RULLET UT (2026-07-26), inspirert av en konkurrentapp (Golf
  GameBook):** brukeren delte en skjermopptaksvideo av GameBooks
  score-registrering og spurte om jeg forstod HVORFOR den flyten fungerer
  bedre enn TeeCups, og om jeg kunne bygge det selv (ingen V0-credits
  igjen). Video analysert bilde for bilde (ffmpeg i en engangs Docker-
  container, samme mønster som en tidligere videoanalyse i prosjektet) --
  identifiserte presist samlebånd-mønsteret: taltastatur med kontekstuelle
  golf-termer per tall (Eagle/Birdie/Par/Bogey ut fra hullets par, ikke
  bare "par"-knappen merket), og at fullført registrering for én spiller
  automatisk åpner NESTE spillers registrering for samme hull -- uten å
  måtte navigere manuelt tilbake til en spillerliste.
  **Bevisst IKKE en full skjermovertakende steg-for-steg-modal** (GameBooks
  egen løsning) -- vurdert som unødvendig risikofylt å bygge korrekt uten
  visuell testing. I stedet: samme etablerte inline-side beholdt, men to
  konkrete forbedringer lagt til:
  1. `NumberPicker` sin Slag-instans fikk en ny `showGolfTerms`-modus --
     HVERT synlig tall viser nå Albatross/Eagle/Birdie/Par/Bogey/Dobbel
     bogey relativt til hullets par (ikke bare selve par-knappen som før).
     Ny lokal `golfTermForScore()`-hjelpefunksjon.
  2. Ny `isEntryComplete()`-sjekk (krever kun det `stat_level` faktisk gjør
     obligatorisk -- slag alene, eller slag+putter; "full"-nivåets ekstra
     detaljer forblir valgfrie og blokkerer ALDRI fremdrift) + ny
     `advanceToNextPlayerOrHole()`. Bunnknappraden "Forrige/Neste hull"
     endret til "Forrige hull" (uendret) + en kontekstsensitiv primærknapp
     som enten viser "Neste: {navn på neste spiller}" (bytter aktiv
     spiller på SAMME hull) eller "Neste hull" (er aktiv spiller den
     siste, går videre til neste hull OG starter på spiller 1 igjen) --
     disabled med forklarende hjelpetekst til de påkrevde feltene er fylt.
  **Verifisert:** golf-term-tabellen og fullført-sjekken UAVHENGIG testet
  i Node (matcher videoens egne eksempler nøyaktig, f.eks. par 4 + slag
  6 = "Dobbel bogey"), samt selve samlebånds-syklusen simulert for 3
  spillere over flere hull-grenser OG for en solo-runde (ingen spillerbytte,
  kun hull-fremgang) -- 21/21 sjekker. Full regresjon (35-punkts
  co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ingen
  backend-endring denne runden). Ekte typesjekket produksjonsbuild
  kompilerte rent, samme engangs `next dev`-container-sjekk som tidligere
  ga 200.
  **Samme ærlige begrensning som resten av de håndkodede rundene:** ingen
  ekte nettleser-interaksjonstest av selve fremdrifts-følelsen (kun logikk
  + server-render bekreftet).
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Scoringsflyt v2: full skjermovertagende veiviser, BYGGET, IKKE ENNÅ
  RULLET UT (2026-07-26) -- ERSTATTER forrige rundes forsøk.** Brukeren
  testet forrige rundes "auto-fremdrift-knapp"-versjon live og var tydelig:
  "ingen forbedring i det hele tatt", "visuelt like overveldende og
  rotete" -- den inline-baserte tilnærmingen var IKKE nok, en ekte
  skjermovertagende veiviser (som opprinnelig vurdert og lagt til side pga.
  risiko uten visuell testing) var det som faktisk kreves. Bygget nå for
  ekte, pluss et eksplisitt nytt krav: akkumulert score-så-langt for RUNDEN
  synlig for HVER spiller samtidig (ikke bare aktiv), matchende
  konkurrentappens vedvarende "E"/"+1"-visning ved siden av hvert navn.
  **Datalasting endret** (nødvendig for punktet over): laster nå hull for
  ALLE deltakere med det samme rundén lastes (ikke lenger lat lasting kun
  for aktiv spiller), og sanntid-signalet (WS) henter nå alles hull på
  nytt, ikke bare énes.
  **Ny `ScoringWizard`-komponent** (fullskjerm, `fixed inset-0 z-50`, egen
  stack utenfor `<main>`): tre steg maks, drevet av `stat_level` --
  "strokes_only" (kun Slag), "strokes_and_putts" (+ Putter/avstand første
  putt), "full" (+ ett samlet detalj-steg: kølle/utslag/innspill/chip/
  bunker/straffeslag/anywayslag). Bevisst FÆRRE, grovere steg enn
  konkurrentens egne 5-6 skjermer (risikoreduksjon uten visuell testing,
  og TeeCups stat_level-modell gjør en så fin oppdeling mindre naturlig).
  Alle spillerne vises som en fast, ikke-trykkbar kontekst-rad øverst i
  veiviseren (aktiv fremhevet) -- speiler konkurrentappens "mist aldri
  oversikten"-prinsipp. "Forrige"/"Neste" beveger seg gjennom stegene;
  siste steg for siste spiller blir "Ferdig" (lukker + går til neste hull,
  starter på spiller 1 igjen), ellers "Neste: {navn}" (bytter spiller i
  SAMME veiviser, nullstiller til steg 1).
  **Hovedsiden forenklet radikalt:** den gamle Slag/Putter/"flere
  detaljer"-inline-blokken er FJERNET -- erstattet med én kompakt liste,
  ett kort per spiller: navn, "HCP X · {til-par så langt} ({N} hull)", og
  en stor rund knapp som viser gjeldende hulls slagtall (eller "–") og
  åpner veiviseren ved trykk. `NumberPicker`s `showGolfTerms`-funksjon fra
  forrige runde gjenbrukes uendret inni veiviserens Slag-steg (det arbeidet
  var ikke bortkastet).
  **Verifisert:** stegmaskinen (steg-antall per stat_level, fremover/
  bakover-navigasjon, disabled-gating per steg, "avbryt på steg 1 lukker",
  "siste steg for siste spiller fullfører") og akkumulert-score-
  beregningen UAVHENGIG simulert i Node (16/16). Et ekte API-rundtur-
  script som sender NØYAKTIG samme felt-kombinasjon som veiviseren ville
  sendt (fullt detalj-steg for en "full"-spiller, kun slag for en
  "strokes_only"-gjest) bekreftet begge lagres korrekt. Full regresjon
  (35-punkts co-player-testsuite) fortsatt grønn, `test_isolation.sql`
  12/12 (ingen migrasjon). Ekte typesjekket produksjonsbuild kompilerte
  rent, samme engangs `next dev`-container-sjekk ga 200.
  **Samme ærlige begrensning som alle håndkodede runder denne uken:** ingen
  ekte nettleser-interaksjonstest -- gitt at FORRIGE runde ble avvist
  nettopp fordi den så gal ut i praksis til tross for at logikken var
  korrekt, er dette IKKE en ubetydelig forbehold denne gangen. Bruker bør
  teste grundig før tillit.
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Scoringsflyt v3: administrasjon og scoring adskilt i to faner, BYGGET,
  IKKE ENNÅ RULLET UT (2026-07-26) -- direkte svar på at bruker fortsatt
  fant siden "bråkete og lite intuitiv" etter v2.** Brukeren viste et NYTT
  sidestilt skjermbilde-par (samme mønster som første gang) og påpekte at
  TeeCup fortsatt hadde mye synlig FØR selve scoringslisten (leaderboard-
  forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten med Rediger-
  ikoner, "+Medspiller") -- nøyaktig det jeg selv identifiserte som GameBooks
  kjerneprinsipp i den aller første analysen ("administrasjon og scoring er
  ADSKILTE tabs"), men aldri fullt ut gjennomførte i v2 (kun `ScoreSoFar`
  ble flyttet forrige runde, resten av det administrative innholdet ble
  stående igjen øverst).
  **Fikset denne gangen for ekte:** ny `pageTab`-state (`"score" | "manage"`,
  default `"score"`), en enkel fanevelger rett under feilmeldingen. "Score"
  (default) inneholder nå KUN: fullført-banner (hvis relevant), hull-
  navigasjon, og selve hull-panelet (header + scoringslisten fra v2 +
  Forrige/Neste hull) -- ingenting annet. "Spillere og runde" samler
  leaderboard-forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten
  (`PlayerList`), OG `ScoreSoFar` (flyttet HIT fra forrige rundes
  "under scoringslisten"-plassering, siden den er detaljert stats-innsyn,
  ikke selve registreringsoppgaven).
  **Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, samme
  engangs `next dev`-container-sjekk ga 200, full regresjon (35-punkts
  co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ren
  frontend-omrokkering, ingen migrasjon).
  **Samme ærlige begrensning som v1/v2:** ingen ekte nettleser-
  interaksjonstest av selve fane-følelsen.
  **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_frontend`
  (gjenskapte også `teecup_api` som vanlig bivirkning). Begge containere
  boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
- **`DESIGN_SYSTEM.md` opprettet (2026-07-27):** brukeren ba om en ny fil
  som viser designsystemet arbeidet tar utgangspunkt i. Skrevet fra bunnen,
  DESKRIPTIVT (hva som ER i koden, ikke et mål) — grunnet direkte i
  `globals.css`/`components/ui/*.tsx` og etablerte tvers-fil-mønstre:
  fargetoken (inkl. OKLCH-opprinnelsen til `primary`/`brand-orange` fra
  ADR-009/016), typografi, avstand/hjørner/lag (inkl. "én sammenhengende
  liste med `divide-y`, ikke separate kort"-regelen fra 2026-07-26s
  leaderboard-fiks), lokale komponentmønstre (NumberPicker/Stepper/
  ChoiceRow/DirectionCross — bevisst per-fil, ikke delte importer),
  golfscore-språket (`ScoreMark`/`ToParMark`/`PointsMark`/`HoleMark`),
  ikonografi, tilbakemelding/tilstander, og navneformat (pekt til CLAUDE.md).
- **`teecup-scorekort-og-entry-spec.md` lest og evaluert, delvis BYGGET SOM
  COMPLIANCE-PASS SAMME DAG (2026-07-27):** brukeren lastet opp et
  PRESKRIPTIVT motstykke til DESIGN_SYSTEM.md (5-app-sammenligning: Golf
  GameBook/Golf Pad/Golfshot/Hole 19/18Birdies) og spurte om det ga mening
  og kunne forbedre TeeCup. Vurdert som reelt verdifullt — sylskarpeste nye
  innsikt: "grønt-på-grønt"-diagnosen (§0.2) forklarer noe av "fortsatt
  rotete"-følelsen fra v1-v3-rundene bedre enn tetthet alene: `primary`
  (grønn) var brukt om hverandre for BÅDE "aktiv tilstand" og "generisk
  fylt/positiv", uten at begge betydde det samme sted. Foreslo (og
  brukeren bekreftet) å fikse spec-dokumentets §3 "Compliance-pass" FØR den
  større strukturelle §1-omleggingen (scorekort som grid) vurderes som egen,
  senere runde.
  **To av sjekklistens fem punkter var KONKRETE, VERIFISERBARE bugs, bekreftet
  direkte i koden (ikke antatt fra spec-teksten alene) FØR de ble fikset:**
  1. **"Deg Deg"-duplikat:** `playerLabel()` (`round-detail.tsx`) erstattet
     tidligere selve navnet med "Deg" for viewer-relativ egen rad, OG en
     separat `<Badge>Deg</Badge>` sto ved siden av samme sted (spillerkort,
     scoringslisten) — bekreftet duplikat, matchet et tidligere skjermbilde
     brukeren delte. **Fikset:** `playerLabel()` returnerer nå alltid det
     faktiske `display_name` (aldri "Deg") — Badge-en er nå ENESTE
     selv-indikator. Dette retter samtidig et videre, ikke tidligere flagget
     avvik: spec-dokumentet krever eksplisitt "roster-kontekst → fullt navn"
     for BÅDE scorekort-gridet og score-entry-headeren — veiviserens header/
     kontekst-rad/"Neste: {navn}"/fullført-banneret viste tidligere "Deg" i
     stedet for et fullt navn der også, uten noen badge til å disambiguere
     (reelt forvirrende på en delt telefon som sendes rundt en flight).
     Det nå overflødige `rawName`-feltet (var identisk med `name` etter
     fiksen) fjernet, tre kallsteder oppdatert.
  2. **Score-knappen fulgte ikke `§Golfscore-språket`:** den store runde
     knappen i den kompakte scoringslisten (`round-detail.tsx`, bygget i
     v2-runden) var `rounded-full`+grønn UANSETT om resultatet var under,
     over eller på par — bogey og eagle så identiske ut. **Fikset:** ny
     `scoreMarkClasses(diff)`-hjelpefunksjon som gjenbruker EKSAKT samme
     `primary`/`brand-orange`-fargespråk og fylt-vs-border+10%-tint-omfangs-
     regel som `ScoreMark` i `round-scorecard.tsx` (bekreftet ved å lese
     `ScoreMark` sin kildekode direkte, ikke gjettet) — sirkel under par,
     nøytral sirkel på par, `rounded-2xl` (bevisst mildere enn scorekortets
     `rounded-[4px]`, for å matche denne skjermens øvrige 56px-trykkflate-
     avrunding) over par, fylt ved 2+ slag fra par.
  **Resten av sjekklisten (44px-trykkgulv, `tabular-nums`) auditert
  systematisk mot AKKURAT denne filen** (samme fil brukerens skjermbilder
  viste) — 2 manglende `tabular-nums` (HCP/tildelte slag i spillerkortet)
  og 11 knapper/lenker under 44px (fane-bryteren `min-h-10`→`min-h-11`,
  `EditRoundPanel`s lukk-ikon `size-8`→`size-11`, feilside-tilbakelenken,
  og åtte knapper i bane-bytte-/legg-til-medspiller-skjemaene) rettet.
  **Bevisst UTENFOR omfang denne runden:** spec-dokumentets §1 (scorekort
  som fullt grid, celle åpner veiviseren) — en STØRRE strukturell endring
  som fortjener et eget, bevisst ja fra brukeren, ikke bygget stille inn i
  en "fiks kjente bugs"-runde.
  **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som
  deployes) kompilerte rent, alle 24 ruter listet. **Samme ærlige
  begrensning som ALLE håndkodede runder denne uken:** ingen ekte
  nettleser-interaksjonstest av det faktiske visuelle resultatet (kun kode-
  lesing + build-verifisering + bevisst gjenbruk av en allerede lest,
  eksisterende komponents fargespråk for å holde risikoen lav).
  **Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
  containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket.
- **Spec-dokumentets §1 (scorekort som fullt grid) BYGGET OG LIVE
  (2026-07-27), samme dag rett etter compliance-passet:** brukeren
  bekreftet eksplisitt "JA" på at grid-redesignet er en egen, bevisst
  runde, egen informasjonsarkitektur enn ett-hull-om-gangen-listen
  bygget dagen før. `round-detail.tsx` sin "Score"-fane fikk hull-strip
  + ett-hull-panel ERSTATTET av en ny `ScorecardGrid`: spillere som rader
  (sticky venstre navnekolonne, navn+HCP, samme tap-target åpner
  veiviseren for gjeldende hull), hull som horisontalt scrollbare
  kolonner, `Hcp`/`Par`-referanserader over spillerradene (samme
  konvensjon som den allerede shippede `round-scorecard.tsx`), sticky
  `Ut`/`Inn`/`Sum` til høyre (Ut/Inn gruppert på FYSISK hullnummer 1-9/
  10-18, uavhengig av øktens starthull — riktig konvensjon uansett
  spillerekkefølge; kun vist for 18-hulls runder, 9-hulls runder får
  én samlet Sum). Ny `ScorecardCell` gjenbruker EKSAKT samme klassifisering
  og Tailwind-klasser som `ScoreMark`/`classify` i `round-scorecard.tsx`
  (lest direkte, ikke gjettet) — ulikt compliance-passets softere
  `rounded-2xl`-knapp-variant (fortsatt riktig der, egen visuell kontekst),
  siden dette er en LITEN tabellcelle som spec eksplisitt ber om å følge
  språket "UBRYTELIG".
  **Interaksjon:** tapp en score-celle ELLER spillerens navnecelle ELLER
  en hull-kolonneoverskrift åpner `ScoringWizard` (uendret komponent fra
  v2-runden) for akkurat den (spiller, hull)-kombinasjonen — kolonne-
  overskrift alene (uten å tappe en celle) setter kun "gjeldende hull"
  uten å åpne veiviseren, samme jobb som den fjernede `HoleNav` gjorde.
  Beholdt en kompakt "Forrige/Neste hull"-knapperad under gridet for
  rask sekvensiell registrering uten bred scrolling.
  **Dødt kode fjernet i samme runde:** `HoleNav`, `holeIsPlayed`,
  GIR-merket/`showGir` (ga ikke lenger mening i en multi-hull-visning),
  og OGSÅ compliance-passets `scoreMarkClasses`-hjelpefunksjon (var kun
  brukt av den nå fjernede ett-hull-listens store runde knapp).
  **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
  som deployes) kompilerte rent, alle 24 ruter listet. I TILLEGG en
  engangs full runner-image bygget og kjørt i en isolert container (ikke
  bare `--target builder`) — `/my-rounds/[id]` for en ukjent runde-id
  ga 200, ingen React-feilgrense/krasj-markup i responsen. **Samme
  ærlige begrensning som ALT håndkodet arbeid denne uken, men STØRRE
  konsekvens denne gangen siden dette er en vesentlig strukturell endring
  (ny informasjonsarkitektur), ikke en liten fiks:** ingen ekte
  nettleser-interaksjonstest (scrolling, tapping av celler/hull-
  overskrifter/navnerad, faktisk visuelt resultat av sticky-kolonnene)
  er utført — flagget eksplisitt til bruker FØR utrulling, bruker bør
  selv klikke seg grundig gjennom før full tillit.
  **Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
  containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200,
  `teeoff.no` upåvirket.
- **Reell produksjonsbug funnet OG fikset SAMME DAG via ekte nettleser-
  testing (2026-07-27) — FØRSTE gang denne økten en Chrome DevTools MCP
  har vært tilgjengelig.** Brukeren ba meg selv åpne gridet
  (`localhost:3000/my-rounds/{id}`), logget meg inn (passord feilet først
  — kontoen mangler passord/miljøet pekte annerledes; løst med en ekte
  magic-link brukeren limte inn), og jeg tok et ekte skjermbilde av det
  nettopp bygde scorekort-gridet på en mobil viewport (390×844) mot ekte
  produksjonsdata (runden `c5db2e31-...`, 2 spillere, 4/18 hull spilt).
  **Fant umiddelbart en alvorlig, reell rendering-bug** som ALDRI ble
  fanget av typesjekking/container-boot-verifisering: `position: sticky`
  på `<td>`/`<th>` inni en `<table>` med `border-collapse` rendret
  fullstendig ødelagt i Chrome — de sticky `Ut`/`Inn`/`Sum`-kolonnene
  overlappet/utvisket hull-kolonnene bak seg (synlige sammenblandede
  siffer, f.eks. "436"/"472" der Par-radens tall lå oppå hverandre).
  Nøyaktig den typen feil den gjentatte "ingen ekte nettleser-test
  utført"-forbeholdet advarte mot hele uken.
  **Første fiks-forsøk (kun `border-collapse` → `border-separate
  border-spacing-0`) løste IKKE problemet** — re-skjermbilde etter
  redeploy viste samme overlapp. **Rot-årsaken var strukturell, ikke
  syntaktisk:** å kombinere en sticky VENSTRE-kolonne MED sticky HØYRE-
  kolonner i en tabell som er mye bredere enn viewporten er i seg selv
  et ustabilt mønster — ved scroll-posisjon 0 blir de sticky høyre-
  kolonnene umiddelbart trukket til synlig høyre kant, og alt som
  "egentlig" befinner seg der i normal dokument-flyt (hull 3+) blir
  liggende RETT BAK dem, delvis synlig gjennom `bg-muted/60`s
  delvise gjennomsiktighet. **Fikset ved å fjerne sticky-posisjonering
  fra `Ut`/`Inn`/`Sum`-kolonnene helt** (de scroller nå med resten av
  hullene i normal flyt, samme velprøvde, veletablerte mønster som den
  gjenværende sticky VENSTRE navnekolonnen, som fungerte korrekt hele
  tiden) — droppet også `/60`-gjennomsiktigheten til fordel for en helt
  opak `bg-muted`.
  **Verifisert presist, ekte, etter fiksen** (ikke bare "ser bedre ut"):
  nytt skjermbilde ved scroll-posisjon 0 viste alle tall rene og lesbare
  (Hcp/Par-radene, begge spilleres scoringsceller, korrekt sirkel/firkant-
  form for bogey/dobbel bogey/par), OG et script som scrollet gridet helt
  til høyre (`scrollLeft = scrollWidth`) bekreftet `Ut`/`Inn`/`Sum` også
  rene der (`Ut 36/Inn 36/Sum 72` på Par-raden — stemmer eksakt med en
  18-hulls par-72-bane), med navnekolonnen fortsatt korrekt pinnet til
  venstre gjennom hele scrollingen. Tilgjengelighetstreet (`take_snapshot`)
  bekreftet også at aria-labels med golftermer ("Bogey", "Dobbel bogey",
  "Par") faktisk leses ut korrekt — `§Golfscore-språket`s "aldri farge
  alene"-regel holder i praksis, ikke bare i teorien.
  **Rullet ut live 2026-07-27**, samme dag: `docker compose up -d --build
  teecup_frontend` kjørt to ganger (én for det mislykkede første forsøket,
  én for den faktiske fiksen), `/health` 200 begge ganger, `teeoff.no`
  upåvirket.
  **Lærdom:** Chrome DevTools MCP-tilgangen endrer risikobildet for alt
  fremtidig håndkodet frontend-arbeid denne økten — bruk den til å
  FAKTISK se resultatet før noe rapporteres som ferdig, i stedet for kun
  typesjekk+container-boot+`curl`-baserte proxyer for "det virker".
- **Full nettleser-gjennomgang av ALLE 22 skjermer, 2026-07-27 —
  systematisk browsersjekk av alt som IKKE var reelt nettleser-testet
  tidligere.** Brukeren spurte først hvilke visninger som faktisk finnes
  (svart med en gruppert oversikt over `frontend/app/`s 22 `page.tsx`-
  ruter), deretter ba om at ALLE de ikke-browsersjekkede skjermene faktisk
  ble sjekket. Logget inn som `hei@erol.no` (spiller-konto, egne runder)
  og — etter en egen runde med å spore opp riktig konto (magic-link fra
  brukeren landet først på feil konto to ganger: `erol.haagenrud@gmail.com`
  og kontoen manglet 2FA — satt opp TOTP for ekte ved å hente ut den rå
  base32-secreten fra oppsett-skjermen og regne ut en gyldig 6-sifret kode
  selv med et frittstående RFC 6238-script, ingen autentisator-app
  involvert) — som `erol.haagenrud@envide.no` (org-eier for «Tjøme
  Gents») for de org-/turnering-scopede skjermene. Gikk gjennom alle 22
  ruter med ekte skjermbilder + konsoll-feil-sjekk (`list_console_
  messages`) på hver.
  **To reelle funn:**
  1. Scorekort-gridet (§1, bygget dagen før) hadde EN NY sticky-kolonne-
     overlapp-bug som IKKE fantes i utgangspunktet -- se eget punkt over,
     fikset samme økt.
  2. **Ny, ekte bug funnet i `round-scorecard.tsx`** (post-runde-
     scorekortet, bygget i en tidligere økt) -- "til par" i BÅDE
     header-hero-tallet og bunn-"TIL PAR"-brikken regnet
     `totalGross - totalPar` der `totalPar` var summen av ALLE 18 hulls
     par, ikke bare de faktisk spilte -- ga en absurd "−53 til par" for
     en runde med 19 slag på kun 4 hull (skulle vært "+2"). Bekreftet
     ved at `/my-rounds/[id]/stats` (en ANNEN komponent) viste riktig
     "+2,00 til par" for SAMME runde, som isolerte feilen presist til
     `round-scorecard.tsx`. Fikset med en ny `playedPar`-variabel
     (paret for KUN spilte hull) brukt i de to til-par-utregningene --
     "Par"-brikken nederst beholdt bevisst `totalPar` (hele rundens
     par, riktig som statisk referanse). Bunn-"Par"/øvre "Hcp"/"Par"-
     referanseradene i `ScoreBlock` ble sjekket og bekreftet IKKE
     rammet (brukes kun til den statiske referansen, aldri til en
     til-par-utregning).
  **For å nå de org-/turnering-scopede skjermene: satt org «Tjøme
  Gents» sin `public_profile`/`slug` MIDLERTIDIG (bekreftet med bruker
  FØR endring) for å teste `/clubs/[slug]`, deretter revertert
  eksplisitt til nøyaktig opprinnelig tilstand (`slug=null`,
  `public_profile=false`) — bekreftet med en direkte databasespørring
  etterpå at reverten var eksakt.
  **Alle 22 skjermer bekreftet uten krasj/konsoll-feil** (utenom
  forventede 401/403 på steder som SKAL avvise — feil passord-forsøk,
  privat lagchat, ugyldig verify-token). Full liste med status i
  chat-loggen denne runden.
  **Rullet ut live 2026-07-27**, ingen migrasjon for til-par-fiksen,
  `docker compose up -d --build teecup_frontend`, verifisert direkte i
  nettleseren mot den samme runden som viste bugen (nå "+2 til par",
  korrekt).
- **Dashboard-runde: bane-navn-fiks, to nye designfarger, bane-detaljvisning
  og aggregert statistikk — BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE
  (2026-07-28):** fem punkter reist av brukeren i samme runde, alle
  bekreftet eksplisitt før bygging (AskUserQuestion) unntatt fargevalget,
  der brukeren ba om at jeg selv "tok på designerbrillene" og foreslo.
  1. **"Kommende runder" → "Runder"** på dashbordet (`dashboard.tsx`,
     `UpcomingRounds`) — ren tekstendring, ingen annen forekomst i kodebasen.
  2. **Tjøme-bane-duplikatet rettet i ekte `teecup_db`:** presist
     diagnostisert FØR noe ble kjørt — alle tre av `hei@erol.no` sine
     runder pekte til nøyaktig samme `teeoff_facility_slug`/
     `teeoff_course_id` (`tjome-golfklubb`/140), kun `round.
     course_name_snapshot`-TEKSTEN differerte (én runde fra FØR
     enkeltbane-navnefiksen 25.07 hadde "Tjøme Golfklubb – Hovedbanen").
     Ingen delt banetabell å slå sammen — frittstående runder har (bevisst,
     ADR-033 Beslutning C) ingen egen `course`-rad, kun et navn-snapshot per
     runde. Fiksen var én presist scopet `UPDATE round SET
     course_name_snapshot = 'Tjøme Golfklubb' WHERE id = 'fa6e528f-...'`
     (1 rad), kjørt etter eksplisitt bekreftelse, verifisert med en
     read-only spørring rett etterpå.
  3. **To nye kjernefarger** — brukeren ba eksplisitt om minst én, "kanskje
     to", etter først å ha fått presentert (og avvist "kun én") en
     tidligere anbefaling fra 25.07 om å løfte den allerede eksisterende
     `--chart-3`-blåtonen. Løftet BEGGE allerede validerte statistikk-
     fargene i stedet for å finne opp nye OKLCH-verdier: `--info`
     (fra `--chart-3`, blå) og `--gold` (fra `--chart-4`, gul/gull) —
     nye tokens i `globals.css` (`:root`/`.dark`/media-dark-blokken, alle
     tre synkronisert som vanlig). Konkret, begrunnet førstebruk for
     begge, ikke bare dekorativt: `--info` på et nytt "Hcp spilt til
     X"-merke (før: ren grå tekst) på rundekort (`round-card.tsx`),
     `--gold` på et nytt "Personlig rekord"-merke (`Medal`-ikon) som vises
     når en fullført runde er brukerens laveste til-par blant MINST to
     fullførte runder (unngår at den eneste fullførte runden feilaktig
     kalles en "rekord"). Beregnet i `dashboard.tsx`/`own-rounds.tsx`/
     `course-rounds.tsx` sine respektive `findPersonalBestRoundId()`.
  4. **Bane-detaljvisning:** "Spilte baner" på dashbordet er nå klikkbar
     (`PlayedCourses`, `dashboard.tsx`) — ny rute
     `/my-rounds/course/[name]` (`components/course-rounds.tsx`), gjenbruker
     eksisterende `GET /rounds` (ingen nytt backend-endepunkt), filtrerer
     client-side på nøyaktig samme `course_name_snapshot`-nøkkel dashbordets
     egen gruppering allerede bruker. "Personlig rekord" regnes likevel over
     HELE rundelisten, ikke bare denne banens, for at merket skal bety det
     samme uansett hvor et rundekort vises.
     **Reell bug funnet OG fikset UNDER browserverifisering, ikke antatt
     riktig fra kildekoden alene:** første versjon leste `params.name` rått
     uten `decodeURIComponent` (matchet et eksisterende mønster i
     `/clubs/[slug]/page.tsx` som aldri hadde blitt testet med et navn som
     inneholder mellomrom/æøå) — et ekte skjermbilde viste tittelen som
     `Tj%C3%B8me%20G...` og "0 runder funnet", siden matchen skjedde mot den
     RÅ URL-kodede strengen. Rettet med et eksplisitt `decodeURIComponent`,
     bekreftet med et nytt skjermbilde: riktig tittel og alle tre Tjøme-
     rundene listet, inkl. både `--info`- og `--gold`-merkene rendret
     korrekt.
  5. **Aggregert statistikk, klikkbar fra dashbordet:** ny
     `GET /rounds/stats/summary` (`app/routers/rounds.py`), ny side
     `/my-rounds/stats` (`components/rounds-stats-summary.tsx`), lenket
     fra dashbordets "Statistikk"-seksjon ("Se full statistikk"). Bruker
     valgte det BREDESTE av tre foreslåtte omfang (utover kun runder/snitt-
     til-par/putt: fairwaytreff, GIR, én-putt, scrambling, sand save,
     snitt chip/bunker/straffeslag/anywayslag per runde) — portert fra de
     allerede R&A/manuelt verifiserte formlene i `round-stats.tsx` sin
     `computeStats()` for ÉN runde, generalisert til å pole alle kvalifiserte
     hull på tvers av ALLE fullførte runder (ikke gjennomsnitt av per-runde-
     prosenter, som ville vektet små utvalg feil).
     **Putt/18-hull-regelen** (brukerens eksplisitte instruks, presist
     bekreftet tolkning FØR bygging via AskUserQuestion): `round_hole` har
     alltid nøyaktig 18 rader per deltaker uansett `holes_planned` (9 eller
     18) — verifisert i `_create_participant`. For hver fullført runde der
     puttsporing faktisk var på (`stat_level != 'strokes_only'`) telles
     derfor alle 18 lagrede rader, et hull uten registrert putt-verdi
     (uspilt, eller utenfor et 9-hulls spilleomfang) telles som 2 putter —
     runder UTEN puttsporing holdes helt utenfor tallet (ellers ville alle
     18 hull feilaktig blitt padded). Kun putt-tallet padder — alle andre
     andelstall bruker KUN faktisk registrerte hull, som instruert.
     **Verifisert i to lag:** (a) en frittstående Python-simulering av
     nøyaktig samme aggregeringslogikk mot et hånd-konstruert 3-runde-
     datasett (én `strokes_only`-runde padding-ekskludert, én
     `strokes_and_putts`-runde med 9 av 18 hull padded) — alle hånd-regnede
     forventninger stemte eksakt, inkl. det kritiske tilfellet (padded runde
     ga nøyaktig 36 putt/18, strokes_only-runden talte 0 mot totalen); (b)
     ekte typesjekket produksjonsbuild + import-sjekk av hele FastAPI-appen
     i det faktiske prod-imaget (ikke bare syntaks) + et ekte browserbesøk
     som viste reelle, korrekt utregnede tall for `hei@erol.no` sine 2
     fullførte runder.
  **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt for
  databaseskrivingen (punkt 2) og fargevalget (punkt 3, "to farger i stedet"
  for anbefalt én); resten bygget direkte på brukerens egen presise
  instruks. Ingen migrasjon. `docker compose up -d --build teecup_api
  teecup_frontend` (kjørt to ganger — én gang for hovedleveransen, én gang
  for `decodeURIComponent`-fiksen over). `/health`/`/dashboard`/
  `/my-rounds/stats`/`/my-rounds/course/...` alle bekreftet 200 og
  konsoll-feilfrie i en ekte innlogget nettleser-sesjon, `teeoff.no`
  upåvirket.
- **Statistikk-siden utvidet: miss-retning, tidsvindu + forrige-periode-
  sammenligning — BYGGET, TESTET OG LIVE (2026-07-28), samme dag, rett
  etter forrige punkt:** brukeren reiste tre ting samtidig — ønsket
  miss-RETNING (ikke bare treff%), trender, og et presist spørsmål om
  hvorvidt GIR-fra-score-og-putt-inferens faktisk var forstått/utnyttet.
  **Siste punkt avklart, ikke en bug:** bekreftet at `isGir = score - putts
  <= par - 2` (allerede i `round-stats.tsx`, portert uendret til det nye
  aggregerte endepunktet dagen før) ER akkurat denne generelle inferensen —
  fungerer for ALLE score/putt/par-kombinasjoner, ikke bare brukerens
  eksempel. Eneste reelle begrensning (forklart, ikke fikset): krever
  putt-tall, så `strokes_only`-runder får aldri en GIR-verdi — score alene
  er nesten aldri nok til å BEVISE GIR (en scrambling-birdie fra utenfor
  green gir identisk score som en ekte GIR+1-putt).
  **Miss-retning bygget:** `_summarize_rounds` i `app/routers/rounds.py`
  utvidet med `fairway_left_pct`/`fairway_right_pct` (samme
  `fairway_tracked`-pool som treff%) og `green_miss_long/short/left/
  right_pct` (egen pool — hull der `approach_result` er registrert og ≠
  "hit", UAVHENGIG av om putt er kjent, samme adskilte spor som
  `round-stats.tsx` sin `missedGreen`/`missDir`). Ny `MissBar`-komponent i
  `rounds-stats-summary.tsx` (fordelingsbar, samme visuelle idé som
  `SegmentedBar`/`DistributionBar` andre steder i appen).
  **Tidsvindu + forrige-periode bygget:** ny `StatsWindow`-type (`Literal`)
  og `_resolve_stats_window()` — brukeren ba eksplisitt om BÅDE en full
  velger (Siste runde/5/10/Denne måneden/I år/Siste år/Alltid) OG
  sammenligningspiler mot forrige periode (utover det opprinnelig
  anbefalte "kun siste 5 vs. alltid, ingen piler"). Én ENSARTET regel for
  "forrige periode" på tvers av alle vindutyper — samme LENGDE (antall
  runder for rullerende antall-vinduer, antall DAGER for dato-vinduer)
  rett før gjeldende vindus start — i stedet for å måtte spesialdefinere
  "forrige måned"/"forrige år" ulikt for hver kalenderbasert type.
  `GET /rounds/stats/summary?window=...` returnerer nå `{window, current,
  previous}` (`RoundStatsWindowSummary`) — samme `_summarize_rounds()`-
  funksjon kalt to ganger på to ulike round_id-mengder, ingen duplisert
  aggregeringslogikk. Frontend fikk en ny `Delta`-komponent (pil + farge,
  farget etter en eksplisitt `goodDirection`-per-tall — lavere er bedre
  for til-par/putt/chip/bunker/straffeslag/anywayslag, høyere er bedre for
  treff-/rednings-prosentene, INGEN farge på de rene miss-retnings-tallene
  siden venstre/høyre ikke er "bedre/verre").
  **Verifisert i to lag:** (a) en frittstående Python-simulering av
  `_resolve_stats_window()` mot syntetiske datasett — rullerende antalls-
  vindu (riktig current/previous-splitt, riktig tomt `previous` når for få
  runder finnes), dato-vindu (kalender-til-dato + rullerende forrige
  periode), og en isolert miss-retning-poolingstest (`None`-verdier
  korrekt ekskludert fra nevneren); (b) ekte typesjekket produksjonsbuild
  (måtte rette en TypeScript-nullbarhets-feil underveis — `current`/
  `previous` pakket ut via en IIFE inni JSX for at TS skulle smalne begge
  riktig), full import-sjekk av hele FastAPI-appen i prod-imaget, OG et
  ekte browserbesøk som viste reelle tall (fairway-miss 36/43/21,
  green-miss-retning 0/75/13/13) + bekreftet vindu-velgeren fungerer
  (klikket "Siste 10", pillen ble grønn, tallene oppdaterte seg) + et
  direkte `fetch()`-kall fra siden selv som bekreftet `{window, current,
  previous}`-formen (2 runder i `current`, korrekt tomt `previous` siden
  brukeren kun har 2 fullførte runder totalt).
  **Rullet ut live 2026-07-28**, ingen migrasjon, `docker compose up -d
  --build teecup_api teecup_frontend`. Begge containere boot-et rent,
  `/health` → 200, anonymt `GET /rounds/stats/summary?window=last_5` → 401
  (bekrefter query-parameteren ruter riktig gjennom Caddy/rewrites),
  `teeoff.no` upåvirket.
- **Statistikk-siden: visuell v2 (kompass-diagram + donuter), HÅNDKODET og
  BROWSERVERIFISERT MED EKTE ITERASJON, LIVE (2026-07-28), samme dag:**
  brukeren delte et referansebilde av en konkurrent-app sin statistikk-
  skjerm (donuter med sentertall, kompass for miss-retning) og ba om noe
  tilsvarende. Skrev først et fullt V0-prompt (data-kontrakt, komponent-
  for-komponent) — brukeren var tom for V0-credits og spurte eksplisitt om
  jeg kunne "gi promptet til meg selv" og bygge det direkte, siden Chrome
  DevTools MCP nå gjør det mulig å faktisk SE resultatet underveis (ulikt
  tidligere håndkodede runder denne uken som kun var typesjekket).
  **Backend:** tre nye felt i `RoundStatsSummary`/`_summarize_rounds`
  (`app/routers/rounds.py`) — `putt_dist_one_pct`/`two_pct`/
  `three_plus_pct`, EGNE eksakte bøtter (`putts==1`/`==2`/`>=3`, samme som
  `round-stats.tsx` sin `puttCategories`), bevisst forskjellig fra det
  allerede eksisterende `one_putt_pct` (som bruker `<=1` — en annen,
  allerede etablert rate, ikke en distribusjon).
  **Frontend, `rounds-stats-summary.tsx` skrevet om betydelig:** ny
  `GirCompass` — greentreff-prosenten i midten (grønn fremheving), fire
  retningsceller rundt (Langt=topp, Kort=bunn, Venstre=venstre,
  Høyre=høyre), samme visuelle språk som den allerede etablerte
  `DirectionCross`/`DirButton` fra `round-detail.tsx` (plusstegn-rutenett,
  `min-h-16 rounded-2xl border`-celler) — bevisst IKKE fargekodet
  grønn/oransje på retningscellene (retning er beskrivende, ikke god/
  dårlig), kun sentercellen. Ny gjenbrukbar `Donut`-komponent
  (conic-gradient, sentertall + valgfri forklaringsliste via
  `showLegend`) brukt til BÅDE puttfordelingen (3 segmenter: 1-putt/
  2-putt/3-putt+) og to enkle rednings-gauger (scrambling/sand save, ett
  segment). Bevisst IKKE et flyt-/beslutningstre-diagram for scrambling/
  sand save (som referansebildet hadde) — det er to uavhengige prosenter i
  datamodellen, ikke en forgrening, et flytdiagram ville antydet en
  struktur som ikke finnes. Bevisst INGEN trendlinje (referansebildets
  fjerde element) — for lite rundehistorikk til å vise noe meningsfullt
  ennå, samme vurdering som tidligere samme dag.
  **Reelt funn UNDER selve visuell iterasjon, ikke bare kodegjennomgang:**
  første versjon av "Redning"-kortet viste samme tall TO GANGER per gauge
  (`Donut` sin egen auto-genererte forklaringsliste "● Scrambling 9 %" RETT
  OVER en egen, manuelt lagt til "Scrambling"-bildetekst) — sett direkte i
  et ekte skjermbilde, ikke antatt. Fikset ved å legge til en
  `showLegend`-prop på `Donut` og slå den av for de to gaugene, beholdt kun
  min egen kompakte bildetekst+delta under ringen.
  **Verifisert:** ekte typesjekket produksjonsbuild (to runder — én for
  hovedversjonen, én for legend-fiksen), full backend-import-sjekk i
  prod-imaget, OG ekte skjermbilder tatt FØR og ETTER legend-fiksen i en
  innlogget nettleser-sesjon (samme mønster som sticky-kolonne-bug-fiksen
  tidligere denne uken) — kompasset viser reelle tall (39 % greentreff,
  75 % kort, 13/13 % venstre/høyre, 0 % langt for `hei@erol.no` sine 2
  runder), puttfordelings-donuten viser korrekt fargede segmenter (17/61/
  22 %), vindu-velgeren fungerer fortsatt uendret (klikket "Siste 5" etter
  redesignet, ingen konsoll-feil). Ingen migrasjon.
  **Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api
  teecup_frontend` (kjørt to ganger). `/health` → 200 begge ganger,
  `teeoff.no` upåvirket.
  **Oppfølging, samme dag:** brukeren ba om at fairway-baren sin venstre/
  høyre-bom bruker SAMME farge (i stedet for to ulike chart-farger) --
  begge er tross alt bare "bom", ingen grunn til å skille dem visuelt.
  Byttet begge til `--brand-orange` (appens etablerte "bom/over par"-farge),
  beholdt grønn kun for selve fairwaytreffet. Verifisert med et nytt
  skjermbilde. Rullet ut, ingen migrasjon.
- **"Til par: med vs. uten"-splitt, BYGGET, TESTET OG LIVE (2026-07-28),
  samme dag:** brukeren ba eksplisitt om snitt-til-par splittet på om en
  hull-hendelse inntraff eller ikke -- greentreff, fairwaytreff, bunker,
  OG anywayslag (eksplisitt fremhevet). Fire nye par felt i
  `RoundStatsSummary`/`_summarize_rounds` (`app/routers/rounds.py`):
  `avg_to_par_with/without_gir`, `_fairway_hit/miss`, `_with/without_bunker`,
  `_with/without_anyway` -- pooler ENKELTHULL på tvers av alle runder i
  perioden (ikke per-runde-snitt, siden dette er hull-nivå-betingelser),
  samme `diff()`-formel som `round-stats.tsx` sin `avgToParWithGir`/
  `avgToParFairwayHit`/`avgToParWithBunker` bruker for én runde (portert
  uendret) -- anywayslag-splitten er en ny, konsekvent utvidelse av
  akkurat samme mønster (fantes ikke fra før for enkeltrunder heller).
  Ny `CompareToPar`-komponent i `rounds-stats-summary.tsx`: to bokser side
  om side, den med FAKTISK lavest til-par denne perioden fremheves grønn
  -- ingen hardkodet antakelse om hvilken side som "skal" vinne (bekreftet
  reelt i data: "Med anywayslag" var faktisk verre enn "Uten anywayslag"
  som forventet, men "I bunker" var marginalt BEDRE enn "Ikke i bunker"
  for denne brukerens 2 runder -- fremhevingen fulgte automatisk det
  virkelige tallet, ikke en antakelse).
  **Verifisert:** en frittstående Python-simulering av alle fire splittene
  mot et hånd-konstruert 5-hulls datasett (eksakte forventede gjennomsnitt
  regnet ut for hånd og sammenlignet), full backend-import-sjekk i
  prod-imaget, ekte typesjekket build, OG et ekte skjermbilde i innlogget
  nettleser som viste reelle, korrekt utregnede og korrekt fargede tall
  for alle fire kategoriene. Ingen migrasjon.
  **Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api
  teecup_frontend`, `/health` → 200, `teeoff.no` upåvirket.

- **Gjennomgang av frittstående runder (ADR-033) + det viktigste hullet
  lukket: faktisk (beregnet) HCP, BYGGET, SCRATCH-/BROWSERVERIFISERT OG
  LIVE (2026-07-28, ADR-038):** brukeren ba om en vurdering av om alt var
  tenkt gjennom for single-runder. Fant ved grep at hele WHS-indeksmotoren
  (`handicap_index_from_differentials`/`low_handicap_index`/
  `apply_index_caps` i `handicap_engine.py`, 41/41 testet siden ADR-033)
  ALDRI ble kalt fra noe API-endepunkt — `round_participant.
  score_differential` ble regnet og lagret per runde, men `app_user.
  handicap_index` endret seg kun manuelt. Bekreftet eksplisitt i
  `rounds.py` sin egen moduldoc ("skjer IKKE automatisk her -- eksplisitt
  uavklart punkt i ADR-033"). Sekundære, mindre hull notert samtidig
  (offline-kø kun for turnering-scorekortet, ikke frittstående runder;
  ingen Stableford; ingen rundedeling/visibility) — brukeren valgte å ta
  tak i HCP-hullet.
  **To load-bærende design-avklaringer** (AskUserQuestion, se ADR-038):
  (1) hver INNLOGGET deltaker (ikke bare eieren) styrer sin egen
  eksklusjon fra faktisk HCP; (2) faktisk HCP designes NÅ for å kunne
  inkludere begge kilder (frittstående runder OG en fremtidig turnering-
  kilde), men v1 bygger kun runder-delen — løst med ett bevisst tynt,
  navngitt skjøtepunkt (`_gather_qualifying_differentials`), IKKE en ny
  generell "scoring record"-tabell (for tidlig abstraksjon).
  **Ny migrasjon `030_actual_handicap_index.sql`:** `app_user.
  computed_handicap_index`/`computed_handicap_index_updated_at` (den
  faktiske, beregnede WHS-indeksen — ALDRI direkte redigerbar, kun
  avledet), `round_participant.exclude_from_handicap` (manuell opt-out,
  uavhengig av den automatiske `counts_for_handicap`-kvalifiseringen),
  `round.play_format` (`stroke`/`match`, selvdeklarert — ingen egen
  match-motor for frittstående runder), `handicap_history.source`
  (`manual`/`computed` — gjenbruker EKSISTERENDE tabell fra ADR-031-
  oppfølgingen i stedet for en parallell historikk-tabell, siden Low
  Handicap Index/cap (Rule 5.7/5.8) trenger nøyaktig samme
  dato+indeks-form som allerede fantes der).
  **WHS-kilde lest og lagt til grunn** (`WHS_Rules_of_Handicapping_2024.
  pdf`, Rule 3.3): matchspill-scorer ER teknisk et gyldig HCP-grunnlag
  under WHS, MEN et konsedert/ikke-utspilt hull krever en subjektiv "most
  likely score" TeeCups rene slagregistrering ikke har noen vei til å
  representere presist — begrunner "spør (med anbefalt eksklusjon), ikke
  tving"-designet i stedet for et hardkodet forbud mot matchspill i
  HCP-grunnlaget.
  **Backend (`app/routers/rounds.py`):** ny `_recompute_computed_
  handicap_index(conn, user_id)` — henter de ≤20 nyeste kvalifiserende
  differensialene, kaller den allerede-testede motoren, henter tidligere
  `computed`-historikk for Low HI-cap (hopper bevisst over capping ved
  FØRSTE beregning noensinne — Low HI er udefinert før en indeks er
  etablert). Kalt fra `complete_round` (alle deltakere med `user_id`),
  `update_participant` (eksklusjon endret på en ALLEREDE fullført runde),
  `remove_guest_participant` (dekker faktisk enhver ikke-eier-fjerning,
  til tross for navnet) og `delete_round` (fanger berørte brukere FØR
  kaskade-slettingen fjerner radene). `update_participant` sin
  autorisasjon utvidet presist: en ikke-eier kan KUN sende
  `exclude_from_handicap`, KUN på sin egen rad (`_OWNER_ONLY_
  PARTICIPANT_FIELDS`-sjekk + eksplisitt eier-eller-selv-gate) — alle
  andre felt forblir strengt eier-only, uendret.
  **Backend (`app/routers/auth.py`):** `Me` fikk `computed_handicap_
  index`/`computed_handicap_index_updated_at`. Ny `POST /auth/profile/
  handicap/apply-computed` — kopierer gjeldende beregnet verdi inn i det
  manuelt satte HCP-et (samme skrivevei/historikk-logging som en vanlig
  manuell PATCH, kun `source='manual'`). `GET /auth/profile/handicap-
  history` eksponerer nå `source` også.
  **Frontend:** `/my-rounds/new` fikk en "Spilleform"-bryter
  (Slagspill/Matchspill) — velges Matchspill, forhåndsutfylles (ikke
  tvinges) en eksklusjons-avkrysning med forklarende tekst.
  `round-detail.tsx` fikk en "Matchspill"-badge i headeren, en
  spilleform-bryter i `EditRoundPanel` (ren metadata, redigerbar uansett
  fullført-status), og `EditParticipantPanel` fikk en ny `restricted`-
  modus: en ikke-eier som redigerer SIN EGEN rad, ELLER EIEREN etter at
  runden er fullført, ser KUN eksklusjons-toggelen (ikke tee/HCP/navn/
  statistikk) — `PlayerList` sin "Rediger"-knapp vises nå også for en
  ikke-eiers egen rad, ikke bare for eieren. `/account` fikk et nytt
  "Faktisk HCP (beregnet)"-kort (verdi + sist-beregnet-dato +
  "Bruk som mitt HCP →"-knapp), og HCP-historikk-listen viser nå
  "beregnet"/"manuelt" per rad.
  **Scratch-verifisert grundig, 111/111 sjekker** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container via ekte HTTP, samme mønster som hele prosjektet):
  hånd-utregnet WHS-matte bekreftet PRESIST (tre runder med kjente
  differensialer [10.0, 12.0, 14.0] ga nøyaktig 8.0 — beste-1-av-3 med
  -2.0-justering fra Rule 5.2a-tabellen), <3 tellende runder gir fortsatt
  `null` (ikke en feil), sletting av en tellende runde regner faktisk HCP
  på nytt (tilbake til `null` under 3), matchspill-runde eksplisitt
  ekskludert ved opprettelse telles korrekt IKKE med, "Bruk som mitt
  HCP"-overføring bekreftet (+ `handicap_history` bærer nå begge kilder),
  og hele autorisasjonsmatrisen for eksklusjons-feltet (medspiller nektes
  andre felt og eierens rad, men kan endre EGEN eksklusjon; eieren kan
  fortsatt overstyre medspillerens). `test_isolation.sql` 12/12 uendret.
  **Reelt funn UNDER selve scratch-oppsettet, ikke i produksjon:** første
  forsøk på en engangs API-container monterte kildekoden til feil sti
  (`/app/app` i stedet for `/srv/app`, som er `Dockerfile` sin faktiske
  `WORKDIR`) — containeren boot-et rent, men kjørte stille det GAMLE,
  innbakte imagekoden uendret. Fanget FØR noe ble stolt på, ved at
  `/auth/me` manglet det nye feltet helt i et faktisk API-svar — rettet
  ved å montere til riktig `/srv`-sti, deretter bekreftet på nytt.
  **Ekte nettleser-verifisert** (Chrome DevTools MCP, engangs `next dev`-
  container mot scratch-backend — samme "sett resultatet, ikke bare
  typesjekk det"-arbeidsmåte som resten av uken): logget inn via ekte
  magic-link, opprettet en matchspill-runde → bekreftet
  forhåndsutfylt-men-overstyrbar eksklusjonsavkrysning i skjemaet,
  "Matchspill"-badge i rundens header, fullførte runden → bekreftet
  `EditParticipantPanel` automatisk bytter til restriktert modus (kun
  eksklusjons-toggel, ingen av de andre feltene), lagret en endring →
  bekreftet ekte `PATCH .../participants/{id}` 200 i nettverksfanen.
  Ekte typesjekket produksjonsbuild (alle 24 ruter) kjørt og bekreftet.
  **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: migrasjon
  030 kjørt mot ekte `teecup_db` (alle fire nye kolonnesettene bekreftet,
  `test_isolation.sql` fortsatt 12/12 mot ekte database), deretter
  `docker compose up -d --build teecup_api teecup_frontend`. Begge
  containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket. Verifisert presist at den nye ruten faktisk når FastAPI:
  anonymt `POST /auth/profile/handicap/apply-computed` over ekte https ga
  korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.
  **Bevisst utenfor omfang, notert i ADR-038:** turnering-/organisasjons-
  scoring teller fortsatt ikke mot faktisk HCP (venter på videre
  ADR-037-arbeid), ingen egen "aging"-bakgrunnsjobb utover at spørringen
  alltid henter kun de 20 nyeste differensialene.

- **Ekte spillformer for frittstående runder (match/skins/fourball/
  foursome/greensome/scramble), BACKEND BYGGET OG SCRATCH-VERIFISERT,
  IKKE ENNÅ RULLET UT MOT EKTE SYSTEMER (2026-07-28, ADR-039):** reist av
  brukeren rett etter ADR-038: "Er dette en slagspillsrunde, en match
  mellom to spillere, skins, eller en par- eller lag-konkurranse.
  Avhengig av svaret så må hcp beregnes forskjellig, og også scorekortet
  vil se annerledes ut." Fire load-bærende avklaringer (AskUserQuestion,
  to runder) FØR bygging — bruker valgte det bredeste omfanget i alle
  runder: to sider (Beslutning A), ALLE fire par-/lag-underformater fra
  start inkl. de to delt-ball-krevende (Beslutning B), skins med BEGGE
  akser konfigurerbare av oppsetteren (netto/brutto OG rullerer/deles,
  Beslutning D), og gå rett til migrasjon+motor samme økt (ikke bare
  dokumentere).
  **Kjerneinnsikt som gjorde dette trygt å bygge fort:** hele match-play-
  motoren (`handicap_engine.py` sin `Format`/`AllowanceStrategy`-familie/
  `match_play_strokes`/`compute_match_state`) og hele "delt-ball vs.
  individuell"-mønsteret (`hole_score.match_participant_id` NULLABLE,
  delt-ball-rader identifisert av `team_side` alene) fantes ALLEREDE,
  bygget og produksjonskjørt for org-scopede turnering-matcher. Denne
  runden PORTERER dette mønsteret til frittstående runder (nye
  `round_side`/`round_participant.round_side_id`/`round_participant.
  playing_handicap`/`round_hole.round_side_id`) i stedet for å finne opp
  noe nytt — kun skins (ingen turnering-motstykke) fikk EKTE ny
  motorkode (`compute_skins`, 7 nye tester, 50/50 i `test_handicap_
  engine.py`). `app/handicap.py` sin `_SIDE_IS_UNIT` omdøpt til
  `SIDE_IS_UNIT` (gjort delt for gjenbruk, samme "fjern understrek når
  et andre bruksted dukker opp"-mønster som tidligere runder).
  **Skjema, migrasjon `031_round_play_formats.sql`:** `round.play_format`
  utvidet til 8 verdier, nye `round.skins_scoring`/`skins_tie_handling`,
  ny `round_side`-tabell (nøyaktig to per runde, håndhevet i app-laget
  som ADR-011s to-lags-grense), `round_participant.round_side_id`/
  `playing_handicap` (sistnevnte ALDRI det samme som det eksisterende
  `course_handicap_snapshot` — den absolutte WHS-verdien rørt av INGEN
  av denne rundens kode), `round_hole.round_participant_id` gjort
  NULLABLE + ny `round_hole.round_side_id` + XOR-CHECK + to partielle
  unike indekser (samme mønster som org-scopet `hole_score`, migrasjon
  001).
  **Reelt, bekreftet funn UNDER selve designet (ikke antatt), avklart
  eksplisitt med bruker FØR bygging:** foursome/greensome/scramble har
  ÉN kombinert score per SIDE per hull -- INGEN individuell score
  finnes i det hele tatt å bygge en Score Differential fra. Løst
  (Beslutning E, bekreftet av bruker): disse deltakerne får
  `counts_for_handicap` ALDRI sann for disse rundene -- og viste seg,
  presist verifisert i scratch, å følge HELT AUTOMATISK av den
  eksisterende `complete_round`-logikken uten noen kodeendring i det
  hele tatt (en delt-ball-deltaker har null individuelle `round_hole`-
  rader, så `played_count` blir alltid 0, som `round_counts_for_
  handicap` allerede tolker som "teller ikke" for både 9- og
  18-hulls-intensjon).
  **API (`app/routers/rounds.py`):** `POST/DELETE .../sides` (eier-only,
  maks to, avvist for slagspill/skins), `ParticipantCreate`/
  `ParticipantUpdate` fikk `round_side_id` (eier-only reassignment,
  validerer siden hører til samme runde), `_recompute_side_handicaps`
  (porterer `compute_and_store_side_handicaps` — venter på at siden når
  forventet spillerantall FØR den skriver noe, samme
  "ikke komplett ennå = ikke skriv"-filosofi som originalen),
  `_relative_strokes_for_round` (porterer `relative_strokes_for_match`),
  nye `GET/PATCH .../sides/{id}/holes/{n}` (delt-ball-scoring, kun
  slagtall — ingen av de andre detalj-feltene gir mening for en delt
  ball), og et nytt lese-endepunkt `GET .../format-result` som regner
  løpende matchstatus (gjenbruker `compute_match_state`/`HoleResult`
  uendret) for de to-sidede formatene, eller en skins-tavle
  (`compute_skins`) for skins — aldri lagret, alltid avledet ved lesing.
  **To reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde
  produksjon:** (1) side-tildelings-recompute kjørte FØR responsen ble
  hentet i stedet for ETTER — testen fanget dette presist (forventet
  `playing_handicap` i responsen, fikk `null` fra FØR omregningen); (2)
  individuell-ball-gren i format-result-spørringen nøkkel-forvekslet
  "enhet" (satte `round_side_id` som nøkkel i stedet for
  `round_participant_id`, mens `side_net()`-oppslaget forventet
  deltaker-id) — ga et tomt `hole_results` til tross for gyldige
  registrerte scorer, fanget da `match_holes_played` kom ut som 0 i
  stedet for det forventede 3.
  **Scratch-verifisert grundig, 96/96 sjekker** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container via ekte HTTP, samme mønster som hele prosjektet): en
  hånd-utregnet 3-hulls singles-match (hcp 5 vs. 10, riktig slagmottak
  på de fem vanskeligste hullene) ga eksakt `lead=0`/"AS"/riktig
  hole-for-hole-mønster; fourball bekreftet INDIVIDUELL (ikke kombinert)
  90 %-beregning per spiller, korrekt "ikke komplett ennå" før andre
  spiller på siden var tildelt; foursome bekreftet 18 round_hole-rader
  opprettet på SIDEN ved side-opprettelse (før noen deltaker lagt til),
  korrekt kombinert 50 %-Playing-Handicap først når begge var tildelt,
  ekte delt-ball-scoring via det nye sub-endepunktet, OG at
  `counts_for_handicap` ble `False` for begge etter fullføring; skins
  (netto+carry) ga eksakt `{sk3: 2.0}` for et 2-hulls scenario med et
  bevisst konstruert uavgjort-så-carry-så-outright-vinn-mønster, OG
  bekreftet skins teller NORMALT mot faktisk HCP etter full fullføring
  (ulikt match). `test_isolation.sql` 12/12 uendret (additiv migrasjon).
  Alle 50 handicap_engine-tester (43 eksisterende + 7 nye skins) grønne.
  **IKKE bygget i denne runden, bevisst utsatt (Beslutning F):**
  frontend — ingen skjerm for å opprette sider, tildele deltakere,
  konfigurere skins, eller vise løpende matchstatus/skins-tavle. Backend
  er fullt funksjonelt og testet, men ubrukelig fra selve appen inntil
  frontend bygges i en egen, senere runde (samme lagdelings-mønster som
  ADR-038: motor/skjema/API FØR frontend).
  **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
  eksplisitt: migrasjon 031 kjørt mot ekte `teecup_db` (nye kolonner/
  `round_side`-tabell bekreftet, `test_isolation.sql` fortsatt 12/12),
  deretter `docker compose up -d --build teecup_api`. Ren boot,
  `/health`/`/dashboard` → 200, ny rute bekreftet nåbar (anonymt
  `POST .../sides` → 401, ikke en rå 404), `teeoff.no` upåvirket.

- **Oppfølging samme dag: manglende minimums-spiller-håndhevelse fanget
  og fikset, BYGGET OG SCRATCH-VERIFISERT, RULLET UT LIVE (2026-07-28):**
  brukeren påpekte presist et hull ADR-039 selv ikke fanget opp: "Er det
  match-spill, skins eller lagspill så MÅ det jo være flere spillere.
  Dette fanges ikke opp." Riktig — ingenting hindret å fullføre en
  "match" med kun eieren, eller la flere spillere enn formatet tillater
  havne på samme side. Bekreftet med bruker (AskUserQuestion): skins
  krever minst 3 spillere (2 gjør skins i praksis identisk med en vanlig
  match — 3+ er der en oppsamlet, uavgjort pott faktisk gir mening).
  **Bygget, ren Python-logikk, INGEN migrasjon:** ny
  `_format_setup_status()` — slagspill alltid klar; skins krever minst
  3 deltakere; to-sidede formater (match/fourball/foursome/greensome/
  scramble) krever NØYAKTIG to sider, INGEN uassignerte deltakere, og
  hver side nøyaktig riktig spillerantall for formatet
  (`_SIDE_PLAYER_COUNT`, allerede definert). Ny `setup_complete`/
  `setup_message` på `RoundOut` (alltid synlig, uansett format/status).
  `complete_round` avviser nå (409 `SETUP_INCOMPLETE`) hvis oppsettet
  ikke er komplett — FØR noe regnes ut. Ny `_check_side_capacity()`
  avviser (409 `SIDE_FULL`) proaktivt ved SELVE tildelingen (både
  `POST .../participants` med `round_side_id` og `PATCH .../
  participants/{id}`), i stedet for å først oppdage overtallet ved
  fullføring — med eksplisitt unntak for en no-op-reassignment til
  samme side (en deltaker teller ikke seg selv ut av plassen sin egen
  side har).
  **Scratch-verifisert grundig, 122/122 sjekker** (samme isolerte
  scratch-oppsett som resten av runden — full regresjon av alle
  tidligere 96 sjekker PLUSS 26 nye): 3. spiller avvist på en full
  match-/foursome-side (409 SIDE_FULL), `setup_complete` korrekt False
  ved kun 1 av 2 sider / uassignerte deltakere / for få skins-spillere,
  fullføring korrekt avvist (409 SETUP_INCOMPLETE) i alle disse
  tilstandene, og korrekt True (+ vellykket fullføring) først når
  oppsettet faktisk er komplett for formatet. `test_isolation.sql`
  uendret (ingen skjemaendring).
  **Rullet ut live 2026-07-28**, ingen migrasjon, kun
  `docker compose up -d --build teecup_api`. Ren boot, `/health`/
  `/dashboard` → 200, `teeoff.no` upåvirket.
- **Frontend for ADR-039 (sider/skins/delt-ball-scoring) BYGGET, BROWSER-
  VERIFISERT OG LIVE (2026-07-28), samme dag:** ingen backend-endring i
  denne runden (alt allerede live) — ren frontend-jobb, testet reelt i
  nettleser mot en isolert scratch-backend (Chrome DevTools), ikke bare
  typesjekk.
  **`new-round.tsx`:** spilleform-velgeren utvidet fra to (Slagspill/
  Match) til alle åtte format (Skins/Fourball/Foursome/Greensome/
  Scramble 2/4 lagt til), med en ny skins-konfigurasjonsseksjon (netto/
  brutto, rullerer/deles) som kun vises for `play_format="skins"` og et
  forklarende "du setter opp sidene inni runden etterpå"-notat for de
  to-sidede formatene (ADR-039 Beslutning A -- sider kan ikke opprettes
  før deltakerne finnes).
  **`round-detail.tsx` (hoveddelen):** ny `SidesPanel` (manage-fanen) --
  opprett/slett de to sidene, tildel/fjern deltakere (kompakte
  "→ Side"-hurtigknapper, deaktivert når siden er full), viser
  `playing_handicap` per side. Ny `FormatResultPanel` -- henter
  `GET .../format-result`, viser løpende matchstatus (oversetter
  motorens bokstavelige "(A)"/"(B)" til faktiske side-navn via
  `round.sides[0]/[1]`, samme sorteringsrekkefølge som backend) eller en
  skins-tavle (sortert synkende). `setup_complete`/`setup_message`
  gater nå "Fullfør runde"-knappen klientside også (server er fortsatt
  autoritativ). For delt-ball-formatene (foursome/greensome/scramble):
  ny `SideScorecardGrid` (rader = sider, ikke spillere) + ny, forenklet
  `SideScoreWizard` (kun slagtall, ingen putt/detalj-steg) mot de
  eksisterende `GET/PATCH .../sides/{id}/holes/{n}`-endepunktene.
  **To reelle stale-state-bugs funnet UNDER selve browserverifiseringen
  (ikke i kodegjennomgang), begge fikset før utrulling:**
  1. `SidesPanel` sin `assign()` oppdaterte kun deltaker-listen lokalt
     (via `onPatchParticipant`) -- `setup_complete`/`setup_message`
     (server-beregnet) ble stående utdatert etter en vellykket
     side-tildeling ("ikke tildelt en side" fortsatte å vises til tross
     for at begge var tildelt). Fikset: `assign()` kaller nå
     `onSidesChanged()` (full runde-refetch) etter en vellykket PATCH.
  2. `FormatResultPanel` sin refetch var kun koblet til hull-registrering
     og WebSocket-signaler, ikke til side-/deltaker-tildeling --
     matchstatus ble stående på "venter..." selv etter at oppsettet var
     komplett og "Fullfør runde" allerede var aktivert. Fikset ved å
     bumpe `formatResultRefreshTick` ved HVER vellykket `loadRound()`
     (enklere og mer robust enn å spore hvert enkelt kallsted som kan
     påvirke handicap-beregningen).
  **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
  `teecup_scratch`-database, isolert scratch-MinIO, engangs API-
  container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP --
  ekte innlogging via magic-link, ekte profil-fullføring): tre komplette
  runder bygget og spilt gjennom UI-et alene, ende-til-ende:
  - **Match:** opprettet med eksklusjons-avkrysning forhåndshuket,
    opprettet to sider, tildelte eier+gjest, bekreftet `playing_handicap`
    (60/20) vist riktig, scoret hull 1 (4 mot 6) via `ScoringWizard`
    (ubrørt komponent), bekreftet `FormatResultPanel` viste "1 UP
    (Erol)" + riktig fargede hull-merker -- kryssjekket med
    `aria-label`-attributtet direkte via `evaluate_script` for å bekrefte
    semantisk korrekt side-navn bak den rå A/B-bokstaven.
  - **Skins:** konfigurasjons-UI-et (netto/brutto, rullerer/deles) bekreftet
    visuelt, opprettet med kun 1 spiller (satte-message "krever minst 3"),
    la til to gjester til (meldingen forsvant idet den tredje ble lagt til,
    "Fullfør runde" aktivert), scoret hull 1 for alle tre (via direkte
    API-kall for hastighet, samme kontrakt som UI-et bruker), bekreftet
    skins-tavlen -- **hånd-regnet og kryssjekket eksakt**: netto 1/4/3 for
    de tre spillerne (course handicap 60/12/6, alle mottar 1 slag på
    hull 1 unntatt eieren som mottar 4) ga korrekt "1 skin" til laveste
    netto.
  - **Foursome:** bekreftet "Opprett begge sidene..."-meldingen i Score-
    fanen FØR sidene fantes (ingen krasj), opprettet to sider, la til tre
    gjester, scoret hull 1 via `SideScorecardGrid`/`SideScoreWizard`
    (5 mot 5 -- observerte LIVE at gridet oppdaterte seg bak selve
    veiviseren), fant OG fikset de to stale-state-bugene over midt i
    denne runden (glemte først å tildele spillerne til sider -- avdekket
    nettopp fordi UI-et da IKKE viste feil tilstand, men en ekte utdatert
    en), bekreftet til slutt `playing_handicap` kombinert riktig per side
    (38/38 og 17/17) og at `FormatResultPanel` viste "1 UP (Rødt lag)"
    -- kryssjekket for hånd at Rødt lag (høyere kombinert CH) mottar
    slag på det vanskeligste hullet og derfor vinner nettoduellen 5 mot 5.
  Ekte typesjekket + full produksjonsbuild kjørt på nytt ETTER
  bug-fiksene (ikke bare før), alle 24 ruter listet.
  **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
  migrasjon (ren frontend), `docker compose up -d --build
  teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
  ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
  `/dashboard` → 200, `teeoff.no` upåvirket. **ADR-039 er dermed
  fullstendig ferdig, backend og frontend, live.**
- **Auto-hopp i scoringsveiviserne, LIVE (2026-07-28), samme dag:**
  brukeren ba om at "i det øyeblikket [scoren] nå registreres" skal
  veiviseren hoppe videre av seg selv, uten å måtte trykke "Neste"/
  "Ferdig". Presiserende avklaring FØR bygging (AskUserQuestion): "Avstand
  første putt" lå tidligere PÅ SAMME steg som selve putt-tallet i
  `ScoringWizard` -- auto-hopp idet putt-tallet velges ville gjort
  avstandsfeltet uoppnåelig (ingen annen inngang finnes). Løst ved å
  splitte putt-steget i to (bekreftet anbefalt løsning): `WizardStep`
  utvidet med et eget `"puttDistance"`-steg, `wizardStepsFor()` gir nå
  `["strokes","putts","puttDistance"]`/`["strokes","putts","puttDistance",
  "details"]` for de to høyere statistikknivåene.
  **Mekanisme (samme mønster i `ScoringWizard` og den enklere
  `SideScoreWizard` for delt-ball-formater):** to refs -- `enteredWithValueRef`
  fanger om steget sitt eget felt ALLEREDE hadde en verdi idet steget ble
  vist (et allerede utfylt hull skal ikke hoppe videre bare fordi
  veiviseren åpnes, og "Forrige" tilbake til et allerede besvart steg skal
  ikke re-trigge et nytt hopp), `firedRef` hindrer dobbelt-triggering.
  Kun steg med ETT entydig felt (Slag, Putter, Avstand, samt hele
  `SideScoreWizard` sitt eneste Slag-felt) auto-hopper -- "flere
  detaljer"-steget (kølle/retning/chip/bunker/straffeslag/anywayslag) har
  ingen enkelt "dette er ferdig"-verdi og beholder derfor "Neste"/
  "Ferdig"-knappen som manuell handling, bevisst uendret.
  **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
  database/MinIO/API-container, ekte `next dev`, Chrome DevTools):
  full "full"-nivå-runde spilt gjennom Slag→Putter→Avstand (alle tre
  auto-hoppet uten et eneste "Neste"-trykk) →detaljer (korrekt IKKE
  auto-hoppet, krevde et bevisst "Ferdig"-trykk, som deretter gikk videre
  til neste hull av seg selv). "Forrige" fra Putter tilbake til Slag
  bekreftet trygt (viste den allerede valgte verdien, hoppet IKKE
  automatisk fremover igjen). Delt-ball (`SideScoreWizard`, foursome)
  bekreftet separat: valgt slagtall for "Rødt" hoppet umiddelbart til
  "Blått" uten trykk. Ekte typesjekket + full produksjonsbuild kjørt før
  utrulling, alle 24 ruter listet.
  **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
  migrasjon (ren frontend), `docker compose up -d --build
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Scorekort for spillformater (match/skins/fourball/foursome/greensome/
  scramble): to reelle bugs bekreftet og fikset, BYGGET, SCRATCH-/
  BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om at scorekortene
  faktisk sjekkes ved å simulere 3-4 spilte hull i hvert spillformat, med
  et konkret forventningsbilde (en match bør vise hvem som vant hvilket
  hull og HVORFOR -- brutto vs. netto, match-hcp). Simulert systematisk mot
  en isolert scratch-backend (Python/urllib-testskript) FØR noe ble antatt
  riktig -- fant to distinkte, bekreftede problemer, presentert til
  brukeren som fikk velge omfang (AskUserQuestion) og valgte full løsning:
  1. **Ekte blokkerende bug:** foursome/greensome/scramble (ADR-039,
     delt-ball -- score lagres PER SIDE, `round_hole.round_participant_id`
     settes ALDRI for disse) viste 0 spilte hull/ingen score på
     `/my-rounds/[id]/scorecard`, `/leaderboard` OG `/stats`, uansett
     faktisk fremdrift -- disse tre sidene spurte alle kun mot
     deltaker-endepunktet (`round_participant_id`), som strukturelt aldri
     kan ha data for disse formatene.
  2. **Reell designmangel:** for match/fourball/skins var rå brutto/netto/
     stableford riktig, men "hvem vant hvilket hull, og hvorfor" fantes
     KUN i `FormatResultPanel` (manage-fanen i `round-detail.tsx`) som et
     rent vinn/tap-merke -- ingen synlige tall (brutto vs. netto, slag
     mottatt) noe sted, og ingenting av dette på de dedikerte Scorekort-/
     Leaderboard-sidene brukeren faktisk testet.
  **Backend:** ny `compute_skins_detail()` i `handicap_engine.py`
  (hull-for-hull-forløp -- verdier/pott-før/tildelt/carried per hull),
  `compute_skins()` omskrevet til en tynn wrapper rundt den (uendret
  signatur/oppførsel, alle 50 eksisterende tester fortsatt grønne + 5 nye).
  `GET /rounds/{id}/format-result` (ADR-039) utvidet med et nytt
  `holes`-felt -- full hull-for-hull-oppløsning (brutto/netto/slag mottatt
  PER enhet PER hull, pluss for fourball hvilken av de to partnernes netto
  som faktisk talte for siden det hullet, R&A-regelen gjort synlig i
  stedet for skjult). **Reell refactor-bug funnet OG fikset UNDER egen
  scratch-verifisering, før noe ble stolt på:** en samlet `side_net()` for
  BEGGE gren-typene (individuell-ball og delt-ball) brukte format-nivåets
  `expected_players` (spiller-ANTALL, f.eks. 2 for foursome) som
  fullstendighetssjekk også for delt-ball, der en side alltid er NØYAKTIG
  ÉN enhet uansett spillerantall -- ga `match_holes_played=0`/tom
  `holes`-liste for ALLE delt-ball-formater til tross for korrekt lagrede
  side-scorer. Rettet med en egen `required_units`-variabel (1 for
  delt-ball, `expected_players` for individuell-ball). `GET .../sides/
  {id}/holes` fikk samtidig et nytt `strokes_received`-felt (samme
  allokeringsalgoritme, nå basert på sidens kombinerte `playing_handicap`).
  **Frontend:** `round-scorecard.tsx` bruker nå SIDER (ikke deltakere) som
  "enhet" for delt-ball-formater -- samme visuelle `ScoreBlock`-tabell,
  bare mot `/sides/{id}/holes`, med en forklarende melding hvis sidene
  ikke er opprettet ennå. Ny `MatchProgressTable`-seksjon (to-sidede
  formater: Hull/Par/Side A/Side B/Resultat, brutto→netto per enhet, ikke-
  tellende fourball-partner tonet ned i stedet for fjernet) og
  `SkinsProgressTable` (skins: hull-for-hull med hvem som vant/hvilket
  hull som rullet videre, netto med brutto i parentes). `round-
  leaderboard.tsx`: to-sidede formater viser nå en `MatchStatusSection`
  (status + hull-merker + lenke til scorekortets fulle oppløsning) i
  stedet for en individuell rangering som uansett ikke gir mening for et
  1v1/lag-format (og alltid var tom for delt-ball) -- `RoundLeaderboardMini`
  returnerer `null` for disse formatene i `round-detail.tsx` sin manage-
  fane, siden `FormatResultPanel` allerede dekker akkurat det der. `round-
  stats.tsx` viser en tydelig forklarende melding for delt-ball-formater
  (individuell slag-for-slag-statistikk er strukturelt umulig der) i
  stedet for en stille tom/misvisende side.
  **Scratch-verifisert grundig, flere lag:** isolert `teecup_scratch`-
  database + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
  API-container (samme mønster som hele prosjektet), 55/55
  `handicap_engine`-tester, et Python/urllib-simuleringsskript som spilte
  4 hull i alle 6 ikke-trivielle formater og sammenlignet rå API-svar før/
  etter fiksen. **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome
  DevTools MCP, engangs `next dev` mot scratch-backend, ekte innlogging):
  opprettet alle 7 formatene (inkl. `stroke` som regresjonssjekk) via
  ekte API-kall fra en innlogget nettleserøkt, besøkte deretter
  scorekort/leaderboard/stats-sidene for hver -- foursome sitt tidligere
  BLANKE scorekort viste nå korrekte side-tabs + reelle score + riktig
  `Matchforløp`-tabell; fourball sin `Matchforløp` viste presist BEGGE
  partnernes brutto/netto med den ikke-tellende partneren korrekt tonet
  ned; skins sin hull-for-hull-tabell viste riktig vinner-navn og
  "Uavgjort — rullet videre" nøyaktig der forventet; leaderboardets
  matchstatus stemte hull-for-hull med scorekortets egen utregning (bevisst
  kryssjekket for hånd); fanebytte mellom sider (Side A/Side B) bekreftet
  å faktisk refetche og re-rendre riktig data. Ekte typesjekket +
  produksjonsbuild (alle 24 ruter) kjørt både før og etter refactor-bug-
  fiksen. `test_isolation.sql` uendret (ingen migrasjon, ren kode-endring).
  **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
  Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket.
- **Offline-kø (ADR-028) utvidet til frittstående runder, BYGGET,
  BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om det som ble
  identifisert som viktigste gjenstående hull etter forrige runde --
  ADR-028s offline-skrivekø var kun koblet til turnering-scorekortet
  (`session-scorecard.tsx`), IKKE frittstående runder
  (`round-detail.tsx`), nettopp der man oftest står alene ute på banen
  uten dekning, og nå som frittstående runder er bekreftet appens
  hovedfokus var dette et reelt hull i kjerneflyten.
  **Enklere port enn originalen, ikke bare en kopi:** turnering-
  scorekortets PATCH-endepunkter er en append-historie (krever egne
  `pendingStrokes`/`pendingResults`-verdi-overlays for å vise queued
  verdier). Frittstående runders `PATCH .../participants/{id}/holes/{n}`
  og `PATCH .../sides/{id}/holes/{n}` erstatter derimot HELE hull-raden
  per kall, og `statToPatchBody()` sine feltnavn matcher `ApiHole` 1:1 --
  en køet skriving kan dermed speiles direkte inn i
  `holesByParticipant`/`holesBySide` med en enkel `{...h, ...body}`-spread,
  ingen egen verdi-overlay nødvendig. Kun to lette `Set<string>` (nøkkel
  `id:hullnummer`) beholdt, utelukkende til selve "lagret lokalt"-
  indikatoren i veiviseren.
  Samme kø/synk-mønster som originalen ellers: `navigator.onLine`-sjekk
  FØR forsøk, `try/catch` rundt selve fetch-kallet som queuer ved en EKTE
  nettverksfeil (ikke ved et avvist HTTP-svar -- det vises fortsatt som
  vanlig feiltekst), auto-synk ved `window`s `online`-event, manuell
  "Synkroniser nå"-knapp, og en engangs-sjekk for allerede køede
  skrivinger fra en TIDLIGERE økt (f.eks. siden ble lukket mens offline)
  ved mount. `flushPending()` matcher URL-mønsteret på hver synkronisert
  kø-oppføring (`/participants/{id}/holes/{n}` vs. `/sides/{id}/holes/
  {n}`) for å vite hvilke deltakere/sider som trenger en ekte refetch
  etterpå (reconciles bl.a. `strokes_received`, som den optimistiske
  speilingen ikke kan regne ut selv). Banner ("Du er offline"/"N
  endringer venter") lagt til rett under fane-velgeren, synlig uansett
  fane. "Lagret lokalt · venter på synk"-indikator lagt til i BÅDE
  `ScoringWizard` (vanlig scoring) og `SideScoreWizard` (delt-ball).
  **Browserverifisert grundig, IKKE bare kodegjennomgang/build denne
  gangen** (i motsetning til den opprinnelige ADR-028-runden, som
  brukeren selv måtte teste manuelt siden intet nettleserverktøy var
  tilgjengelig da) -- Chrome DevTools MCP sin ekte nettverks-emulering
  (`Offline`, bekreftet at `navigator.onLine` faktisk flippet til
  `false`) mot en isolert scratch-backend: registrerte slag+putter
  offline for en vanlig runde -- bekreftet 2 kø-oppføringer i ekte
  IndexedDB, optimistisk oppdatert scorekort-grid, "1 endring venter"-
  banner, "Lagret lokalt"-indikator i veiviseren; koblet til nett igjen
  -- bekreftet AUTOMATISK synk (ingen manuelt trykk), kø tom etterpå, OG
  et direkte API-kall som bekreftet serveren faktisk hadde de riktige
  verdiene (score=5, putts=1, strokes_received=1). Gjentok hele syklusen
  for en ny foursome-runde via `SideScoreWizard` (delt-ball) -- samme
  resultat, kø tom og server bekreftet score=4 for siden etterpå. Ingen
  konsollfeil i noen av rundene. Ekte typesjekket + full produksjonsbuild
  (alle 24 ruter) kjørt før utrulling.
  **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
  migrasjon (ren frontend), `docker compose up -d --build
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket.
- **Turnering-scorekortets offline-flyt (den OPPRINNELIGE ADR-028, session-
  scorecard.tsx) FAKTISK browserverifisert, samme dag (2026-07-28):**
  eneste reelt gjenstående punkt fra forrige runde — offline-koden i
  turnering-scorekortet er over ett år gammel (funksjonelt), men var ALDRI
  browser-testet, kun kodegjennomgang/build (se opprinnelig ADR-028-notat).
  Bygget en full isolert scratch-turnering fra bunnen via API for å nå
  frem til selve scorekortet (organisasjon → turnering → to lag → to
  spillere rostret som kapteiner → egendefinert bane med 18 hull + tee →
  økt (`format=singles`, `scoring_mode=stroke`) → match → to
  match-deltakere → begge lag låst) -- ingen slik full turnering-scaffold
  fantes fra før i noe testskript denne uken (frittstående runder trenger
  ikke dette apparatet i det hele tatt, ADR-033 Beslutning A), måtte bygges
  fra grunnen ved å lese `tournaments.py`/`matches.py`/`courses.py` sine
  faktiske endepunkt-kontrakter direkte.
  **Verifisert identisk mønster som frittstående runder samme dag:** ekte
  DevTools-nettverksemulering (`navigator.onLine` bekreftet `false`),
  registrerte et slag (5, Spiller B, hull 1) offline -- bekreftet ÉN
  kø-oppføring i ekte IndexedDB (`POST .../hole-scores`), "1 endring
  venter"-banner, "Lagret lokalt · venter på synk"-tekst under
  tallvelgeren, hull-navigasjonens sjekkmerke. Koblet til nett igjen --
  bekreftet AUTOMATISK synk (ingen manuelt trykk på "Synkroniser nå"
  nødvendig), kø tom etterpå, "Bogey"-etiketten dukket opp (bevis på at
  `refetchScorecard()` faktisk hentet det avledede resultatet fra
  serveren), OG et direkte API-kall mot `.../scorecard` som bekreftet
  serveren faktisk hadde `gross_strokes=5` lagret. Ingen konsollfeil.
  **Ingen kodeendring i denne runden** — ren verifisering av allerede
  levert funksjonalitet, ingen utrulling nødvendig.
- **Undersøkt: grensesnitt for å følge en venns runde live — BEKREFTET AT
  DET IKKE FINNES (2026-07-28), ren undersøkelse, ingen kode skrevet.**
  Brukeren spurte om en spiller kan gå inn på en venns profil og følge en
  pågående frittstående runde live, gitt at rettighetene er gitt. Lest
  direkte i koden (ikke antatt): (1) `frontend/components/friends.tsx` har
  INGEN lenke/rute til en vennprofil-side i det hele tatt — kun
  send/aksepter/avvis-knapper og kategorisering, ingen `/friends/[id]`-
  eller lignende rute finnes noe sted i `frontend/app/`. (2) `round`-
  tabellen har INGEN `visibility`-kolonne (bekreftet med grep over ALLE
  migrasjoner — `visibility` finnes kun på `tournament`, fra
  `009_landing_pages_and_visibility.sql`); `020_personal_rounds.sql` sin
  egen kommentar (linje 39-42) slår eksplisitt fast v1-avgrensningen: en
  runde er kun synlig for `owner_user_id`. (3) `app/routers/rounds.py` sin
  `_get_accessible_round_or_404` (linje 1220-1240, lest direkte) gir
  tilgang KUN til eieren ELLER en lenket `round_participant.user_id`
  (ADR-036 fase 3) — ingen sjekk mot `friendship`-tabellen noe sted,
  bekreftet med `grep -n -i "friend" app/routers/rounds.py` → null treff.
  (4) `GET /ws/rounds/{id}/live` bruker NØYAKTIG samme
  `_get_accessible_round_or_404`-sjekk FØR `websocket.accept()` (samme
  fil, linje ~2763) — en venn som ikke er lenket deltaker får
  websocketen lukket med kode 4403, ulikt turnering-live (ADR-027), som
  bevisst TILLATER anonym tilgang. Ingen forberedt/påbegynt kode for
  "følg venns runde" funnet noe sted (grep etter "follow"/"følg"/"friend"
  i både `rounds.py` og `friends.py` — null relevante treff).
  **Konklusjon: dette er nøyaktig ADR-036 fase 2 (rundevisibilitet
  public/private/friends), som lenge har stått notert som IKKE bygget i
  FEATURE_BACKLOG.md/CLAUDE.md** — bekreftet nå med presis kode-evidens i
  stedet for bare et notat om at det mangler. Ingen ny beslutning tatt,
  ingen kode skrevet — dette var en ren undersøkelse på brukerens
  eksplisitte forespørsel. Se FEATURE_BACKLOG.md for ADR-036 fase 2 sitt
  design (public/private/friends, eksplisitt gruppevalg).
- **ADR-036 fase 2 (rundevisibilitet) BYGGET, GRUNDIG SCRATCH-/
  BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging
  av undersøkelsen over — brukeren bekreftet eksplisitt retningen fra
  Beslutning B (allerede fullt designet i ADR-036, ikke funnet opp på
  nytt): tre nivåer (`public`/`private`/`friends`), der `friends` krever
  et EKSPLISITT kategori-valg (ikke "alle venner").
  **Migrasjon `032_round_visibility.sql`:** `round.visibility_mode`
  (`public`/`private`/`friends`, default `private`), ny tabell
  `round_visible_category` (samme faste kategori-sett som
  `friend_categorization`, migrasjon 025, bevisst duplisert CHECK fremfor
  delt ENUM — samme pragmatiske mønster som resten av skjemaet).
  **Kjernestykket, `app/routers/rounds.py`:** ny `_can_view_round()` —
  eier ELLER lenket medspiller ser alltid; ellers `public`→alle (også
  anonyme), `private`→ingen, `friends`→krever et AKSEPTERT vennskap MED
  eieren OG at EIERENS kategorisering av viewer (retningen er bevisst
  omvendt av hva man skulle tro — det er eieren som begrenser, basert på
  egen gruppering) treffer minst én av rundens synlige kategorier.
  **Reelt funn under selve designarbeidet:** `_can_view_round()` viste
  seg å være en STRIKT SUPERSETT av den eksisterende
  `_get_accessible_round_or_404()` sin logikk (dens to første grener ER
  nøyaktig eier+medspiller-sjekken) — i stedet for å bygge en helt
  parallell endepunkt-familie fra bunnen, ble fire eksisterende
  autentiserte lese-endepunkters KROPP ekstrahert til delte
  hjelpefunksjoner (`_build_participant_holes`/`_build_side_holes`/
  `_build_leaderboard`/`_build_format_result` — mekanisk gjort med et
  Python-script for presis inndenting fremfor manuell redigering av
  ~270+95 linjer, verifisert med `ast.parse` + full regresjonskjøring
  etterpå), gjenbrukt av BÅDE de originale autentiserte endepunktene OG
  syv nye `/public/rounds/*`-endepunkter (samme `get_current_user_
  optional`-mønster som turnering sin offentlige side, ADR-018/026/027)
  pluss et nytt offentlig WS-endepunkt `/ws/public/rounds/{id}/live`
  (ulikt det eksisterende PRIVATE `/ws/rounds/{id}/live`, som fortsatt
  krever ekte autentisert eier/medspiller-sesjon, uendret). Egen, leaner
  `PublicRoundOut` (aldri `guest_email`, som er PII, aldri `my_*`/
  `setup_*`, som kun gir mening for eier/deltaker) i stedet for å
  gjenbruke den fulle `RoundOut` med etterhånds-redigering.
  Ny `GET /people/{id}` (friends.py, samme lavsensitive felt-sett som
  `/people/search`, bevisst OPTIONALT autentisert — en offentlig runde
  må kunne nås via en delt lenke selv av en anonym leser, og
  vennprofil-siden som leder dit må da fungere anonymt også) og
  `GET /public/people/{id}/rounds` (rounds.py, lister eierens runder
  filtrert gjennom `_can_view_round()` per rad — "pågår nå" alltid øverst).
  **Scratch-verifisert grundig, 191 automatiserte sjekker i tre testløp**
  (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
  API-container, samme mønster som hele prosjektet): et NYTT 34-punkts
  skript som dekket hele synlighetsmatrisen presist (tre brukere A/B/C +
  en helt anonym opener) — B/C/anonym nektet privat OG venner-runde FØR
  vennskap, alle ser offentlig (inkl. anonym), venner-runde fortsatt
  nektet RETT ETTER vennskap men FØR kategorisering, tilgjengelig
  UMIDDELBART etter riktig kategorisering, feil kategori (close_family
  mot golf_friends) fortsatt nektet, PATCH kan endre synlighet i
  etterkant (bekreftet begge retninger), offentlig+autentisert
  leaderboard ga BEVIST IDENTISK resultat (kryssjekket), ukjent
  runde-id ga 404 (ikke 403). PLUSS en full regresjonskjøring av to
  eksisterende testsuiter fra tidligere runder denne uken (35-punkts
  co-player-flyt, 122-punkts spillformat-flyt) — begge 100 % grønne,
  bekrefter at ekstraheringen av de fire delte funksjonene ikke endret
  noen eksisterende oppførsel.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
  isolert browser-kontekst for en helt anonym tredje "bruker" ved siden
  av to ekte innloggede faner): (1) synlighetsvelgeren i opprett-runde-
  veiviseren fungerte som designet (tre knapper, kategori-multiselect
  dukker kun opp ved "Venner", advarselstekst ved 0 valgte kategorier) —
  runde opprettet med `friends`+`golf_friends`, bekreftet via API;
  (2) rediger-runde-panelet viste korrekt FORHÅNDSUTFYLT synlighet,
  PATCH til `public` bekreftet lagret; (3) en HELT ANONYM
  nettleserkontekst (ingen cookies i det hele tatt) kunne se den
  offentlige runden direkte på `/watch/{id}` (live-puls, leaderboard,
  riktig eiernavn) OG via `/my-friends/{eier-id}`-profilsiden (navn/
  avatar/HCP/hjemmeklubb + rundeliste med lenke inn); (4) en ekte andre
  bruker sendte venneforespørsel, eieren aksepterte og kategoriserte
  vedkommende som "Golfvenner" VIA DEN FAKTISKE UI-EN (ikke bare API) —
  satte deretter runden til `friends`+`golf_friends`, bekreftet vennen
  fikk tilgang UMIDDELBART via profilsiden; (5) **negativ kontroll,
  samme venn**: byttet runden til `friends`+`close_family` (en kategori
  vennen IKKE var satt i) — profilsiden viste korrekt INGEN runder
  lenger, og et direkte `/watch/{id}`-forsøk ga en tydelig "Du har ikke
  tilgang"-melding (403), atskilt fra en egen "Denne runden finnes
  ikke"-melding for en ukjent id (404) — begge bekreftet med ekte
  skjermbilder. Ekte typesjekket + full produksjonsbuild (26 ruter, inkl.
  de to nye `/my-friends/[id]` og `/watch/[id]`) kjørt før utrulling.
  **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
  eksplisitt (viste frem full plan for migrasjon FØR den kjørte, per
  CLAUDE.md sin ufravikelige regel): migrasjon 032 kjørt mot ekte
  `teecup_db` (kun additivt — ny kolonne med default, ny tabell, ingen
  eksisterende rader rørt), `test_isolation.sql` fortsatt 12/12,
  deretter `docker compose up -d --build teecup_api teecup_frontend`.
  Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket. Verifisert presist at de nye rutene faktisk når FastAPI
  (ikke bare at Next.js svarte): et ukjent person-id mot
  `/public/people/{id}/rounds` over ekte https ga korrekt JSON-formet
  `404 NOT_FOUND` (ikke en rå Next.js-404-side).
  **ADR-036 er dermed HELT ferdig, alle tre faser** (venner-kjernen,
  rundevisibilitet, ekte medspillere) — backend + frontend, live.

- **Rundevarsler koblet til det eksisterende varslingssenteret, BYGGET,
  SCRATCH-VERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging
  av ADR-036 fase 2 — varslingssenteret (2026-07-26) hadde fra start
  reservert `type` for `"round"`/`"result"` (skjema OG frontendens
  `KIND_ICON`/`KIND_LABEL`), men INGENTING skrev noensinne en slik rad —
  kun venneforespørsler trigget et varsel. Bruker bekreftet omfang
  eksplisitt (AskUserQuestion, alle tre valgt): (A) lagt til som ekte
  medspiller på en runde, (B) en venn starter en runde du kan se
  (offentlig, ELLER `friends`-synlig med treffende kategori), (C) en
  runde du er koblet til (eier/medspiller/tredjeparts-venn fra B) blir
  fullført.
  **Ren backend-endring, INGEN frontend-endring nødvendig** — bekreftet
  ved lesing av `notifications.tsx` FØR noe ble bygget: `NotificationKind`/
  `KIND_ICON`/`KIND_LABEL` dekker allerede `"round"`/`"result"` fullt ut,
  siden de ble reservert med akkurat dette for øye i den opprinnelige
  runden.
  **Bygget i `app/routers/rounds.py`:** ny delt `_friends_who_can_see_
  round(conn, round_id, owner_user_id)` — GJENBEREGNER (ikke en lagret
  mottakerliste) nøyaktig samme regel som `_can_view_round` (offentlig =
  alle aksepterte venner, `friends` = kun de hvis EGEN kategorisering av
  venn treffer rundens synlige kategorier), brukt BÅDE ved opprettelse og
  ved fullføring — aldri ute av synk med selve tilgangskontrollen.
  `create_round` sender (B) rett etter transaksjonen (kun for
  `public`/`friends`, aldri `private`), lenke til `/watch/{id}`.
  `add_participant` sender (A) inni transaksjonen når `user_id` er satt
  (aldri for gjester — de har ingen konto å varsle), lenke til
  `/my-rounds/{id}` (full tilgang, ikke tredjeparts-visningen).
  `complete_round` sender (C) til to ATSKILTE mottakergrupper med ulik
  lenke, siden de har ulik tilgang: lenkede medspillere (unntatt den som
  selv fullførte) → `/my-rounds/{id}`; tredjeparts-venner fra (B),
  gjenberegnet på nytt → `/watch/{id}` — ingen dobbel-varsling hvis noen
  skulle være i begge grupper (settoperasjon), og aldri et varsel til
  personen som selv utførte handlingen.
  **Scratch-verifisert grundig, 28/28 nye sjekker** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container, samme mønster som hele prosjektet, alle 32 migrasjoner kjørt
  friskt): venn fikk `round`-varsel ved synlig opprettelse, fremmed fikk
  det IKKE, privat runde ga INGEN varsel til noen, medspiller fikk
  `round`-varsel med riktig `/my-rounds/`-lenke ved tilføyelse, fullføring
  ga `result`-varsel til BÅDE medspiller (`/my-rounds/`) og venn
  (`/watch/`) men ALDRI til den som selv fullførte, en privat solorunde
  sin fullføring ga fortsatt ingen varsler, mark-as-read uendret. PLUSS
  full regresjon av tre eksisterende testsuiter (co-player-flyt 35/35,
  spillformat-flyt 122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle
  fortsatt 100 % grønne, ingen utilsiktet bivirkning av de nye
  `create_notification()`-kallene inni eksisterende transaksjoner.
  `test_isolation.sql` 12/12 uendret (ingen migrasjon, ren Python-logikk).
  **Rullet ut live 2026-07-28**, ingen migrasjon, kun
  `docker compose up -d --build teecup_api`. Ren boot
  (`Application startup complete`), `/health`/`/dashboard` → 200,
  `teeoff.no` upåvirket.

- **E-post-fallback for varsler, per type, BYGGET OG SCRATCH-VERIFISERT
  (2026-07-28), samme dag, rett etter rundevarsel-runden:** brukeren ba
  eksplisitt om at mottakeren selv skal kunne velge HVILKE varseltyper som
  skal utløse e-post — ikke en enkelt global av/på-bryter. Ny migrasjon
  `033_notification_email_prefs.sql`: `user_notification_email_pref`
  (`user_id`, `type`, samme CHECK-sett som `notification.type`) — samme
  "tilstedeværelse = valgt"-mønster som `round_visible_category`/
  `friend_categorization`, TRYGG STANDARD ingen rad = ingen e-post for
  noen type (samme "se ingenting til noen har valgt"-filosofi som resten
  av appen).
  **Bevisst ÉN felles innsnevring, ikke ett kallsted per varseltrigger:**
  e-post-utsendingen ligger INNI `create_notification()` selv (etter selve
  INSERT-en) — modul-docstringen kalte den allerede "den eneste
  skrivevegen inn", så alle nåværende OG fremtidige varsel-triggere (i dag
  2 i friends.py, 4 i rounds.py) får e-post-støtte helt uten å røres,
  ingen risiko for at et fremtidig kallsted glemmer det. Samme
  `SMTP_CONFIGURED`-sjekk + try/except + `traceback.print_exc()`-mønster
  som all annen e-postutsending i appen (routers/auth.py,
  organizations.py) — en driftsfeil i selve SMTP-en skal ALDRI hindre at
  in-app-varselet (allerede skrevet FØR e-post-forsøket) består.
  Ny `send_notification_email()` i `app/email.py` — bevisst tospråklig KUN
  i ramme-teksten (emne/hilsen/lenkeforklaring); selve `message`-teksten
  er allerede en ferdig norsk snapshot-tekst (samme prinsipp som
  in-app-varselet), ingen full i18n av selve varselinnholdet i denne
  runden.
  Nye `GET`/`PUT /notifications/email-prefs` — PUT er en FULL erstatning
  (samme kontrakt som `PUT /friends/{id}/categories`), ikke en delvis
  PATCH.
  **Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på:**
  forrige rundes `add_participant`-varsel (medspiller lagt til) lå INNI
  den åpne DB-transaksjonen for selve deltaker-innsettingen — flyttet til
  RETT ETTER (samme mønster som `create_round`/`complete_round` allerede
  fulgte), slik at en fremtidig blokkerende SMTP-utsending aldri skjer
  mens en transaksjon holder låser.
  **Frontend:** ny seksjon "Varsler på e-post" i `/account`
  (`NotificationEmailPrefsSection`, `account-settings.tsx`) — fire
  avkrysningsbokser (Venneforespørsler/Runder/Resultater/Turneringer, sistnevnte
  reservert for fremtidig bruk), samme avkrysningsboks-mønster som
  kølle-bag-listen lenger opp i samme fil, lagrer umiddelbart ved hvert
  klikk (ingen egen "lagre"-knapp).
  **Scratch-verifisert grundig, 19/19 nye sjekker** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container, alle 33 migrasjoner kjørt friskt): trygg standard bekreftet
  (ingen typer valgt fra start), full-erstatning bekreftet (PUT med ett
  sett fjerner det forrige, legger ikke til), e-post-fallback FAKTISK
  logget (dev-log-varianten av `SMTP_CONFIGURED`-grenen) for en bruker med
  `round`/`result` valgt inn — BÅDE ved medspiller-tilføyelse og ved
  fullføring — INGEN e-post til brukeren som selv utførte handlingen,
  INGEN e-post til en bruker (eieren) som ikke har valgt inn NOE, og etter
  å ha slått AV `result` igjen: ingen ny e-post ved neste fullføring MENS
  in-app-varselet fortsatt opprettes helt uendret (de to er reelt
  atskilte, ikke koblet). PLUSS full regresjon av fire eksisterende
  testsuiter (rundevarsler 28/28, co-player-flyt 35/35, spillformat-flyt
  122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle fortsatt 100 %
  grønne, ingen bivirkning av at `add_participant`s varsel flyttet utenfor
  transaksjonen. `test_isolation.sql` 12/12 uendret. Ekte typesjekket
  produksjonsbuild (samme `Dockerfile` som deployes) kompilerte rent,
  alle 24 ruter listet uendret.

- **Tre organisator-oppfølgingspunkter fra FEATURE_BACKLOG.md, ALLE
  BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:**
  brukeren ba om at alle tre gjenstående, godt avgrensede oppfølgings-
  punkter fra en tidligere gjennomgang av `.md`-filene tas i én runde,
  inkludert oppdatering av de relevante backlog-/beslutningsfilene.
  1. **Flytte spiller mellom lag:** ny
     `POST /orgs/{id}/teams/{team_id}/roster/{roster_id}/move`
     (`{"target_team_id": ...}`, `app/routers/tournaments.py`) —
     atomisk `UPDATE team_roster SET team_id = ...`, avviser 409
     `ALREADY_IN_MATCH` hvis spilleren allerede er lagt til i en match
     (roster-raden er referert av `match_participant.team_roster_id`
     med `ON DELETE RESTRICT` -- en flytting ville da gjort matchens
     `team_side` inkonsistent med spillerens faktiske lag), 400 ved
     flytting til samme lag, 404 ved ukjent mållag/roster-id.
     Kapteinmerket nullstilles eksplisitt ved flytting (følger ikke med
     til det nye laget). Frontend: ny "Flytt til {annet lag}"-handling i
     `TeamPanel` sin per-spiller-meny (`tournament-detail.tsx`) — v1s
     to-lags-grense (ADR-011) gjør målet entydig, ingen dropdown
     nødvendig. Ingen migrasjon.
  2. **Individuell rangering PER ØKT i en org-turnering:** ny
     `GET /orgs/{id}/sessions/{id}/individual-leaderboard` — bevisst
     AVGRENSET til én økt (ikke summert på tvers av turneringen, samme
     avklaring som rundeleaderboardets omfang tidligere), og KUN
     meningsfull for `scoring_mode='stroke'`-økter i individuell-ball-
     format (singles/fourball; delt-ball-formater og hole_result-modus
     avvist med 400, ikke krasj, siden de ikke har noen individuell
     brutto-score å rangere fra). Gjenbruker `mp.playing_handicap`
     (allerede beregnet, allowance-justert) + `allocate_over_played_
     holes` (samme mønster som eksisterende match-play-scoring i
     `scoring.py`, som fikk sin `_played_hole_numbers` omdøpt til
     `played_hole_numbers` -- "fjern understrek når et andre bruksted
     dukker opp"-mønsteret, samme som `SIDE_IS_UNIT` tidligere) — ingen
     ny regnelogikk. Respekterer samme reveal-gating (`locked_team_ids`/
     `own_team_ids`, ADR-013/026) som den eksisterende matchlisten.
     Frontend: ny side `/tournaments/[id]/sessions/[sessionId]/
     individual-leaderboard` (`session-individual-leaderboard.tsx`,
     egen enklere lokal variant av `round-leaderboard.tsx` sitt
     rangerings-/mode-toggle-mønster -- brutto/netto/poeng, delt
     plassering "T-N"), lenket fra blind draw-skjermen for
     kvalifiserende økter. Ingen migrasjon.
  3. **Midlertidige spillere + automatisk etter-runde-invitasjon
     (økt-nivå):** de tre tidligere åpne spørsmålene avklart eksplisitt
     (AskUserQuestion) -- nivå ØKT (ikke turnering), dobbel-utsending-
     sperre JA, locale bevisst alltid `nb`. Ny migrasjon
     `034_session_scorecard_invitations.sql`
     (`match_participant.invitation_sent_at`, samme "tidsstempel =
     skjedd"-mønster som `round.started_at` m.fl.). Ny
     `POST /orgs/{id}/sessions/{id}/send-scorecard-invitations` —
     sender KUN til spillere uten konto (`player.user_id IS NULL`) OG
     med registrert e-post OG uten en tidligere sendt invitasjon for
     akkurat denne (økt, deltaker)-kombinasjonen; responsen skiller
     `sent`/`skipped_has_account`/`skipped_no_email`/
     `skipped_already_sent` for full gjennomsiktighet. Gjenbruker
     SAMME magic-link-token-mekanisme som vanlig innlogging (ikke bare
     en "logg inn senere"-henvisning) -- ny `send_session_result_email()`
     i `app/email.py` (begge nb/en-maler klare, kun `nb` faktisk brukt).
     E-postens innhold: matchresultat (`status_text`) + individuelt
     slagtotal når tilgjengelig (individuell-ball-formater -- `SUM
     gross_strokes` er naturlig `NULL` for delt-ball-formater uten noen
     egen format-sjekk, siden `match_participant_id` aldri settes i
     `hole_score` der). Frontend: ny "Send scorekort til alle med
     e-post"-knapp i blind draw-skjermen (`session-blind-draw.tsx`),
     synlig når økten har minst én match.
  **Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på**
  (delt med forrige rundes rundevarsel-mønster): dev-log-grenen for den
  nye invitasjons-e-posten skrev først KUN en oppsummeringstekst, ikke
  selve token-en -- umulig å teste innloggingslenken i et scratch-/dev-
  miljø uten SMTP. Rettet til å skrive to linjer: én i SAMME format som
  `request_magic_link` sin etablerte `[DEV] Magic link for ...`-linje
  (så eksisterende dev-verktøy som harvester token derfra fungerer
  uendret), pluss en egen lesbar oppsummeringslinje.
  **Scratch-verifisert grundig, 54/54 nye sjekker** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container, alle 34 migrasjoner kjørt friskt, full org-turnering-
  scaffold bygget fra bunnen via API -- org/bane/18 hull/tee/turnering/
  to lag/seks spillere/roster/singles-stroke-økt/to matcher): flytting
  happy-path + kaptein-nullstilling + alle tre feilveier (409/400/404)
  bekreftet, individuell rangering bekreftet tom→fylt→korrekt brutto/
  netto/poeng for et hånd-utregnet 4-spiller-scenario (kryssjekket at
  netto-til-par alltid ≤ brutto-til-par for alle fire), begge ikke-
  kvalifiserende økt-typer (hole_result, foursome) avvist rent,
  invitasjons-utsending bekreftet presist (1 sendt/2 manglet e-post/1
  hadde allerede konto via en EKTE innlogget "linket" bruker), andre
  kall bekreftet idempotent (0 nye, riktig `skipped_already_sent`), OG
  den utstedte lenken bekreftet FAKTISK brukbar (spilleren logget inn
  med den, kontoen ble koblet til spiller-profilen, samme ADR-017-
  mekanisme uendret). PLUSS regresjon av scoring.py-omdøpingen
  (`test_holeless_course_crash.py`, 5/5) og to notifikasjons-/e-post-
  testsuiter fra forrige runde (28/28, 19/19) — alle fortsatt grønne.
  `test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild
  kompilerte rent, ny rute `/tournaments/[id]/sessions/[sessionId]/
  individual-leaderboard` listet.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
  isolert scratch-backend, ekte innlogging inkl. tvungen 2FA-oppsett for
  en fersk organisasjonseier): flyttet en spiller mellom lag direkte i
  UI-et og bekreftet begge lags roster-lister/-antall oppdatert
  umiddelbart; åpnet individuell rangering og bekreftet alle tre
  visningsmodiene (Brutto/Netto/Poeng) viste korrekte, ulike tall for de
  samme to spillerne; trykket "Send scorekort til alle med e-post" og
  bekreftet resultatteksten "1 invitasjon sendt, 1 mangler registrert
  e-post" -- trykket samme knapp igjen og bekreftet "0 invitasjoner
  sendt, 1 allerede sendt tidligere, …" (dobbel-sperren synlig direkte i
  UI-et, ikke bare i et API-svar). Ingen konsollfeil i noen av de tre
  rundene.
  **Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
  eksplisitt: migrasjon 034 kjørt mot ekte `teecup_db`
  (`invitation_sent_at`-kolonnen bekreftet, `test_isolation.sql`
  fortsatt 12/12), deretter `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/
  `/dashboard` → 200, `teeoff.no` upåvirket.
  **Samtidig, ren dokumentasjonshygiene:** et par steder i
  `FEATURE_BACKLOG.md`/`ARCHITECTURE_DECISIONS.md` hadde blitt hengende
  etter `CLAUDE.md` (ADR-039-utrulling/frontend og PWA-offline-
  browsertesting fremstod fortsatt som "ikke gjort" til tross for at
  begge var fullført i tidligere økter denne uken) — rettet til å
  stemme med den faktiske, allerede leverte tilstanden.

- **Fire gjenstående forslag fra en tidligere "hva nå?"-runde, ALLE FIRE
  BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28, samme dag:**
  brukeren ba eksplisitt om å ferdigstille alle fire samtidig
  (PWA-installasjon, flere flighter i frittstående runder, scramble/
  greensome-statistikk, push-varsler til telefonens OS) — se
  FEATURE_BACKLOG.md for full detalj per punkt, kort oppsummert her.
  Tre nye migrasjoner (`035_round_flight_group.sql`,
  `036_round_hole_selected_participant.sql`, `037_push_subscriptions.sql`),
  alle rent additive.
  1. **PWA-installasjonsoppfordring** — `lib/pwa-install.ts` (globalt
     fanget `beforeinstallprompt`, mountet via `sw-register.tsx` på alle
     sider siden eventet kun fyres én gang per side-liv) + ny
     `components/install-prompt.tsx`, vist på dashbordet rett under
     hilsenen. Android/Chrome-familien får en ekte "Installer"-knapp,
     iOS Safari et instruksjonsbanner (ingen programmatisk vei finnes
     der), andre nettlesere uten reell installasjonsvei viser ingenting.
  2. **Flere flighter i én frittstående runde** (retning 1, løs
     gruppering av separate `round`-rader bundet sammen av en klient-
     generert delt UUID, `round.flight_group_id`) — ny
     `GET /rounds/{id}/flight-group` + `GET .../flight-group/leaderboard`
     (slår sammen hver tilgjengelig søsken-flights EGET leaderboard til
     én rangert liste, `score_to_par` allerede normalisert og dermed
     sammenlignbart på tvers av baner). Ny `FlightGroupPanel` i
     `round-detail.tsx` ("+ Legg til en flight til" gjenbruker HELE
     opprett-runde-flyten, forhåndsutfylt via URL-parametre — banen
     velges på nytt per flight, bevisst), ny side
     `/my-rounds/[id]/flights`.
  3. **Scramble/greensome: valgt utslag per spiller** — avgrenset til
     frittstående runder (ADR-039 sitt delt-ball-format), IKKE
     org-scopede turneringer i denne runden (bevisst scope-kutt, se
     FEATURE_BACKLOG.md). Ny `round_hole.selected_participant_id`,
     `PATCH .../sides/{id}/holes/{n}` fikk et nytt valgfritt felt (samme
     "full overwrite hvert kall"-kontrakt som `played`/`score`).
     `SideScoreWizard` fikk en ny valgfri "Hvem sitt utslag ble brukt?"-
     seksjon (auto-hopp bevisst slått av her, ellers ville brukeren blitt
     revet videre før valget kunne gjøres); `round-stats.tsx` fikk en ny
     "Utslag brukt"-oppsummering per side.
  4. **Push-varsler til telefonens OS** (Web Push/VAPID) — ny
     `push_subscription`-tabell, nytt `app/push.py`
     (`send_push_to_user`, `pywebpush`), kalt fra `create_notification()`
     for ALLE fire varseltyper. Bevisst INGEN egen per-type opt-in
     (ulikt e-post-fallbacken) — å abonnere ER samtykket. VAPID-nøkler
     valgfrie (`PUSH_CONFIGURED`), samme grasiøs-degraderings-mønster som
     SMTP. `public/sw.js` fikk `push`/`notificationclick`-håndtering, ny
     `lib/push-subscribe.ts` + seksjon i `/account`
     (`PushNotificationSection`).
  **VAPID-nøkkelpar generert lokalt** (Python `cryptography`, EC P-256,
  rå base64url-kodet privat/offentlig nøkkel — samme format `pywebpush`
  og nettleserens `applicationServerKey` forventer), ALDRI vist i
  klartekst i chatten (skrevet direkte til en midlertidig fil med 600-
  rettigheter, lest inn i ekte `.env` ved utrulling, slettet fra
  scratchpad etterpå) — samme regel som alle andre hemmeligheter i
  prosjektet.
  **Scratch-verifisert grundig, 27/27 sjekker i ett delt testløp**
  (isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
  API-container med `pywebpush` installert, alle 37 migrasjoner kjørt
  friskt): flight-group happy-path + ugyldig UUID avvist + kryss-bruker-
  isolasjon (403/404, ingen lekkasje) + slått-sammen leaderboard rangerer
  korrekt på tvers av to flighter; scramble-valg PATCH lykkes + feil side
  avvist (400) + eksplisitt null nullstiller; VAPID-nøkkel eksponert +
  abonnement lagret + anonymt abonnement avvist (401) + en EKTE
  `webpush()`-utsendelse forsøkt mot en syntaktisk ugyldig test-nøkkel
  (`WebPushException` fanget og logget, in-app-varselet ble uansett
  opprettet normalt — beviser feil-isolasjonen fungerer i praksis, ikke
  bare i teorien). `test_isolation.sql` 12/12 uendret.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
  isolert scratch-backend, ekte innlogging): PWA-knappen rendret og var
  klikkbar (Chrome fyrte faktisk `beforeinstallprompt` i testøkten), ingen
  konsollfeil; hele "+ Legg til en flight til"-flyten klikket gjennom FRA
  KNAPPETRYKK TIL FERDIG RUNDE (riktig forhåndsutfylt skjema, korrekt
  `flightGroupId` i URL-en, DIREKTE DATABASE-bekreftelse at begge rundene
  delte samme gruppe-id etterpå), kombinert leaderboard-siden bekreftet
  visuelt med korrekt rangering; scramble-valget satt i veiviseren,
  bekreftet DIREKTE I DATABASEN, OG "Utslag brukt"-oppsummeringen
  bekreftet visuelt på statistikksiden; push-UI-et rendret korrekt og
  håndterte avslått tillatelse med en forklarende tekst uten konsollfeil
  (denne automatiserte nettleserøkten hadde `Notification.permission`
  forhåndssatt til "denied" av selve miljøet — en ekte innvilget
  tillatelse → ekte levert OS-varsel er derfor IKKE bevist, ærlig
  begrensning, bruker bør selv teste dette på en ekte enhet før full
  tillit). Ekte typesjekket produksjonsbuild (`docker build --target
  builder`) kompilerte rent, alle 27 ruter listet inkl. den nye
  `/my-rounds/[id]/flights`.
  **Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt (plan vist
  FØR kjøring, per CLAUDE.md sin ufravikelige regel): migrasjon 035-037
  kjørt mot ekte `teecup_db` (alle tre bekreftet med direkte spørring,
  `test_isolation.sql` fortsatt 12/12), VAPID-nøkler lagt inn i ekte
  `.env`, `docker compose up -d --build teecup_api teecup_frontend`.
  Begge containere boot-et rent, `/health`/`/dashboard` → 200,
  `GET /push/vapid-public-key` over ekte https ga korrekt offentlig
  nøkkel, `GET /rounds/{ukjent-id}/flight-group` ga korrekt
  `401 NOT_AUTHENTICATED` (ikke en rå 404 — bekrefter ruten faktisk når
  FastAPI), `teeoff.no` upåvirket.
- **Designinstruksen erstattet/formalisert + seks brukerpunkter, ALLE
  BYGGET OG LIVE (2026-07-29):** `alternativ designinstruks.md` (fra
  dagen før) erstattet med en revidert versjon fra bruker (allerede
  forankret i det eksisterende token-systemet, ikke lenger i konflikt med
  oransje/hardkodet-slate-spørsmålene) — INNHOLDET er nå slått sammen inn
  i `DESIGN_SYSTEM.md` som den gjeldende fasiten (8-punkts rutenett,
  `shadow-md shadow-black/8`, `font-normal` OK ved `text-base`+god
  kontrast, `:active`-tilstander, safe-area, 16px input, `transition-all
  duration-200`). `alternativ designinstruks.md` selv beholdt kun som
  arkiv/historikk. **Konsistens-sveip:** det gamle svake skygge-mønsteret
  (`shadow-sm shadow-black/5`) erstattet mekanisk på tvers av 20 filer
  (`sed`, verifisert 0 gjenværende treff) — IKKE en fullstendig
  strukturell gjennomgang av alle 27 skjermer, kun dette ene, trygge,
  mekaniske mønsteret.
  **Hurtighandlinger:** dashbordets tre knapper er nå `grid-cols-3` også
  på mobil (var `grid-cols-1 sm:grid-cols-3`, tok unødvendig mye plass) —
  mindre ikon/tekst/høyde for å fortsatt være lesbare i smalere kolonner.
  **Par/stroke-index manglet på rundeleaderboardet** (`/my-rounds/{id}/
  leaderboard`) — ny `HoleReferenceStrip` viser Hull/Par/Hcp ÉN gang
  (banedata, identisk for alle deltakere) rett over den rangerte listen.
  **Venner: kategorisering nå OBLIGATORISK fra vennskapet inngås**
  (presisert av bruker: "det skal ikke finnes ukategoriserte venner") —
  `POST /friends` og `POST /friends/{id}/accept` krever begge minst én
  kategori (`Field(min_length=1)`), satt AV BEGGE PARTER på hvert sitt
  naturlige tidspunkt (avsender ved sending, mottaker ved aksept) — ikke
  en frivillig senere handling. `PUT .../categories` nekter også å sette
  et tomt sett. Ny delt `CategoryPicker`-komponent (friends.tsx, alle
  forhåndsvalgt — "man må heller velge bort", presisert av bruker) brukt
  tre steder (send/godta/rediger), nekter å fjerne SISTE avkrysning.
  Selv-helbredende sikkerhetsnett for venner fra FØR denne regelen: ny
  advarselsbanner øverst i `/my-friends` ("N venner mangler kategori"),
  tvinger vedkommendes kategori-panel åpent til det er løst — bekreftet
  reelt nødvendig i produksjon (Erol hadde aldri kategorisert Tore
  Morell, sin faste matchspill-motstander — vil nå bli fanget opp og
  tvunget løst neste gang `/my-friends` besøkes).
  **Synlighetsregelen for "Venner"-runder endret fra "minst én treffende
  kategori" til "ALLE vennens kategorier må være i det synlige settet"**
  (presisert av bruker: "'ikke vise' overstyrer 'vise'") — en venn med
  ÉN ikke-valgt kategori ekskluderes nå helt, selv om en annen av
  kategoriene deres er valgt. En venn med NULL kategorier vises ALDRI
  (avklart eksplisitt med bruker via spørsmål — skal i praksis aldri
  forekomme lenger pga. regelen over, kun en igjenværende tilstand fra
  FØR den ble håndhevet). `_can_view_round`/`_friends_who_can_see_round`
  (rounds.py) omskrevet til denne AND-semantikken (fra tidligere OR).
  `new-round.tsx` sin kategori-multiselect for "Venner"-synlighet starter
  nå med ALLE forhåndsvalgt (samme "velg heller bort"-prinsipp).
  **"Match leaderboard" — undersøkt grundig, IKKE en backend-bug:**
  bekreftet direkte mot ekte `teecup_db` (kun lesing) at brukerens egen
  Tjøme-matchrunde (`d857289c-...`) hadde begge sider korrekt satt opp
  med beregnet `playing_handicap` — `format-result`-endepunktet ville
  altså allerede regnet ut riktig "1 UP (A)"-status. Det reelle hullet
  var at `FormatResultPanel` (matchstatus/skins-tavle, med hull-for-hull
  vinner/AS-visning) KUN lå under "Spillere og runde"-fanen, usynlig fra
  "Score"-fanen der scoring naturlig skjer. Fikset ved å vise samme
  komponent (ikke duplisert logikk) øverst på BEGGE faner.
  **Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API-
  container, samme mønster som hele prosjektet): full kategori-håndheving
  (422 uten kategorier ved både send/godta/rediger, 200 med), presis
  eksklusjon-overstyrer-inklusjon-test (venn med golf_friends+close_family
  ekskludert når kun golf_friends er synlig, inkludert når begge er
  synlige — bekreftet mot BÅDE rå SQL og de faktiske API-endepunktene),
  match-runde med sider satt opp fra bunnen ga korrekt "1 UP (A)".
  Deretter en FULL, ekte nettleser-gjennomgang (Chrome DevTools, mobil
  390×844): 3-kolonners hurtighandlinger, matchstatus-banner synlig på
  Score-fanen, Hull/Par/Hcp-referanserad på leaderboardet, hele venne-
  søk→kategorivelger（alle forhåndsvalgt, avkrysning fungerer)→send-
  flyten, og selv-helbredende-banner-flyten (simulerte en gammel
  ukategorisert venn direkte i databasen, bekreftet banneret dukket opp
  OG forsvant igjen etter kategorisering via UI-et).
  **Rullet ut live 2026-07-29**, ingen migrasjon (kun eksisterende felt/
  logikk endret), `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, anonymt `POST /friends` ga korrekt `401` (ikke en rå 404 —
  bekrefter ruten når FastAPI), `teeoff.no` upåvirket.

- **Match-scorekort redesignet på tvers av appen (frittstående runde +
  turnering), BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
  (2026-07-29):** brukeren delte et referansebilde av en konkurrentapp sitt
  1v1-matchscorekort (horisontalt rutenett, farget etter hvem som vant
  hvert hull, løpende "Stilling"-rad AS/X UP/X&Y, navn+HCP-banner) og ba om
  det tilsvarende for ALLE match-scorekort i TeeCup, uten direkte plagiat.
  **Bevisst IKKE en kopi:** beholdt appens egne, allerede etablerte
  fargespråk i stedet for referansens røde/blå -- frittstående runder bruker
  primær/oransje (samme "side A/side B"-konvensjon som `ResultChip`/
  `FormatResultPanel` fra tidligere runder), turnering-matcher bruker lagets
  faktiske `team.color` (samme konvensjon som resten av turnering-UI-et).
  Droppet bevisst referansens "Matchplay NET/Stableford NET"-fane (ga ikke
  entydig mening for delt-ball-formater) og "Lik/Kommentar til
  spillfeeden/Spillere"-ikonraden (en helt ny sosial funksjon, ikke en
  scorekort-redesign -- utenfor denne rundens omfang).
  **Kjernemekanisme, ny og delt idé (separat implementert i begge filer,
  ikke faktisk delt kode siden filene allerede har egne lokale typer per
  etablert konvensjon):** `computeRunning()` speiler
  `handicap_engine.py` sin `compute_match_state()`/`describe()` presist,
  men regnet ETT PREFIKS om gangen client-side (backend cacher i dag kun
  SLUTT-tilstanden) -- gir en løpende AS/X UP/dormie/X&Y-status per hull i
  stedet for kun et sluttresultat.
  **`round-scorecard.tsx`:** den generiske `ScoreBlock`-tabellen erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/
  liste) erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/
  Par/en rad per spiller-ELLER-side/Stilling), identitetsbanner (navn+HCP,
  farget dot, løpende sentral status + "Ferdig"-merke når avgjort). Fargen
  på scorecellen følger HVEM SOM VANT hullet (fylt sirkel), ikke over/under
  par -- egen semantikk fra `ScoreMark` med vilje, siden dette er en match,
  ikke en individuell runde. `ApiParticipant` fikk `playing_handicap`
  (allerede eksponert av backend, kun en frontend-typeutvidelse).
  **`session-scorecard.tsx`:** `HoleSummaryTable`/`OutcomeBadge`/
  `grossPairLabel` erstattet med `TournamentMatchGrid` (samme struktur,
  brukt som HOVEDVISNING for pågående matcher, og i `DecidedView`s "Hull
  for hull" -- én komponent, ikke to). `Unit`-typen fikk `playingHandicap`.
  **Ekte, nødvendig backend-endring:** `match_participant.playing_handicap`
  ble beregnet og lagret siden ADR-039, men var ALDRI eksponert i
  `MatchParticipantOut` (matches.py) -- lagt til i modellen og i begge
  SELECT-spørringene som populerer den (`fetch_matches` og
  `add_participant`). **Reelt funn, fikset FØR utrulling:**
  `add_participant` sin `row` hentes FØR
  `compute_and_store_side_handicaps()` kjører -- en naiv
  `MatchParticipantOut(**dict(row))` ville derfor alltid returnert
  `playing_handicap: null` for en NYLIG lagt til deltaker, selv når verdien
  faktisk ble beregnet et øyeblikk senere i samme kall. Fikset med et
  eksplisitt re-oppslag av `playing_handicap` RETT FØR responsen bygges.
  **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
  isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
  friskt, samme mønster som resten av prosjektet): en full match bygget fra
  bunnen for BEGGE surfacene (en frittstående match-runde med to sider, OG
  en full turnering-scaffold -- org/bane-import/tee/tournament/to lag/
  roster/singel-økt/match/deltakere -- bygget fra API-et for FØRSTE gang i
  et testskript denne uken, siden frittstående runder aldri har trengt det
  apparatet før). 8-9 hull registrert med et bevisst blandet vinn/tap/delt-
  mønster, bekreftet at `format-result`/`scorecard` sine per-hull
  resultater matchet forventet handicap-justert utfall.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP):
  et REELT, tidkrevende miljøproblem ble diagnostisert og løst underveis --
  React-komponentenes egne `useEffect`-datahentinger kjørte ALDRI (evig
  lastespinner på HVER side, ikke bare de nye) når dev-serveren ble besøkt
  via `127.0.0.1:3100` i stedet for `localhost:3100`; Next.js 16 sin
  `allowedDevOrigins`-beskyttelse blokkerer stille dev-ressurser (HMR-
  websocket m.m.) for det som oppfattes som et fremmed opphav, og dette
  fikk hele klient-hydreringen til å henge uten en eneste konsollfeil.
  Løst ved å konsekvent bruke `localhost` i stedet for `127.0.0.1` --
  ren miljø-lærdom for fremtidige scratch-frontend-økter, ikke en bug i
  selve appen. Etter fiksen: bekreftet BEGGE match-scorekortene visuelt,
  i BÅDE lys og mørk modus, i BÅDE pågående (løpende "2 UP"/farget
  Stilling-rad) og avgjort tilstand ("8 UP"/"Ferdig"-merke, cachet
  banner), inkl. et helt nytt 2FA-oppsett fullført på fersk konto via en
  engangs e-post-2FA-kode lest fra dev-loggen (org-eier/admin krever 2FA,
  ADR-021) for å nå frem til turnering-siden. Ingen konsollfeil i noen av
  rundene.
  **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
  Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  → 200 upåvirket.

- **Match-scorekort-redesignet også på selve LIVE scoringssiden
  (`/my-rounds/{id}` sin Score-fane), BYGGET, GRUNDIG SCRATCH-/
  BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag rett etter forrige
  punkt:** brukeren viste tre skjermbilder (referansen på nytt, samt et
  mobil- OG et PC-skjermbilde av "hvordan du har løst det nå") og spurte
  direkte hvorfor det ikke så riktig ut ennå. **Presis diagnose FØR noe ble
  bygget:** PC-skjermbildets URL (`teecup.teeoff.no/my-rounds/{id}`, uten
  `/scorecard`) avslørte at forrige runde kun traff `round-scorecard.tsx`
  (en egen, separat LESE-visning) og `session-scorecard.tsx` (turnering) --
  aldri `round-detail.tsx` sin egen, ELDRE `FormatResultPanel`+
  `ScorecardGrid`/`SideScorecardGrid`, som er det brukeren faktisk ser og
  bruker under selve scoringen. `FormatResultPanel` viste dessuten en
  reell, synlig svakhet: falt tilbake til det generiske "Side B" i stedet
  for spillerens faktiske navn når siden ikke hadde et eget satt navn.
  **Bygget:** `ApiFormatResult` utvidet med samme `holes`-per-hull-
  oppløsning som round-scorecard.tsx allerede har (ingen backend-endring
  -- feltet fantes allerede i API-svaret, bare ikke lest her). Selve
  fetch-en LØFTET fra `FormatResultPanel` opp til hovedkomponenten (`Round
  Detail`) som ny delt `formatResult`-state, siden BÅDE banneret OG de to
  scorekort-gridene nå trenger den samme dataen (én henting, ikke to).
  `FormatResultPanel` gjort om til en ren presentasjonskomponent (tar
  `result` som prop) med et nytt identitetsbanner (navn+HCP, farget prikk,
  stor sentrert løpende status, "Ferdig"-merke) -- samme visuelle språk
  som `MatchScorecardGrid`/`TournamentMatchGrid` fra forrige runde, egen
  lokal kopi per prosjektets "ett sted, én fil"-konvensjon. Den tidligere
  hull-for-hull-sirkel-listen under banneret FJERNET (overflødig nå som
  selve gridet under viser dette per hull).
  `ScorecardGrid` (individuell-ball: match/fourball) og `SideScorecardGrid`
  (delt-ball: foursome/greensome/scramble) fikk begge: en farget prikk ved
  siden av spiller-/side-navnet (grønn=side A, oransje=side B), en ny
  `MatchScorecardCell` som farger scorecellen etter HVEM SOM VANT hullet
  (fylt sirkel) i stedet for over/under par -- men KUN når formatet
  faktisk er to-sidet (`isTwoSided`-prop, gate på `TWO_SIDED_FORMATS`) --
  slagspill og skins beholder uendret over/under-par-fargelegging siden de
  ikke har noe "vant hullet"-konsept. Fourball sin ikke-tellende
  partner-score tones ned (samme "laveste netto teller"-nedtoning som
  round-scorecard.tsx). Ny "Stilling"-rad nederst i BEGGE grid, regnet med
  en lokal `matchStillingByHole()` (samme prefiks-vise
  `compute_match_state()`-speiling som forrige rundes `computeRunning()`,
  egen kopi her siden denne trenger å være justert til `holeOrder`, ikke
  bare en flat liste). Alt dette er BEVISST lagt OPPÅ den eksisterende
  klikk-for-å-registrere-interaksjonen (ScoringWizard/SideScoreWizard)
  uendret -- ingen endring i selve registreringsflyten, kun presentasjonen
  av allerede registrerte hull.
  **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
  isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
  friskt): tre nye runder bygget fra bunnen via API for å dekke alle tre
  strukturelle grenene -- en MATCH-runde (Erol vs. en gjest, ulik HCP),
  en FOURBALL-runde (2v2, individuell netto, bevisst konstruert med et
  hull der "feil" partner sin lavere brutto ikke telte pga. handicap-
  justering), og en FOURSOME-runde (delt ball). Ekte typesjekket
  produksjonsbuild kompilerte rent.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
  samme `localhost`-ikke-`127.0.0.1`-lærdom fra forrige runde anvendt fra
  start denne gangen): alle tre rundene bekreftet visuelt -- identitets-
  banneret viste ekte spillernavn+HCP (ikke lenger "Side B"), gridcellene
  fargela nøyaktig riktig hull som vunnet (kryssjekket presist mot de
  faktiske innsendte tallene og aria-labelene, inkl. et hull der en
  tilsynelatende brutto-uavgjort (4-4) faktisk ble avgjort på netto pga.
  et stort HCP-gap -- bekreftet korrekt, ikke en bug), fourball sin
  nedtonede ikke-tellende partner-celle bekreftet nøyaktig på det
  konstruerte hullet, Stilling-raden fulgte riktig gjennom AS/1 UP/2 UP-
  mønsteret for alle tre rundene. Bekreftet at et klikk på en scorecelle
  FORTSATT åpner riktig veiviser (`ScoringWizard`/`SideScoreWizard`)
  uendret, i begge grid-typer. Kjørte i tillegg en ren REGRESJONSSJEKK
  mot en vanlig `stroke`-formatert runde -- bekreftet ingen Stilling-rad,
  ingen fargede prikker, ingen bytte av cellespråk (uendret over/under-
  par-styling som før). Ingen konsollfeil i noen av rundene, i verken lys
  eller mørk modus.
  **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (ba om fiksen
  rett etter diagnosen): ingen migrasjon, `docker compose up -d --build
  teecup_api teecup_frontend`. Begge containere boot-et rent,
  `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. **Alle tre stedene
  et match-scorekort vises i appen (frittstående runde sin live Score-
  fane, frittstående runde sin egen lesevisning, turnering-match) bruker
  nå samme konsistente visuelle språk.**

- **Dashbordets rundeboks: viewer-relativt matchresultat + netto til-par,
  pluss mottatte slag vist FØR hullet fylles ut, BYGGET, SCRATCH-/
  BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag:** brukeren ba om to
  ting samtidig -- at rundeboksen (`round-card.tsx`, brukt av dashbordet,
  "Egne runder" og "Spilte baner") viser spillerens eget resultat i
  matchen/runden ("1 UP"/"+4 netto"/"-2 brutto" osv.), og at det er
  tydelig hvor mange slag en spiller MOTTAR på et hull -- kommunisert på
  scorekortet, allerede FØR hullet er fylt ut (ikke bare etterpå som
  netto-tallet i dag). Fire load-bærende avklaringer bekreftet eksplisitt
  (AskUserQuestion, alle anbefalte valg): vis resultat BÅDE for pågående
  og fullførte runder (ikke bare fullførte som før), samme "UP"/"AS"/
  "X&Y"-golfkonvensjon som scorekortets Stilling-rad (ikke et eget
  dagligdags språk kun for dashbordkortet), vis BÅDE netto og brutto
  til-par for slagspill når HCP spores, og vis slag-indikatoren INNI selve
  den tomme score-ruten (ikke en egen ny rad).
  **Backend (`app/routers/rounds.py`):** `RoundOut`/`_load_round_out`
  (delt av BÅDE `GET /rounds` (liste) OG `GET /rounds/{id}`, ingen egen
  kode for listen) fikk tre nye felt: `my_net_score_to_par` (kun
  `play_format=="stroke"` OG `course_handicap_snapshot` satt, samme
  `allocate_strokes_by_index`-algoritme som list_holes/update_hole
  allerede bruker -- ingen ny utregningsmåte), og `my_match_status`/
  `my_match_lead` (to-sidede formater KUN, ALLTID viewer-relativt --
  ny `_viewer_match_status_text()`-formatter tar en fortegns-FLIPPET
  `lead` fra `_build_format_result` sitt allerede eksisterende, testede
  resultat -- gjenbrukt UENDRET, ikke en ny match-motor -- slik at
  positivt alltid betyr "spørrende bruker leder", uansett hvilken side de
  faktisk sitter på). Et avgjort resultat vises som "Vunnet 2&1"/"Tapt
  2&1" (eksplisitt prefiks, siden "2&1" alene ikke lenger sier hvem sin
  side det gjaldt når visningen ikke er side-A/B-basert).
  **Frontend (`round-card.tsx`):** ny `Round.matchStatus`/`matchLead`/
  `netToPar`. To-sidede formater viser ett "Resultat"-felt med
  matchstatusen (farget grønn ved ledelse, oransje ved etterslep, samme
  "form+farge"-språk som resten av appen -- ALDRI backend sin bokstavelige
  side A/B). Slagspill beholder "Resultat"+"Til par" (brutto) og fikk et
  nytt "Netto"-felt ved siden av, kun når tilgjengelig. Resultatraden er
  nå IKKE lenger gatet til kun fullførte runder -- vises også mens runden
  pågår. Alle tre kallstedene (`dashboard.tsx`, `own-rounds.tsx`,
  `course-rounds.tsx`) oppdatert til å lese og videresende de tre nye
  API-feltene, samme mønster i alle tre (ingen egen logikk per fil).
  **Slag-indikator FØR utfylling (`round-detail.tsx`):** `ScorecardGrid`
  (individuell-ball) og `SideScorecardGrid` (delt-ball) viser nå en liten
  blå "−N"-tekst inni den tomme score-ruten når spilleren/siden faktisk
  mottar minst ett slag på det hullet -- samme `strokes_received`-verdi
  som allerede ble hentet (kun aldri vist før noe var registrert). Egen
  farge (`text-info`, den etablerte blåtonen) for å skille denne FØR-
  visningen tydelig fra den eksisterende grå netto-visningen ETTER
  utfylling. **Reelt, tidligere ubrukt hull funnet underveis:**
  `SideScorecardGrid` sin egen frontend-type `ApiSideHole` manglet
  `strokes_received` helt (backend har alltid eksponert feltet i
  `RoundSideHoleOut`, bare aldri lest her) -- lagt til.
  **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
  isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
  friskt): en slagspill-runde ga korrekt brutto til-par 4 / netto til-par
  2 (både i enkelt-GET og i listen), en matchrunde der spilleren vant
  begge registrerte hull ga korrekt `"2 UP"`/`lead=2`. Deretter en FULL,
  ekte nettleser-gjennomgang (samme `localhost`-lærdom anvendt fra start):
  rundeboksen viste "2 UP" i grønt for en pågående matchrunde og "Resultat
  17 / Til par +4 / Netto +2" (oransje) for en pågående slagspill-runde,
  i BÅDE lys og mørk modus; scorekortet viste "−1"-hint i blått på
  nøyaktig de tomme rutene der spilleren/siden faktisk mottar et slag,
  bekreftet i BÅDE individuell-ball- (match) og delt-ball-grid (foursome).
  Ingen konsollfeil i noen av rundene.
  **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
  Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket.

- **Match-identitetsbanneret: territorium-bar i stedet for flat 50/50-boks,
  BYGGET, GRUNDIG BROWSERVERIFISERT (inkl. en reell overlapp-bug funnet OG
  fikset) OG LIVE (2026-07-29), samme dag:** brukeren lastet opp to bilder
  av dagens banner (samme identitetskort som ble bygget dagen før -- navn+
  HCP i hver ende, en sentrert statusboks) og en "VELDIG DÅRLIG, kun
  illustrerende" skisse av eget forslag: den ledende sidens halvdel bør
  markeres OVER på motstanderens halvdel (ikke en statisk 50/50-boks), og
  ved AS skal begge sider markeres likt (nøytralt).
  **Design:** ny `leadZoneFraction(lead, totalHoles)` -- `0.5 + (lead /
  totalHoles) * 0.5`, klippet til `[0.18, 0.82]` for å alltid holde begge
  navn lesbare selv ved en ekstrem ledelse. Dominant side får en solid,
  mettet farge (`bg-primary`/`bg-brand-orange`, hvit/kontrastfarget tekst
  via allerede eksisterende `--primary-foreground`/`--brand-orange-
  foreground`-tokens) og strekker seg proporsjonalt forbi midtlinjen;
  underlegne side beholder samme fargetone som en svak, lys tint
  (`/12`-opacity) med vanlig temafarget tekst. Ved AS (eller ingen hull
  spilt ennå): begge soner nøyaktig 50/50 og nøytralt `bg-muted` -- ingen
  side ser ut til å lede. Egen lokal kopi i alle tre filer der et match-
  identitetsbanner finnes (samme "én fil, én kopi"-konvensjon som
  MatchScorecardCell-arbeidet dagen før): `round-detail.tsx` sin
  `FormatResultPanel` (primary/brand-orange-tokens), `round-scorecard.tsx`
  sin nye delte `LeadBar`+`LeadZone` (samme tokens, portert fra den
  tidligere `IdentitySide`+`CenterBadge`-duoen), `session-scorecard.tsx`
  sin `IdentityBlock` (FAKTISK lagets `team.color`-hex i stedet for faste
  tokens -- dominant sone `backgroundColor: color` + hvit tekst, svak sone
  `color-mix(in srgb, ${color} 15%, transparent)`, samme kontrastvalg som
  den eksisterende `SegmentedBar` i tournament-leaderboard.tsx).
  **Reell overlapp-bug funnet OG fikset UNDER selve browserverifiseringen,
  ikke antatt riktig fra kodegjennomgang alene:** første forsøk brukte en
  `position:absolute`-sentrert statusboks OPPÅ to `width:X%`-delte soner --
  et ekte skjermbilde på en smal (390px) mobil-viewport viste at et langt
  navn ("Erol Haagenrud") ble delvis SKJULT bak statusboksen ("l Haagenrud"
  vist i stedet, ikke en ellipse-trunkering, et reelt visuelt overlapp).
  Første fiksforsøk (CSS Grid med `minmax(0,X fr) auto minmax(0,Y fr)` i
  stedet for absolutt posisjonering) løste IKKE problemet alene -- fortsatt
  samme overlapp ved re-test. Rot-årsaken var dypere: identitetsboksen
  brukte `items-start`/`items-end` på sin YTRE flex-kolonne, som sizer
  boksen etter INNHOLDETS egen bredde (shrink-to-fit) i stedet for sonens
  faktiske tildelte bredde -- innholdet kunne dermed visuelt strekke seg
  utover sin egen sone og inn i midt-kolonnen uansett hvor korrekt selve
  grid-/bredde-fordelingen var. Fikset ved at boksen alltid STREKKER SEG
  (fjernet items-start/items-end helt), med venstre/høyre-justering i
  stedet løst via `justify-start`/`justify-end` på navne-raden og
  `text-left`/`text-right` på HCP-teksten -- INNI en boks som nå alltid har
  sonens fulle, korrekte bredde, slik at `truncate` faktisk virker presist.
  **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
  `teecup_scratch`-database + isolert scratch-MinIO + engangs API-
  container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP, 390×
  844 mobil-viewport): bygget en ekte match-runde fra bunnen via API (egen-
  definert 18-hulls bane, Erol HCP 10 vs. gjest "Tore Morell" HCP 54,
  samme HCP-mønster som brukerens skjermbilde) og testet re-render etter
  HVER kodeendring, ikke bare én gang til slutt -- fanget nettopp DERFOR
  både at CSS Grid-fiksen alene ikke var nok, og at stretch-fiksen
  faktisk løste det. Bekreftet: normal ledelse ("1 UP", dominant sone
  moderat bredere), en EKSTREM ledelse (avgjort "9&7", dominant sone
  nesten fyller hele baren, underlegne navn korrekt trunkert "T…"/"HCP…"
  i stedet for å overlappe), AS/ingen-hull-spilt (nøytral 50/50, `bg-
  muted`), BÅDE lys og mørk modus (kontrast bekreftet i begge), og samme
  fiks bekreftet å fungere identisk i `round-scorecard.tsx` sin
  `MatchScorecardGrid`-visning (samme runde, `/my-rounds/{id}/scorecard`).
  `session-scorecard.tsx` sin tournament-variant IKKE egen nettleser-
  testet denne runden (identisk kodemønster, kun `team.color`-hex i stedet
  for faste tokens -- lavere risiko, men flagget ærlig som ikke eget
  bevist). Ekte typesjekket produksjonsbuild kjørt (to runder -- én etter
  første, mislykkede CSS Grid-only-forsøk, én etter den faktiske stretch-
  fiksen), alle 24 ruter listet begge ganger.
  **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
  migrasjon (ren frontend), `docker compose up -d --build
  teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
  ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
  `/dashboard` → 200, `teeoff.no` upåvirket.

- **Territorium-bar-språket fullført på de to siste "Matchstatus"-boksene,
  BYGGET, BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag -- brukeren ba
  eksplisitt om "visuell konsistens ferdig helt ut" etter at jeg flagget
  disse to som gjenstående:** `round-leaderboard.tsx` sin
  `MatchStatusSection` (den innloggede rundens leaderboard-side) og
  `watch-round.tsx` sin `MatchStatus` (den offentlige "Følger live"-siden
  for en delt/synlig runde, ADR-036 fase 2) hadde begge fortsatt den gamle
  flate boksen (kun stor tekst, ingen fargesone). Disse to har INGEN
  navn å vise (deltakernavnene vises allerede andre steder på samme side)
  -- lagt til KUN de to fargesonene + den sentrerte statuspillen (samme
  `leadZoneFraction`+CSS-Grid-mønster som identitetsbanneret dagen før,
  egen lokal kopi i hver fil), ikke selve `LeadZone`-identitetsboksen.
  Begge filenes lokale `ApiFormatResult`-type manglet `match_lead` helt
  (aldri lest her før, selv om backend alltid har eksponert det) -- lagt
  til i begge.
  **Verifisert i en isolert scratch-nettleserøkt** (fersk `teecup_scratch`-
  database + isolert scratch-MinIO + engangs API-container, ekte `next
  dev`, Chrome DevTools MCP, 390×844 mobil-viewport): bygget en ekte
  OFFENTLIG (`visibility_mode:"public"`) match-runde fra bunnen via API,
  bekreftet BEGGE sider (innlogget leaderboard OG anonym `/watch/{id}`)
  viser identisk, korrekt fargesatt "1 UP (Side B)"-tilstand med riktig
  dominant/lys sone -- OG en egen AS/0-hull-spilt-test (nøytral 50/50,
  ingen farge). Begge bekreftet i BÅDE lys og mørk modus. Ingen
  konsollfeil (kun en godartet, urelatert PWA-install-loggmelding). Ekte
  typesjekket produksjonsbuild kjørt og bekreftet.
  **Rullet ut live 2026-07-29**, ingen migrasjon (ren frontend), `docker
  compose up -d --build teecup_frontend` (gjenskapte også `teecup_api`
  som vanlig bivirkning, ingen backend-kode rørt). Begge containere
  boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
  **Territorium-bar-språket er dermed konsistent på ALLE fem stedene en
  matchstatus vises i appen** (tre fulle identitetsbannere + disse to
  navnløse variantene) -- listevisningene (blind draw-reveal, offentlig
  turnering-live) beholder bevisst sin egen, ulike kort-per-match-stil
  (farget topplinje + statuschip), siden de viser MANGE matcher samtidig
  og ikke er en fokusert enkelt-match-visning.

- **Stableford som ekte spilleform + "plukket opp"-tilstand for frittstående
  runder, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT (inkl. TO reelle bugs
  funnet OG fikset UNDER browserverifiseringen) OG LIVE (2026-07-29), samme
  dag:** brukeren ba om å ta fatt på det siste, klart avgrensede hullet fra
  ADR-038-gjennomgangen -- Stableford-POENGENE var allerede beregnet og vist
  flere steder (round-detail.tsx/round-scorecard.tsx/round-leaderboard.tsx
  sin `stablefordPoints()`/`total_points`, bygget 2026-07-25/26), men to
  ting manglet reelt: (1) `'stableford'` fantes ikke som et faktisk
  selvdeklarert `play_format` (kun "stroke"/"match" fantes som individuelle
  format-etiketter), (2) ingen vei til å registrere at en spiller plukket
  opp ballen fordi hullet uansett var klart 0 poeng.
  **Design, ny migrasjon `038_stableford_and_pickup.sql`:** `round_hole.
  picked_up boolean DEFAULT false`, `'stableford'` lagt til
  `round_play_format_check`. "Plukket opp" lagres IKKE som en NULL-score --
  serveren skriver eksplisitt Net Double Bogey (par + 2 + mottatte slag,
  `handicap_engine.py` sin allerede eksisterende og testede `max_hole_
  score_for_handicap()`, Rule 3.1b) som selve `score`-verdien når
  `picked_up=true` sendes til `PATCH .../holes/{n}` -- dette gir automatisk
  presis 0 Stableford-poeng (samme formel som ellers, ingen spesialkoding)
  OG et korrekt AGS-bidrag for faktisk-HCP (ADR-038) helt uendret -- INGEN
  ny motorlogikk trengs, kun ett nytt felt for VISNING ("PU"-merke i stedet
  for det underliggende tallet, samme "form+farge, aldri farge alene"-språk
  som resten av golfscore-visningen). `HoleUpdate` beregner strokes_received
  for AKKURAT det aktuelle hullet FØR selve UPDATE-en (omstrukturert fra
  tidligere "beregn etterpå"-rekkefølge) for å kunne skrive riktig NDB-verdi
  i samme kall. Avvist tydelig (400 `VALIDATION_FAILED`) hvis deltakeren
  ikke har noen beregnet course handicap ennå.
  **To reelle bugs funnet OG fikset UNDER selve browserverifiseringen, ikke
  antatt riktig fra kodegjennomgang alene** -- begge samme underliggende
  mønster: eksisterende kode som spesialsjekket `play_format === "stroke"`
  (for å bety "individuelt, ikke to-sidet format") måtte utvides til
  eksplisitt å ekskludere `"stableford"` også, ellers falt det nye formatet
  feilaktig gjennom til den TO-SIDEDE grenen:
  1. `new-round.tsx` sin `needsSidesSetup = playFormat !== "stroke" &&
     playFormat !== "skins"` -- et ekte skjermbilde viste "Du setter opp
     sidene..."-teksten under Stableford-valget, til tross for at formatet
     aldri bruker sider. Rettet ved å legge til `&& playFormat !==
     "stableford"`.
  2. **Alvorligere:** `round-detail.tsx` sin `FormatResultPanel` (`if
     (round.play_format === "stroke" || !result) return null`) -- et ekte
     skjermbilde av en fersk Stableford-runde viste en fullstendig
     meningsløs "Side A"/"Side B"-territorium-bar (samme komponent bygget
     tidligere samme dag for ekte to-sidede formater) med "Ingen hull
     spilt ennå", siden backend sin `_build_format_result()` returnerer en
     harmløs tom `ready:true`-respons for ethvert format utenfor
     `_TWO_SIDED_FORMATS`/`"skins"` (inkl. det nye "stableford"), som
     frontend-sjekken ikke fanget opp. Rettet samme sted (lagt til
     `round.play_format === "stableford"` i den samme betingelsen), pluss
     den tilhørende `useEffect` som utløser selve HTTP-kallet (unødvendig
     nettverkskall for Stableford, samme fiks som for "stroke").
  **Systematisk oppfølging etter de to funnene, IKKE bare disse to
  stedene:** grep'et gjennom HELE frontend-kodebasen etter samme
  `"stroke"`-spesialsjekk-mønster og fant tre til ekte hull i
  `watch-round.tsx` (den offentlige "Følger live"-siden) -- leaderboard-
  henting, `StrokeLeaderboard`-visning, og "ingen live-visning
  tilgjengelig"-fallback-meldingen ville alle feilaktig behandlet en
  offentlig Stableford-runde som enten to-sidet eller "ukjent format".
  Alle tre rettet samme runde, FØR de kunne bli oppdaget i produksjon.
  Backend-siden av samme klasse spesialsjekk (`app/routers/rounds.py`)
  var allerede korrekt fra første forsøk (`_format_setup_status`,
  `my_net_score_to_par`-gaten, `RoundCreate`/`RoundUpdate` sine
  `Literal`-typer) -- kun frontend hadde det gjentatte hullet.
  `EditRoundPanel` sin spilleform-brytar (rediger-runde-panelet) utvidet
  fra to til tre valg (Slagspill/Stableford/Matchspill), samme mønster
  som `new-round.tsx`.
  **Scratch-verifisert grundig i to lag** (isolert `teecup_app_scratch`-
  rolle + isolert scratch-MinIO + engangs API-container, alle 38
  migrasjoner kjørt friskt, samme mønster som hele prosjektet): et
  hånd-utregnet scenario (course handicap 18, slope 113/rating lik par,
  1 slag mottatt per hull) bekreftet "plukket opp" på et par-4-hull med
  1 mottatt slag ga eksakt score 7 (4+2+1, identisk med håndregning),
  leaderboardets `total_points` stemte eksakt (0 for plukket-opp-hullet +
  3 for et normalt netto-birdie-hull = 3), reversering (fjern plukket-
  opp, sett ekte score, plukk opp igjen) fungerte, avvist tydelig for en
  deltaker uten HCP (400), `_format_setup_status` bekreftet IKKE lenger
  krasjer (KeyError) for stableford (`setup_complete:true` uten
  sider), `RoundUpdate.play_format` PATCH til "stableford" i etterkant
  bekreftet, runde fullført uten krasj.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
  samme `localhost`-ikke-`127.0.0.1`-lærdom fra tidligere runder): hele
  opprett-runde-flyten klikket gjennom i UI-et (Stableford-valg, riktig
  beskrivelsestekst, INGEN sider-notis etter fiksen), "Plukket opp (0
  poeng)"-knappen i selve `ScoringWizard` bekreftet å auto-hoppe videre
  akkurat som et vanlig slagtall, og "PU"-merket bekreftet konsekvent på
  ALLE FIRE stedene det vises: scorekort-gridet på selve Score-fanen,
  det post-runde `round-scorecard.tsx`-scorekortet (med korrekt "Poeng
  0"/netto 6), `round-leaderboard.tsx` (både i hull-stripen og i
  Poeng-modus, total "3" korrekt), og "Så langt i runden"-tabellen. En
  separat REGRESJONSSJEKK av et eksisterende `match`-format (to sider,
  territorium-bar, "1 UP") bekreftet UENDRET oppførsel -- de to bug-
  fiksene rørte kun stableford-grenen. Ingen konsollfeil i noen av
  rundene.
  **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist
  FØR migrasjonen, per CLAUDE.md sin ufravikelige regel): migrasjon 038
  kjørt mot ekte `teecup_db` (kolonne + utvidet CHECK-constraint
  bekreftet, `test_isolation.sql` fortsatt 12/12), deretter `docker
  compose up -d --build teecup_api teecup_frontend`. Begge containere
  boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.

- **Scramble/greensome: "utslag brukt"-statistikk NÅ OGSÅ for org-scopede
  turneringer, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
  (2026-07-29), samme dag:** direkte oppfølging av brukerens eget forslag
  fra "hva nå?"-runden rett etter Stableford-arbeidet -- funksjonen ble
  bevisst holdt utenfor migrasjon 036 (frittstående runder, 2026-07-25),
  se dens egen kommentar; dette er den avgrensede oppfølgeren.
  **Design, ny migrasjon `039_hole_score_selected_participant.sql`:**
  `hole_score.selected_participant_id uuid` (nullable), composite FK
  `(organization_id, selected_participant_id) REFERENCES match_
  participant(organization_id, id) ON DELETE SET NULL` -- samme mønster
  som `hole_score.match_participant_id` sin egen FK, men SET NULL (ikke
  CASCADE): fjernes en deltaker fra matchen (mulig FØR lås) skal ikke
  slette allerede registrerte hull-scorer, kun nullstille selve valget.
  Kun meningsfullt på en DELT-BALL-hull-rad (`match_participant_id IS
  NULL`) -- håndhevet i app-laget (`submit_hole_score`), ikke en CHECK,
  samme mønster som migrasjon 036.
  **Backend (`app/routers/scoring.py`):** `HoleScoreCreate`/`HoleScoreOut`
  fikk `selected_participant_id`. Validering: avvist (400
  `WRONG_PARTICIPANT_MODE`) for individuell ball (`match_participant_id`
  satt) -- gir ingen mening der. For delt ball: validert å tilhøre
  matchen OG samme `team_side` som selve scoren (400 `VALIDATION_FAILED`
  ellers), speiler `round.py` sin identiske sjekk for frittstående runder.
  Begge INSERT/UPSERT-setningene i `submit_hole_score` oppdatert (delt-
  ball-grenen skriver/oppdaterer feltet, individuell-ball-grenen inkluderer
  det aldri i det hele tatt -- alltid NULL der). `get_scorecard` sin
  `stroke_entries`-SELECT utvidet -- feltet flyter automatisk gjennom
  siden den allerede delte `HoleScoreOut`-modellen gjenbrukes.
  **Frontend (`session-scorecard.tsx`):** ny "Hvem sitt utslag ble
  brukt?"-knapperad (samme mønster som round-detail.tsx sin
  `SideScoreWizard` fikk 2025-07-25) under `StrokePicker` for delt-ball-
  enheter -- vises KUN når et slagtall allerede er registrert for hullet
  (ulikt frittstående runders `round_hole`, som alltid forhåndsopprettes
  med nullbar score, krever `hole_score`-raden faktisk å EKSISTERE først,
  siden `gross_strokes` er `NOT NULL` i skjemaet -- en reell strukturell
  forskjell fra migrasjon 036s mønster, løst ved å gjenbruke samme
  `submitStroke`-funksjon med et nytt valgfritt fjerde argument som
  enten beholder gjeldende valg (utelatt) eller setter et nytt (inkl.
  eksplisitt `null` for å fjerne). Ny `SelectedDriverSummary`-komponent
  (samme presentasjon som round-stats.tsx sin tilsvarende, tilpasset
  denne sidens `teams`/`match.participants`/`scorecard.stroke_entries`-
  datform) lagt inn i den eksisterende "Vis full oversikt"-seksjonen.
  **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
  isolert scratch-MinIO + engangs API-container, alle 39 migrasjoner
  kjørt friskt): en FULL org-turnering-scaffold bygget fra bunnen via API
  for FØRSTE gang i en test for akkurat denne funksjonen (org/egendefinert
  bane/18 hull/tee/turnering/to lag/fire spillere/roster/foursome-økt+
  match/fire deltakere/lås) -- 51/51 sjekker: skriving uten valg, skriving
  med valg (gross_strokes bevart), feil-side-valg avvist (400
  VALIDATION_FAILED), individuell-ball+valg avvist (400
  WRONG_PARTICIPANT_MODE), persistens bekreftet via `GET .../scorecard`,
  korrekt opptelling (2 vs. 1 utslag), eksplisitt `null` fjerner valget.
  `test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket
  produksjonsbuild kjørt og bekreftet.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
  isolert scratch-backend, inkl. et ekte 2FA-oppsett for en fersk
  organisasjonseier siden ADR-021 krever det): naviger til foursome-
  matchens scorekort, bekreftet at allerede-registrerte valg (satt via
  API) vises korrekt som trykte knapper for RIKTIG spiller på RIKTIG
  hull -- OG et ekte klikk i UI-et som byttet valgt spiller for et hull,
  bekreftet persistert direkte i databasen etterpå (ikke bare at UI-et
  så riktig ut). "Vis full oversikt" sin nye "Utslag brukt"-seksjon
  bekreftet med nøyaktig riktig opptelling for begge lag (2/1 og 1/0,
  inkl. "Registrert på N av M spilte hull"-teksten). Ingen konsollfeil.
  **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist
  FØR migrasjonen): migrasjon 039 kjørt mot ekte `teecup_db` (kolonne +
  FK-constraint bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
  `docker compose up -d --build teecup_api teecup_frontend`. Begge
  containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket. **"Utslag brukt"-statistikken finnes dermed nå for BEGGE
  domener** (frittstående runder siden 2026-07-25, org-scopede
  turneringer siden i dag) -- se ARCHITECTURE_DECISIONS.md sitt åpne
  spørsmål 4 (Scramble-grensesnitt), som dermed er helt avsluttet.

- **ADR-037 (individuelle/flerrunde-turneringer): migrasjon + motor + API
  BYGGET OG SCRATCH-VERIFISERT, IKKE ENNÅ RULLET UT (2026-07-30):** bruker
  ba om å gå videre med anbefalingen fra en dypdykk-gjennomgang av
  .md-filene -- det eneste gjenværende punktet med en ferdig, load-bærende
  strukturbeslutning (ADR-037, 2026-07-26) uten kode. Bygget i tre lag,
  samme "test i isolasjon FØR resten"-rekkefølge som ADR-005/033/038/039.
  **Migrasjon `040_individual_tournaments.sql`:** `tournament.format_type`
  (`team`/`individual`, default `team` -- alle eksisterende rader uendret)
  + `tournament.scoring_method` (`stroke_gross`/`stroke_net`/`stableford`,
  nullable). Fem nye, RLS-beskyttede tabeller: `tournament_round` (samme
  rolle som `session`, peker til org-ens EGEN bane -- ingen snapshot,
  ulikt ADR-033), `tournament_participant` (samme rolle som `team_roster`,
  fryser `handicap_index_snapshot`), `tournament_round_participant`
  (tee + cachet course/playing-handicap PER runde), `tournament_round_hole`
  (rå brutto slag, kilde-sannhet), `tournament_round_score` (ferdig
  utregnet brutto/netto/Stableford-total PER deltaker PER runde, cachet --
  samme mønster som `match.status_text`/`points_side_a/b`; sammenlagt over
  flere runder summeres VED LESING i leaderboardet, ingen egen tredje
  cache-tabell, Beslutning C).
  **Scratch-verifisert alene FØR API-et ble bygget:** alle 40 migrasjoner
  kjørte rent i rekkefølge, `test_isolation.sql` fortsatt 12/12, 10 egne
  funksjonelle sjekker (kryss-org-isolasjon på BÅDE lesing og skriving,
  `format_type`-CHECK+default, unik-constraints, `gross_strokes`-CHECK,
  kaskade-sletting).
  **Motor** (`handicap_engine.py`, ny seksjon rett etter
  `allocate_over_played_holes`): `stroke_play_gross_total`/
  `stroke_play_net_total`/`stableford_points_for_hole`/`stableford_total`
  -- rene funksjoner, ingen ny slagfordeling (bruker samme
  `allocate_over_played_holes`-output som resten av motoren). 8 nye
  tester, alle 63 (55 eksisterende + 8 nye) bestått i
  `test_handicap_engine.py`.
  **API** (nytt `app/routers/individual_tournaments.py`, registrert i
  `main.py`): CRUD for runder/turnering-deltakere/rundedeltakere (med
  handicap-beregning ved tilføyelse -- v1 har INGEN allowance-prosent for
  individuelle turneringer, `playing_handicap` er alltid identisk med
  avrundet `course_handicap`, ulikt lagturneringenes komplekse relative
  `AllowanceStrategy`-familie, som er bygget for et to-siders oppgjør og
  ikke gir mening for et flatt felt), hull-for-hull-scoring
  (`PATCH .../holes/{n}`, cacher totalen på nytt ved hver innsending),
  leaderboard som summerer på tvers av runder ved lesing.
  `tournaments.py` sin `TournamentCreate`/`TournamentUpdate`/`Tournament`
  utvidet med `format_type`/`scoring_method` (samme `exclude_unset`-PATCH-
  mønster som resten av filen).
  **To reelle funn, begge fikset FØR utrulling:**
  1. Leaderboard-endepunktet kunne IKKE hete
     `/orgs/{id}/tournaments/{id}/leaderboard` -- den stien er allerede
     `tournaments.py` sitt LAG-leaderboard, og siden `tournaments.router`
     registreres FØR `individual_tournaments.router` i `main.py`, ville
     det stille skygget for det nye endepunktet (funnet presist ved en
     ekte API-test som krasjet på feil responsform). Løst med et eget
     navn, `/individual-leaderboard` -- samme kollisjonsklasse som
     `/rounds` vs. `/my-rounds` tidligere, denne gangen unngått fra start.
  2. `list_rounds`/`list_tournament_participants` manglet en eksplisitt
     "finnes turneringen"-sjekk (samme mønster `list_sessions` allerede
     har) -- ga stille en tom liste under RLS for en fremmed
     turnering-id i stedet for 404 (ingen sikkerhetslekkasje, RLS
     blokkerte fortsatt all faktisk data, men inkonsistent med resten av
     API-et). Rettet til å matche `list_sessions` presist.
  **Scratch-API-verifisert grundig, 52/52 sjekker** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
  API-container, ekte HTTP via `requests`, ekte magic-link-innlogging via
  dev-log): full happy path fra org til leaderboard, hånd-utregnet netto-
  kryssjekk for to spillere (course handicap 11/20, stemte eksakt),
  `front_9`-runde avviser hull utenfor omfang, kryss-org-isolasjon
  (bekreftet BÅDE lesing og en FK-basert skrivesperre), slette-vern (runde
  MED deltakere avvist 409, turnering-deltaker fortsatt referert av en
  rundedeltaker avvist 400 RESTRICT-FK, løst opp igjen etter fjerning).
  `test_isolation.sql` 12/12 uendret (additiv migrasjon).
  **IKKE bygget i denne runden, bevisst neste steg:** frontend (ingen
  skjerm ennå -- samme lagdelings-rekkefølge som ADR-033/038/039).
  Autorisasjon er bredt org-medlemskap for ALT i denne runden, inkl. selve
  scoreregistreringen -- en senere innstramming (analogt ADR-023s
  kaptein-only) er en naturlig, separat oppfølger. De fem konkrete
  formatene (Københavner m.fl.) og Order of Merit fortsatt ikke designet.
  **Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt
  (plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige regel):
  migrasjon 040 kjørt mot ekte `teecup_db` (alle fem nye tabeller + de to
  nye `tournament`-kolonnene bekreftet, eksisterende turnering fikk
  korrekt default `format_type='team'`, `test_isolation.sql` fortsatt
  12/12 mot ekte database), deretter `docker compose up -d --build
  teecup_api` (kun backend, ingen frontend-kode denne runden). Containeren
  boot-et rent (`Application startup complete`), `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket. Verifisert presist at den nye ruten
  faktisk når FastAPI gjennom hele produksjonsstacken (Caddy → Next.js
  rewrite → `teecup_api`): et anonymt kall mot
  `/orgs/.../tournaments/.../rounds` ga korrekt `401 NOT_AUTHENTICATED`,
  ikke en rå 404.

- **ADR-037 frontend HÅNDKODET, GRUNDIG BROWSERVERIFISERT (inkl. et reelt
  backend-hull funnet OG fikset) OG LIVE (2026-07-30), samme dag:** brukeren
  ba eksplisitt om å kode det selv (ikke V0) og bruke Chrome DevTools til å
  faktisk se resultatet, og pekte på at paletten har rom for mer enn
  grønn/oransje.
  **Bygget:** ny `components/individual-tournament-detail.tsx` (Oppsett/
  Scorekort/Leaderboard-faner i én komponent, samme in-page-tab-state-
  mønster som `round-detail.tsx`) + ny `components/tournament-router.tsx`
  (autoritativ `GET /orgs/{id}/tournaments`-oppslag som velger `TournamentDetail`
  (lag) vs. `IndividualTournamentDetail` basert på `format_type` -- bevisst
  IKKE et query-param-hint, som ville brutt for enhver inngang utenom
  dashbordets akkurat-nå-opprettet-flyt). `app/tournaments/[id]/page.tsx`
  peker nå til routeren. `dashboard.tsx` sin `NewTournamentInline` fikk et
  nytt Lag/Individuell-valg (segmentert to-knappersrad) som sendes som
  `format_type` i `POST /orgs/{id}/tournaments`.
  **Ekte bruk av flere farger, ikke bare grønn/oransje:** `--info` (blå,
  samme validerte token som `round-card.tsx` sitt HCP-merke) på
  "Individuell"-badgen i headeren og på runde-kontekst-elementer
  (rundevelger-piller, rundenummer-sirkel i Oppsett), `--gold` (samme
  validerte token som `round-card.tsx` sitt "Personlig rekord"-merke) på
  leaderboardets 1.-plass-rad (medaljeikon + gullbakgrunn).
  **Scorekortet** gjenbruker `round-scorecard.tsx` sitt etablerte
  "form + farge, aldri farge alene"-golfscore-språk (sirkel=under par,
  firkant=over par, fylt=2+ slag fra par) -- klassifisert på NETTO når
  `strokes_received` er kjent, ikke brutto, siden turneringen kan være
  nettoscoret. Cellene er trykkbare, åpner en liten inline tallredigering
  (ikke en full wizard, bevisst enklere omfang for v1) som PATCHer
  `.../holes/{n}` direkte.
  **Ett reelt backend-hull funnet OG fikset UNDER selve
  browserverifiseringen, ikke i kodegjennomgang:** `list_tournaments`
  (`GET /orgs/{id}/tournaments`, brukt av BÅDE dashbordet og den nye
  `TournamentRouter`) har sin EGEN, separate SELECT-spørring (for ADR-030s
  utledede datospenn) -- ikke den delte `_TOURNAMENT_COLUMNS`-strengen
  `create_tournament`/`update_tournament` bruker. Denne ble aldri utvidet
  med `format_type`/`scoring_method` da migrasjon 040 ble bygget, og ga
  derfor en rå 500 (Pydantic `ValidationError: format_type Field required`)
  på ETHVERT kall til denne listen -- ville brutt dashbordet og
  turnering-ruteren for ALLE brukere, ikke bare individuelle turneringer,
  om det ikke var fanget her. Rettet med to nye kolonner i SELECT-en.
  **Ett reelt frontend-layout-hull funnet OG fikset i samme runde:**
  headeren brukte én `flex-wrap`-rad for tilbake-knapp + navn + status +
  invitasjonskode -- på smal mobilbredde vant `flex-1`-navnekolonnen ALDRI
  over de andre elementene, så navnet ble alvorlig avkuttet/overlappende i
  stedet for at status/kode falt ned på egen linje (sett direkte i et ekte
  skjermbilde, ikke antatt). Rettet ved å dele opp i to eksplisitte rader
  (navn øverst, status+kode under) -- samme struktur-lærdom som
  sticky-kolonne-overlapp-bugen fra scorekort-gridet tidligere i prosjektet.
  **Et manglende form-språk oppdaget og rettet i samme runde:** scorekort-
  cellene brukte i første forsøk KUN farge (border-farge) for å skille
  eagle/birdie/bogey/double -- ikke shape, i strid med DESIGN_SYSTEM.md sin
  "aldri farge alene"-regel og `round-scorecard.tsx` sin egen etablerte
  `ScoreMark`. Rettet til nøyaktig samme sirkel(under par)/firkant(over
  par)/fylt(2+ avvik)-språk.
  **Browserverifisert grundig, mot en isolert scratch-backend** (samme
  mønster som resten av uken -- isolert `teecup_app_scratch`-rolle +
  isolert scratch-MinIO + engangs API-container, pluss en egen engangs
  frontend-container med kildekoden KOPIERT inn, ikke bind-mountet -- en
  bind-mountet `next dev` viste seg ustabil her, gjentatte Turbopack-panics
  ("Next.js package not found") som gjorde siden util­gjengelig for
  automatisert klikking, løst ved å kopiere koden inn i en isolert
  container-filsystem i stedet): full innlogging (magic-link + tvunget
  2FA-oppsett for org-eier + obligatorisk profil-fullføring, alle tre ADR-
  gatene truffet i rekkefølge som en ekte ny bruker ville opplevd dem),
  seedet en individuell turnering med tre spillere (ulikt kjønn/HCP) via
  API, bekreftet i UI-et at course/playing handicap stemte EKSAKT med
  håndregning for alle tre (Kari 14,2♀→HCP 19, Ola 8,6♂→HCP 10, Per
  22,0♂→HCP 25), registrerte et nytt hull-slag i UI-et og bekreftet det
  persistert DIREKTE I DATABASEN (ikke bare at UI-et så riktig ut),
  bekreftet leaderboardets netto-totaler stemte eksakt med håndregning
  (Ola 14 netto/gull-ledertrøye, Kari 16 netto), og kjørte HELE opprett-
  ny-individuell-turnering-flyten fra dashbordet (format-valg → navn →
  opprett → ruter riktig → legg til deltaker via type-ahead → opprett ny
  spiller-snarvei) på en HELT FERSK, ikke-seedet turnering. Ingen
  konsollfeil (`list_console_messages`) gjennom hele økten. Ekte
  typesjekket produksjonsbuild (samme `Dockerfile` som deployes) kompilerte
  rent til slutt, med begge fiksene inne.
  **Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt:
  ingen migrasjon, `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `teeoff.no` upåvirket, `GET /orgs/.../tournaments` (den fikset
  ruten) bekreftet nåbar med korrekt `401` gjennom hele produksjonsstacken.
  **ADR-037 er dermed live, backend + frontend** (grunnstruktur — de fem
  konkrete formatene og Order of Merit fortsatt ikke designet, se
  FEATURE_BACKLOG.md).

- **To reelle punkter fra bruker, BEGGE FIKSET OG LIVE, samme dag
  (2026-07-30):** rapportert med et vedlagt skjermbilde av
  Chip/Bunker/Straffeslag-stepperne som gled over i hverandre på
  `/my-rounds/{id}`, pluss en forespørsel om et TeeCup-relatert favikon.
  1. **Stepper-overlapp, root cause presist identifisert:** `ScoringWizard`
     sin "flere detaljer"-seksjon (`round-detail.tsx`) er fast begrenset
     til `max-w-sm` (384px) UANSETT hvor bred selve viewporten er -- men
     Chip/Bunker/Straffeslag-gridet brukte et VIEWPORT-basert
     `sm:grid-cols-3`-brudd (640px). Enhver skjerm bredere enn 640px
     (dvs. de fleste telefoner i liggende modus, nettbrett, og skjermbildet
     brukeren delte) trigget dermed 3-kolonne-modus INNI en 384px-bred
     kolonne -- hver Stepper har to faste 44px-knapper (tilgjengelighets-
     kravet, kan ikke krympes) + verdi + padding, som aldri kan bli
     smalere enn ca. 190px, så tre av dem kolliderte alltid. Samme
     klasse feil som de tidligere sticky-kolonne- og match-identitets-
     boks-overlappene i prosjektet (fast innholdsbredde møter et
     viewport-basert, ikke container-basert, brudd). Fikset ved å fjerne
     `sm:grid-cols-3` helt -- alltid stablet i én kolonne, som uansett var
     den eneste bredden som noensinne hadde plass i en 384px-container.
  2. **Favikon var faktisk V0s egen logo, ikke TeeCup:** `icon.svg` og
     `icon-light-32x32.png`/`icon-dark-32x32.png` (selve nettleser-fane-
     ikonet, styrt av `layout.tsx` sin `metadata.icons`) viste seg -- ved
     faktisk å åpne og se på filene, ikke anta -- å fortsatt være en
     ubrukt "V0"-logo (sort/hvit v0.app-merke) helt siden V0-eksporten,
     aldri erstattet. `apple-icon.png` og selve PWA-ikonsettet
     (`public/icons/icon-192.png` m.fl.) var derimot ALLEREDE korrekt
     TeeCup-merket (grønt golf-flagg, fra PWA-runden 2026-07-19) -- kun
     favikon-stien var glemt. Fikset ved å gjenbruke SAMME etablerte
     golf-flagg-design: `icon-light-32x32.png`/`icon-dark-32x32.png`
     regenerert (nedskalert fra `icon-512.png` via en engangs
     `sharp`-scratch, samme verktøy som PWA-runden brukte) og `icon.svg`
     skrevet på nytt som en ren vektor av samme flagg (grønn `#8BC24A`-
     bakgrunn, hvit flaggstang+flagg -- appens egen merkevarefarge, ikke
     funnet på).
  **Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
  som deployes) kompilerte rent BEGGE ganger (én mislykket 404-test mot
  kun `--target builder`-imaget, som IKKE har `public/`-mappen kopiert inn
  ved siden av `server.js` ennå -- forventet byggestrenge-artefakt, ikke en
  reell bug -- rettet ved å bygge og teste det FULLE, faktiske
  produksjonsimaget i stedet, som ga korrekt `200`/riktig `content-type`
  for alle tre ikonfilene). Stepper-fiksen browserverifisert grundig i en
  isolert scratch-økt (ekte innlogging, en fersk `full`-stat_level-runde
  seedet via API, veiviseren kjørt gjennom Slag→Putter→Avstand→Detaljer):
  bekreftet INGEN overlapp ved 800px viewport (der bugen reprodusertes
  presist, matcher brukerens skjermbilde) OG ingen regresjon ved 390px
  (alltid stablet der uansett, uendret oppførsel). Ingen konsollfeil.
  **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt (samme
  "redeploy begge"-godkjenning som ADR-037): ingen migrasjon,
  `docker compose up -d --build teecup_frontend` (gjenskapte også
  `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
  containere boot-et rent, `/health`/`/dashboard` → 200, favikon-filene
  bekreftet nåbare over ekte https med riktig `content-type`, `teeoff.no`
  upåvirket.

- **Individuelle turneringer (ADR-037): scoring-autorisasjon strammet inn,
  BYGGET, SCRATCH-VERIFISERT OG LIVE (2026-07-30), samme dag som frontend-
  runden:** direkte oppfølging av det noterte hullet fra byggerunden
  tidligere samme dag — ethvert org-medlem kunne skrive score for HVEM SOM
  HELST i en individuell turnering, ikke bare sin egen.
  Ny `user_is_own_tournament_participant` i `app/team_authz.py` — samme
  mønster som `user_is_match_participant` (ADR-023): krever at brukeren ER
  spilleren bak `tournament_participant`-raden (via `player.user_id`),
  eller er org-eier/admin. Brukt av `individual_tournaments.py` sin
  `update_hole` (eneste endepunkt strammet inn). Runde-/deltaker-OPPSETT
  (opprett/slett runde, legg til/fjern turnering-/rundedeltaker) forblir
  bevisst på vanlig org-medlemsnivå — matcher presedensen fra `session`-
  opprettelse og `team_roster`-tilføyelse i tournaments.py, som heller
  aldri har vært captain-/admin-gatet.
  **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
  isolert scratch-MinIO + engangs API-container, alle 40 migrasjoner kjørt
  friskt): full regresjon av den eksisterende 52-punkts testsuiten (uendret
  grønn — org-eier brukes gjennomgående der, rammes ikke av innstrammingen),
  pluss 13 nye målrettede sjekker: et fremmed org-medlem (uten kobling til
  noen av deltakerne) NEKTES å score for både en annen deltaker OG en
  tredje deltaker (403 `NOT_TOURNAMENT_PARTICIPANT`), en spiller KOBLET til
  sin egen `tournament_participant` (via `player.email` + ADR-017s
  kontokobling ved innlogging) FÅR score seg selv men NEKTES å score for en
  annen, org-eier beholder uendret admin-fallback for begge, og lesing
  (`GET .../holes`) er bekreftet uendret tilgjengelig for et vanlig
  org-medlem (kun skriving er strammet inn, ikke lesing).
  **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
  migrasjon (ren Python-logikk), `docker compose up -d --build
  teecup_api`. Containeren boot-et rent (`Application startup complete`),
  `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.

- **To notater fra brukeren, BEGGE BYGGET, GRUNDIG BROWSERVERIFISERT OG
  LIVE (2026-07-30), samme dag som ADR-037-autorisasjonsfiksen:**
  1. **GIR-auto-inferens for "Innspill: Traff":** ny `useEffect` i
     `ScoringWizard` (`round-detail.tsx`) -- idet "flere detaljer"-steget
     nås, settes `stat.approach = "hit"` automatisk når
     `stat.strokes - stat.putts <= hole.par - 2` (samme formel som den
     allerede eksisterende GIR-STATISTIKK-inferensen i `round-stats.tsx`
     sin `isGir`, nå også koblet til selve REGISTRERINGEN) -- men KUN når
     `stat.approach` fortsatt er `null` (rører aldri et allerede satt
     manuelt ELLER tidligere auto-satt valg).
  2. **Auto-prompt "Fullfør runde":** ny `allHolesEnteredForEveryone`-
     beregning + `AllHolesEnteredBanner`-komponent i `RoundDetail` -- viser
     en tydelig CTA øverst på Score-fanen ("Alle hull er ført — Klar til å
     fullføre runden?") så snart ALLE spillere (eller BEGGE sider for
     delt-ball-formater) har `played=true` på alle hull i `holeOrder`,
     gatet på `round.setup_complete`. Kaller samme `finishRound()` som den
     eksisterende, tidligere passive knappen.
  **Browserverifisert grundig i en isolert scratch-nettleserøkt** (fersk
  `teecup_scratch`-database + isolert scratch-MinIO + engangs API-
  container + en isolert `next dev`-frontend-container med KOPIERT, ikke
  bind-mountet, kildekode -- bind-mount ga gjentatte Turbopack-panics i
  denne økten, løst ved å `tar`-kopiere kildetreet inn i en isolert
  container-filsystem i stedet): GIR-auto-inferens bekreftet BÅDE positivt
  (birdie+1-putt -> "Traff" auto-merket umiddelbart, uten klikk, bekreftet
  visuelt OG direkte i databasen) og negativt (bogey+1-putt -> "Traff"
  korrekt IKKE forhåndsmerket). Auto-prompt-banneret bekreftet å dukke opp
  automatisk idet siste hull ble fylt for begge spillerne i en 18-hulls
  2-spiller-runde, og "Fullfør runden"-knappen i banneret bekreftet å
  fullføre runden korrekt (håndterte den native `confirm()`-dialogen via
  Chrome DevTools -- HCP-differensialer beregnet og vist etterpå). Ingen
  konsollfeil i noen av rundene.
  **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
  migrasjon (ren frontend), `docker compose up -d --build
  teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
  ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
  `/dashboard` → 200, `teeoff.no` upåvirket.

- **Symbolforklaringen fjernet fra scorekortet + to design-notater fanget
  opp (2026-07-30), samme dag:** brukeren viste et skjermbilde av
  `round-scorecard.tsx` med "Under par (sirkel)/Over par (firkant)/Fylt
  symbol..."-forklaringen sirklet inn og ba om at den fjernes — `Legend`-
  komponenten (kun ett bruksted) fjernet fullstendig. Samtidig reist:
  (1) spilleren bør kunne se historikk/statistikk for NØYAKTIG hullet som
  spilles/er spilt (ikke bygget — krever en ny, ikke-triviell "aggreger på
  tvers av runder, filtrert til banenavn+hullnummer"-spørring, notert i
  FEATURE_BACKLOG.md); (2) manglende lenke scorekort→statistikk, og et
  åpent spørsmål om scorekort/statistikk/live-registrering burde slås
  sammen til én visning, med mottatte slag vist som prikker. Svarte
  direkte (ikke bygget): behold scorekort og statistikk som to separate
  sider (bevisst skilt 2026-07-25 nettopp for å unngå én lang/rotete
  side) — legg heller til den manglende lenken; behold også selve
  live-registrerings-gridet uendret (bygget 2026-07-27 som en AKTIV
  data-entry-flate, ikke en lesevisning — et vertikalt front9/back9-delt
  format ville svekket registreringsergonomikken). "Prikker for mottatte
  slag" vurdert som en god, uavhengig senere polish-oppgave. Full
  begrunnelse i FEATURE_BACKLOG.md.
  **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ekte
  typesjekket produksjonsbuild kompilerte rent, `docker compose up -d
  --build teecup_frontend` (gjenskapte også `teecup_api` som vanlig
  bivirkning). Begge containere boot-et rent, `/health` → 200,
  scorekort-siden bekreftet 200, `teeoff.no` upåvirket.

- **De to avgrensede scorekort-oppfølgerne fra samme dag BYGGET,
  BROWSERVERIFISERT OG LIVE (2026-07-30):** (1) ny "Se full
  rundestatistikk"-lenke nederst på `round-scorecard.tsx` (symmetrisk med
  den eksisterende motsatte lenken på statistikksiden); (2) mottatte
  slag-hintet (vist FØR et hull er fylt ut) endret fra "−N"-tekst til
  prikker (`StrokeDots`, `round-detail.tsx`, i BÅDE `ScorecardGrid` og
  `SideScorecardGrid`) — selve tallet bevart i `aria-label` for
  tilgjengelighet. Selve spørsmålet om å slå sammen scorekort/statistikk/
  live-registrering til én visning er BEVISST IKKE avgjort — bruker
  ønsker en fremtidig brukertest først.
  **Browserverifisert grundig i en isolert scratch-nettleserøkt** (samme
  mønster som resten av uken): en HCP 28-spiller (course handicap 30)
  bekreftet å vise nøyaktig to prikker på hull 1-12 i live-
  registreringsgridet (riktig ut fra 30-18=12 ekstra slag), den nye
  lenken bekreftet klikkbar og navigerte korrekt til `/stats`. Ekte
  typesjekket produksjonsbuild kompilerte rent. Ingen konsollfeil (kun en
  godartet, urelatert WebSocket-advarsel fra rask sidenavigasjon).
  **Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning). Begge containere boot-et
  rent, `/health`/`/dashboard`/scorekort-siden → 200, `teeoff.no`
  upåvirket.

- **Åtte nye turneringsformater: BACKEND FERDIG, SCRATCH-VERIFISERT OG
  RULLET UT LIVE (2026-07-30):** brukeren ba om å ta fatt på det
  lenge noterte "flere turneringsformater"-punktet fra 2026-07-19. Listen
  ble utvidet fra fire til åtte (Shamble, Chapman/Pinehurst, Bingo Bango
  Bongo, Money Ball/Lone Ranger, Nassau Match Play lagt til; "High-low-
  high"/"Try all" presist avklart av bruker med et fullt utregnet
  eksempel; "Robbins" droppet). Full plan skrevet og godkjent (plan-
  modus) FØR bygging, deretter bygget ETT format om gangen i rekkefølgen
  Chapman → Nassau → Københavner → Bingo Bango Bongo → Flaggturnering →
  Shamble → Money Ball → High-low-high, motor→migrasjon→API→scratch-
  verifisering per format FØR neste startet.
  **Arkitektonisk hjem** (bekreftet av bruker "begge, fra start"): flatt-
  felt-formater (Københavner/BBB/Flag) → frittstående runder OG den
  individuelle org-turnering-modellen (ADR-037), ALDRI org-lagturneringer
  (ADR-011s to-lags-modell passer strukturelt ikke). To-siders-formater
  (Chapman/Nassau/Shamble/Money Ball/High-low-high) → frittstående runder
  OG org-lagturneringer, ALDRI den individuelle org-modellen. Shamble/
  Money Ball i frittstående runder: ETT LAG = HELE RUNDENS deltakersett
  (bekreftet av bruker, INGEN `round_side`) -- flere lag kobles via
  eksisterende `flight_group_id`-leaderboard.
  **Ny delt `ENGINE_FORMAT_ALIASES`-mekanisme** i `app/handicap.py` --
  Chapman/Shamble/Money Ball/High-low-high aliaserer til
  foursome/singles/singles/fourball for HCP-beregning (ingen dupliser
  allowance-logikk), løst opp FØRST i `parse_allowance_config`/
  `compute_and_store_side_handicaps`/`relative_strokes_for_match`.
  **Reelle funn/presiseringer underveis, alle rettet FØR utrulling:**
  Nassau kan hindres av org-matchers eksisterende ALREADY_DECIDED-sperre
  (ADR-012) hvis overall-matchen avgjøres tidlig -- dokumentert v1-
  begrensning, ikke fikset. Københavner-poeng må regnes om for ALLE tre
  deltakerne samtidig (eneste scoring_method som bryter "regn om for ÉN
  deltaker"-mønsteret). Money Ball-rotasjon er basert på POSISJON i spilt
  rekkefølge, ikke rått hullnummer. High-low-high passer ikke inn i den
  ternære `HoleResult`-cachen -- egen dedikert leseendepunkt per system
  (som Nassau), generisk `status_text` viser en ufarlig statisk "AS"-
  plassholder for dette formatet. To reelle "manglende SELECT-kolonne"-
  krasj (bbb_sweep_bonus_enabled, shamble_best_n) fanget og rettet under
  scratch-testing, før noe nådde en antatt-ferdig tilstand.
  **Verifisert grundig:** `handicap_engine.py`-enhetstester 98/98 (opp fra
  63 FØR denne runden), full scratch-API-verifisering i begge relevante
  systemer per format (isolert `teecup_app_scratch`-rolle + isolert
  scratch-MinIO + engangs API-container) -- over 400 sjekker totalt på
  tvers av de åtte formatenes testskript, inkl. High-low-high sin
  eksakte gjenskaping av brukerens eget håndregnede eksempel ("1-1 etter
  hull 1", "2-1 til lag 2"). `test_isolation.sql` 12/12 uendret gjennom
  hele runden (migrasjonene 041-050 er rent additive).
  **IKKE bygget ennå, bevisst neste steg:** frontend for samtlige åtte
  formater (nye format-valg i opprett-runde/opprett-økt, nye
  resultatvisninger) — backend er fullt funksjonelt og testet, men
  ubrukelig fra selve appen inntil frontend bygges, samme lagdelings-
  mønster som ADR-037/038/039.
  **Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt
  ("Ja takk"): migrasjonene 041-050 kjørt i rekkefølge mot ekte
  `teecup_db` (alle 10 rene, additive `DROP/ADD CONSTRAINT`+nye
  tabeller/kolonner, ingen eksisterende rader rørt), `test_isolation.sql`
  fortsatt 12/12, deretter `docker compose up -d --build teecup_api`.
  Containeren boot-et rent (`Application startup complete`), `/health`/
  `/dashboard` → 200, `teeoff.no` upåvirket. Verifisert presist at det nye
  Nassau-endepunktet faktisk når FastAPI gjennom hele produksjonsstacken:
  et anonymt kall ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.
  Scratch-miljøet (isolert rolle/database/MinIO/API-container, alle
  test-skript) ryddet opp etterpå, som vanlig. Se FEATURE_BACKLOG.md for
  full detalj per format.

- **Driftsvarsel ved ny konto, BYGGET, SCRATCH-VERIFISERT OG LIVE
  (2026-07-30):** brukeren ba om en e-post til seg selv
  (`hei@erol.no`) hver gang noen oppretter en HELT NY TeeCup-konto —
  avklart eksplisitt (AskUserQuestion) at dette gjelder kontoopprettelse
  generelt (ikke turnering-selvregistrering, ADR-017, som var det andre
  alternativet). Ingen migrasjon — ren kode-endring.
  Ny `settings.NEW_ACCOUNT_ALERT_EMAIL` (`app/config.py`, env-variabel
  `TEECUP_NEW_ACCOUNT_ALERT_EMAIL`, default `hei@erol.no` — egen setting
  fremfor hardkodet adresse, kan endres uten ny utrulling). Ny
  `send_new_account_alert_email()` i `app/email.py` (alltid norsk — internt
  driftsvarsel til én fast, kjent mottaker, ikke brukervendt i18n-tekst).
  **Kjernestykket:** `verify_magic_link` (`app/routers/auth.py`) er den
  ENESTE plassen en `app_user`-rad noensinne settes inn (bekreftet med
  `grep -rn "INSERT INTO app_user"` — null andre treff) — utvidet til å
  fange `is_new_account` (sann KUN når selve INSERT-en faktisk vant, ikke
  ved en samtidig konflikt ELLER en sekundær-e-post-innlogging som løses
  til en eksisterende konto, `FEATURE_BACKLOG.md` "Én person, flere
  e-postadresser"). Selve e-postutsendingen skjer BEVISST ETTER at
  `plain_connection()`-blokken er lukket (samme "e-post skal aldri sendes
  mens en tilkobling/transaksjon holdes åpen"-prinsipp som resten av
  appen), med samme `DEV_LOG_MAGIC_LINKS`/`SMTP_CONFIGURED`/try-except-
  mønster som all annen e-postutsending — en driftsfeil i selve
  sendingen kan aldri endre innloggingsresponsen.
  **Scratch-verifisert, 8/8 sjekker:** varsel logget nøyaktig én gang ved
  aller første innlogging for en ny e-post, INGEN nytt varsel ved en
  påfølgende innlogging for SAMME konto, og et helt nytt, eget varsel for
  en ANNEN ny e-post (uendret telling for den første). `test_isolation.sql`
  12/12 uendret (ingen skjemaendring).
  **Rullet ut live 2026-07-30**, sammen med migrasjonene 041-050 over (én
  samlet utrulling, bruker bekreftet eksplisitt): ingen migrasjon for
  denne delen isolert, dekket av samme `docker compose up -d --build
  teecup_api`-kjøring, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket.

- **Login-skjermen redesignet ("Forest Green", inspirert av et eksternt
  design-verktøy kalt "Stitch"), BYGGET OG LIVE (2026-08-01/02):**
  brukeren ba eksplisitt om et V0-prompt basert på Stitchs skisse, kjørte
  det selv, sendte zip-eksporten tilbake. `frontend/components/login-
  form.tsx` skrevet fullstendig om — V0s nye visuelle skall (låst
  fargepalett `const C = {...}`, WCAG-kontrast verifisert presist, ikke
  anslått, for hvert fargevalg) flettet med 100 % ekte logikk fra den
  gamle komponenten uendret: `sendLink()` (`POST /auth/request-link`),
  passord-innlogging (`POST /auth/login-password`), invitasjonskode-
  oppslag (`GET /public/tournaments/by-code/{code}` + navigasjon).
  `TwoFactorVerifyForm`/`TwoFactorSetupForm` (ADR-021) beholdt HELT
  uendret, kun rendret inni det nye kortskallet. **Reell bug funnet under
  integrering, browserverifisert:** `app/page.tsx` hadde sin egen
  `<Wordmark/>`+tagline-header OVER selve `LoginForm`, som nå OGSÅ hadde
  fått sin egen header inni det nye kortet — duplikat. Fjernet den ytre
  headeren + det nå ubrukte `Wordmark`-importet fra `page.tsx`.
  **Rullet ut live**, bruker bekreftet eksplisitt ("Rull ut live. Jeg har
  comitted til git og kan reversere om nødvendig."): ren frontend-endring,
  ingen migrasjon.

- **Dashbordet redesignet i samme visuelle retning, BYGGET OG LIVE
  (2026-08-01/02), to runder samme dag:** første runde (V0-eksport)
  skrev om `dashboard.tsx` fra bunnen — flettet ekte ADR-035-datalag
  (organisasjoner/turneringer/runder/HCP-historikk/venner/varsler) med
  V0s nye visuelle seksjoner, la til en NY «Venner på banen»-seksjon
  (bekreftet av bruker at den skal ligge RETT UNDER hurtighandlingene,
  jf. Stitchs egen skisse — jeg hadde ikke selv sjekket dette FØR
  brukeren spurte eksplisitt, rettet før prompten ble sendt).
  **Ny backend-endepunkt bygget for dette:** `GET /friends/on-course`
  (`app/routers/rounds.py`, ny `FriendOnCourseEntry`-modell) — venner med
  en PÅGÅENDE runde, eller en fullført innen siste 24 timer (bekreftet
  av bruker), filtrert gjennom SAMME synlighets-SQL som `_can_view_
  round` sin venner-gren (ADR-036 fase 2) — ingen egen, parallell
  synlighetsregel.
  **Andre runde, presise fargekorreksjoner mot Stitchs FAKTISKE
  skjermbilde** (ikke bare beskrivelse — brukeren lastet opp det ekte
  bildet etter at jeg først måtte innrømme jeg hadde slettet den
  opprinnelige zip-en og derfor sammenlignet blindt): pikselverdier
  hentet presist via PIL i en engangs Docker-container (Stitchs egen
  knappegrønn viste seg å være `#2d950c`, kontrast mot hvit tekst kun
  3,88:1 — FEILER WCAG AA, flagget proaktivt FØR den ble brukt).
  `const C` fikk `primary:"#1f6b08"` (samme fargefamilie, kontrast
  ~6,6:1, verifisert med samme relative-luminans-formel), `primaryInk:
  "#ffffff"`, `borderSoft:"#e4ebe3"`, ny `AVATAR_HUES`-array (pastell-
  par til venne-avatarer). Header endret fra glassmorfisme til flat
  opak bakgrunn, flagg-ikonet fikk lys grønn bakgrunn i stedet for
  oransje, `QuickAction`/`ShortcutButton` og `LiveFriends`-kortene
  fargekorrigert tilsvarende.
  **Ny fast bunn-fanerad** (`BottomTabBar`, eksportert fra
  `dashboard.tsx`, brukt av flere sider): fem faner (Hjem/Runder/
  Turneringer/Profil/Mer). **Reell rute-kollisjon funnet og rettet FØR
  utrulling** (samme klasse feil som tidligere i prosjektet, `/rounds`
  vs. API-prefikset) — V0s genererte `href="/rounds"`/`href="/tournaments"`
  pekte begge feil (ingen slik side finnes) — rettet til `/my-rounds` og
  et ankerpunkt `/dashboard#kommende-turneringer` (ingen egen turnering-
  liste-side finnes). Ny, tidligere ikke-eksisterende `/more`-side bygget
  (`components/more-menu.tsx`, `app/more/page.tsx`) som femte fanes reelle
  mål — samler Konto/Venner/Varsler/organisasjoner ett sted, ekte
  `handleLogout()`. `<main>`-padding økt (`pb-24`) for å ikke overlappe
  den nye faste bunnraden.
  `install-prompt.tsx` fikk kun ny JSX (mørk grønn gradient-banner),
  100 % av den ekte iOS/Android-deteksjons-/`beforeinstallprompt`-/
  14-dagers-utsettelseslogikken uendret.
  **Rullet ut live**, bruker bekreftet eksplisitt: ingen migrasjon for
  visuelle deler, ny `/friends/on-course`-ruten dekket av samme
  `docker compose up -d --build teecup_api teecup_frontend`. Begge
  containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket.

- **«Det store grepet»: sammenhengende rundeoppsett + delt Score/
  Scorekort/Leaderboard-navigasjon — steg 1+2 av 5 BYGGET, SCRATCH-/
  BROWSERVERIFISERT OG LIVE (2026-08-02), se ADR-040 for full
  beslutningslogg:** direkte oppfølging av login-/dashbord-redesignet —
  brukeren pekte på to strukturelle hull utover selve fargespråket
  (rundeoppsettet spredt over to skjermer, Score/Scorekort/Leaderboard
  deler ingen fast navigasjon). Fem beslutninger bekreftet (ADR-040
  Beslutning A-E), plan skrevet som en levende Artifact-skisse før
  bygging.
  **Steg 1 (backend, HCP-prosent alltid justerbar + Match-HCP-bryter):**
  ny migrasjon `051_round_allowance_override.sql` (`round.allowance_
  override jsonb`), koblet inn i `_recompute_side_handicaps`/`_relative_
  strokes_for_round`/`POST`+`PATCH /rounds` (`app/routers/rounds.py`).
  Gjenbruker ADR-014s allerede ferdigbygde motor uendret (`match_play_
  strokes()`, `parse_allowance_config`) — frittstående runder manglet
  bare selve kolonnen.
  **Steg 2 (ren kode-fiks, ikke V0): spillere i scorekortet var ALDRI
  gruppert lagvis** — reelt, tidligere udokumentert funn rapportert av
  bruker med en konkret lenke (fourball-runde der to kjente lagkamerater
  ble vist interleaved). Bekreftet i koden: `ScorecardGrid` (`round-
  detail.tsx`) og `MatchScorecardGrid` (`round-scorecard.tsx`) sorterte
  ingen av dem på `round_side_id`. Fikset med stabil sortering (side A
  samlet, deretter side B) i begge.
  **Scratch-verifisert presist:** 100 %→50 %-prosent ga `playing_
  handicap` 24/10 → 12/5 (nøyaktig som beregnet for hånd), `use_
  matchplay_handicap:false` ga rå 12/5 i stedet for differensial 7/0.
  Lag-grupperingsfiksen browserverifisert mot en fersk scratch-fourball-
  runde som gjenskapte brukerens eget scenario nøyaktig (interleaved
  tilføyelsesrekkefølge A→Rødt, B→Blått, C→Rødt, D→Blått) — bekreftet
  visuelt i BEGGE visningene at lagene nå vises samlet.
  **Rullet ut mot ekte systemer 2026-08-02**, bruker bekreftet eksplisitt
  (plan vist FØR migrasjonen): migrasjon 051 kjørt mot ekte `teecup_db`
  (kolonne bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
  `docker compose up -d --build teecup_api teecup_frontend`. Begge
  containere boot-et rent, `/health`/`/dashboard` → 200, anonym
  `PATCH /rounds/{ukjent-id}` ga korrekt `401` (ikke en rå 404), `teeoff.no`
  upåvirket.
  **Gjenstår (steg 3-5 av 5, ingen V0-prompt skrevet/sendt ennå):**
  V0-prompt 1 (den samlede rundeoppsett-veiviseren), V0-prompt 2 (den
  delte fane-raden + spørsmålet om lag-markering utover sortering),
  deretter integrering/browserverifisering/utrulling av begge. Se
  FEATURE_BACKLOG.md for detaljert arbeidsnotat.

- **«Det store grepet»: steg 3 av 5 (V0-prompt 1, rundeoppsett-
  veiviseren) BYGGET, GRUNDIG VERIFISERT OG LIVE (2026-08-02), samme
  dag:** V0-prompten (Beslutning A/B) skrevet, kjørt av bruker (zip 25),
  `frontend/components/new-round.tsx` skrevet fullstendig om —
  V0s 5-stegs veiviser-skall flettet med ekte logikk (offisiell/egen
  bane-søk inkl. nearby-geolokasjon, ekte egen-bane-opprettelse portert
  fra tidligere versjon, ekte `/people/search`, ny `allowance_override`-
  konstruksjon i steg 2 samme mønster som `CreateSessionCard`, og en helt
  ny innsendingsorkestrering — runden+deltakere+sider/lineup finnes
  FØRST når hele veiviseren er fullført, ulikt den gamle "opprett runden
  først"-flyten).
  **Reelle feil funnet og rettet under integrering:** V0s egen
  `Tee`-type manglet kjønnsfelt (ville vist ufiltrerte utslag) — lagt til
  `genders`, filtrert riktig. To TS-feil (prop-spredning som overskrev
  `course`/`ownGender` tilbake til `null`, en `"x"`-kjønn-mismatch i
  tee-filtreringen).
  **Verifisert grundig i isolert scratch:** full produksjonsbuild (alle
  ruter listet), to komplette nettleser-gjennomkjøringer mot en fersk
  scratch-backend (én Fourball-runde med egen bane/gjest/to sider/90 %
  HCP-prosent, én Slagspill-runde med rene standardvalg) — bekreftet
  direkte mot databasen at `allowance_override` ble bygget nøyaktig
  riktig i begge tilfeller (`per_player` for Fourball siden det ikke er
  en side-enhet, `null` for Slagspill), sidene/tildelingene stemte, og
  HCP ble beregnet korrekt fra ekte profildata.
  **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning). Begge containere boot-et
  rent, `/health`/`/dashboard`/`/my-rounds/new` → 200, `teeoff.no`
  upåvirket. Full detalj i ARCHITECTURE_DECISIONS.md (ADR-040).

- **«Det store grepet»: steg 4-5 av 5 (V0-prompt 2, den delte Score/
  Scorekort/Leaderboard-fane-raden + integrering) BYGGET, GRUNDIG
  VERIFISERT OG LIVE (2026-08-02), samme dag — ADR-040 dermed HELT
  FERDIG:** V0-prompten (Beslutning C/D) bevisst smalere i omfang enn
  prompt 1 — kun selve header/fane-chrome-en, ikke en redesign av
  Scorekort-sidens innhold (den sammenslåingen med Statistikk er ren
  kode-sammenstabling, løst direkte uten V0). Kjørt av bruker (zip 26)
  — ny `components/round-header.tsx` (tatt inn uendret: kontekstblokk
  + tre-fanet segmentert kontroll + en "Administrer"-knapp som åpner en
  bunnsheet-dialog med "Spillere og runde"-lenke + "Fullfør runde" +
  to-stegs "Slett runde").
  **Ekte datalag:** ny `components/round-page-shell.tsx` — henter
  rundedata for headeren, implementerer `onFinishRound`/`onDeleteRound`
  som egne, selvstendige kall (samme `POST .../complete`/`DELETE ...`
  som før, men uavhengig siden komponenten deles av alle tre sidene).
  **Wiret inn:** Score (`round-detail.tsx` sin gamle egne header
  fjernet, erstattet med `RoundPageShell` — den interne "Score"/
  "Spillere og runde"-fanevekslingen bevisst BEHOLDT som lavrisiko-valg,
  nås nå i tillegg via headerens dialog med en ny `?tab=manage`-URL-
  parameter), Scorekort (`round-scorecard.tsx`+`round-stats.tsx` slått
  sammen til ÉN side via en ny `embedded`-prop på begge -- statistikk
  RETT UNDER scorekortet, gammel `/stats`-rute er nå en redirect dit),
  Leaderboard.
  **Reelt funn og fiks under integrering:** to `<main>`-landemerker på
  samme side etter sammenslåingen (ugyldig HTML/a11y) -- rettet med en
  `ContentTag`-switch (`div` når embedded) i `round-stats.tsx`. Fjernet
  nå overflødige kryss-lenker, kollapset "Se scorekort"/"Se full
  rundestatistikk" til én knapp i `round-detail.tsx` sitt fullført-
  banner.
  **Verifisert grundig i isolert scratch:** full produksjonsbuild, full
  nettleser-gjennomkjøring av hele "Administrer"-flyten -- headeren
  konsistent på tvers av alle tre fanene, dialogens lenke til "Spillere
  og runde" bekreftet å faktisk åpne riktig intern fane, ekte "Fullfør
  runde" (bekreftet `completed_at` satt via API etterpå) og ekte
  to-stegs "Slett runde" (bekreftet navigerte korrekt til tom-tilstand
  etterpå). Traff en Turbopack-flakighet i scratch-dev-serveren
  underveis (stuck spinner) -- løst med frisk containerstart, bekreftet
  IKKE en kodefeil.
  **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen
  migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning). Begge containere boot-et
  rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
  **Bevisst IKKE dekket:** Beslutning E sitt spørsmål om lag-
  gruppering trenger mer enn celle-fargelegging (sortering i seg selv
  ble fikset i steg 2) -- prompt 2 ble bevisst avgrenset til kun
  header-chrome-en, dette spørsmålet er derfor fortsatt åpent, ikke
  stilt til V0 ennå. Se FEATURE_BACKLOG.md.

- **Rundeoppsett-veiviseren: dato/klokkeslett forhåndsutfylt, LIVE
  (2026-08-02), samme dag:** brukeren ba om at dagens dato og gjeldende
  klokkeslett skal være default når man setter opp en runde, i stedet
  for tomme felt. `frontend/components/new-round.tsx` sitt Steg 1 fikk
  to nye lokale hjelpefunksjoner (`todayIso()` -- lokal tidssone, ikke
  UTC, portert fra en tidligere versjon av filen, samme presisjonshensyn
  som `<input type="date">` alltid har krevd i dette prosjektet;
  `nowTimeString()`) — `date`/`time`-state initialiseres nå med disse i
  stedet for tomme strenger (dato respekterer fortsatt `prefillPlayedAt`
  fra "legg til en flight til"-flyten der den er satt). Browserverifisert
  i isolert scratch: begge feltene viste korrekt dagens dato/klokkeslett
  ved første besøk til Steg 1s felt-visning. Rullet ut live, ingen
  migrasjon, kun `docker compose up -d --build teecup_frontend`, `/health`/
  `/my-rounds/new` → 200, `teeoff.no` upåvirket.
- **Grønnfarge-vasken fjernet på tvers av hele appen, BYGGET, SCRATCH-/
  BROWSERVERIFISERT OG LIVE (2026-08-02), samme dag:** brukeren delte et
  Stitch-referansebilde og et skjermbilde av `/my-rounds/new` og spurte
  hvorfor TeeCup fortsatt virket "fast i det lysegrønne utseendet" til
  tross for at Stitch-referansen tydelig bare bruker grønt to steder
  (valgt tilstand + CTA). Diagnostisert presist FØR noe ble endret (ikke
  gjettet): `--primary` (den mettede merkevaregrønnen) var riktig, men
  (1) de "nøytrale" tokenene (`--background`/`--muted`/`--accent`/
  `--border`/`--secondary`) hadde alle en svak grønn hue (130-145)
  iblandet i `globals.css` selv når de skulle være ren grå -- ga et
  vedvarende grønt skjær på nesten hver bakgrunn/hover/kant i hele appen;
  og (2) et helt separat, mye mer synlig problem: et `bg-primary/15
  text-primary`-ikonchip-/avatar-/pille-mønster var brukt UBETINGET
  (uavhengig av valgt/aktiv-tilstand) i over 30 komponentfiler --
  `grep -rl "bg-primary/(5|10|15|20)|bg-accent/"` traff nesten hele
  `components/`-mappen.
  **To lag fikset, med bevisst avgrensning:**
  1. **Token-nivå** (`frontend/app/globals.css`, `:root`/`.dark`/
     `@media (prefers-color-scheme: dark)`, alle tre synkronisert):
     nøytrale tokens satt til ekte `chroma 0` (f.eks. `--background:
     oklch(0.985 0.005 130)` → `oklch(0.985 0 0)`). `--primary`/
     `--brand-orange`/`--destructive`/`--ring`/`--info`/`--gold`/
     `chart-1..6` UENDRET -- kun de tokenene som skulle vært "nøytral
     grå" ble rettet, ikke merkevarefargene. `DESIGN_SYSTEM.md` sin
     token-tabell + en ny historikk-note oppdatert til å reflektere dette.
  2. **Ikonchip-sveip** (~17 filer, ~35 enkeltsteder, gjennomgått ett og
     ett med `grep -B1 -A3` for kontekst FØR endring, ikke et blindt
     sed-sveip over hele treet): `bg-primary/{5,10,12,15,20}` +
     `text-primary` byttet til `bg-muted`/`text-muted-foreground` KUN der
     mønsteret var ubetinget (samme farge uansett tilstand) --
     seksjonshode-ikoner (`account-settings.tsx` × 7, `two-factor-
     flow.tsx` × 3, `tournament-program.tsx` × 2, m.fl.), avatar-/
     initial-chips (`new-round.tsx` sin `Avatar`, `friend-profile.tsx`,
     `friends.tsx`, `round-detail.tsx` sitt medspiller-søk), tomtilstand-
     ikoner (`course-rounds.tsx`, `own-rounds.tsx`, `rounds-stats-
     summary.tsx`), kategori-piller (`friends.tsx`), og en gjennomgående
     navigasjons-ikonchip (`round-header.tsx`, vises på ALLE tre
     rundeskjermene). **Bevisst latt urørt** der grønt faktisk BÆRER
     mening (dokumentert i `DESIGN_SYSTEM.md`s "primær = valgt/aktiv,
     bekreftet"-regel): ternary-baserte valgt-/aktiv-tilstander (`Choice
     Card`, `Pill`, kategori-avkrysning), "bekreftet"-tilstander
     (`public-tournament.tsx` sin "Du er påmeldt!"-status,
     `verify-form.tsx`/`verify-email-form.tsx` sine suksess-skjermer),
     territorium-baren (match-lederskap, dokumentert bespoke mønster),
     GIR-kompassets bevisste sentercelle, scorekortets "hero-rad"
     (kommentert i koden som en bevisst fremhevet rad), og "Valgt
     bane"-bekreftelsesbanneret i `new-round.tsx` (bekrefter et fullført
     valg, samme semantikk som en "confirmed"-tilstand).
  **Scratch-verifisert grundig, to browserrunder** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container + full produksjonsbuild av frontend, samme mønster som hele
  prosjektet): ekte innlogging, profil-fullføring, og skjermbilder av
  nøyaktig samme skjerm brukeren viste (`/my-rounds/new`), pluss
  dashbord og `/my-friends` -- bekreftet ikonchips nå nøytral grå,
  kortbakgrunner hvite, sidebakgrunn ekte grå, grønt kun på
  steg-indikatoren/wordmarket/CTA-knappen. Ingen konsollfeil. Ekte
  typesjekket produksjonsbuild kompilerte rent begge runder.
  **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt (etter at
  skjermbildene ikke lot seg vise i klienten -- bekreftet muntlig i
  stedet: "implementer endringen"): ingen migrasjon, ren frontend-
  endring, `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt).
  Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
  upåvirket.
- **Den EGENTLIGE gjenværende grønnfarge-kilden funnet og fikset,
  BROWSERVERIFISERT AV BRUKER SELV, RULLET UT LIVE (2026-08-02), samme
  dag:** brukeren fortsatte å se `#F7FAF8` som sidebakgrunn på ekte
  produksjon selv etter forrige runde -- viste ekte DOM-inspeksjon fra en
  nettleser som aldri hadde vært brukt før (utelukket cache/service
  worker som årsak). Diagnostisert presist (ikke gjettet): `--background`
  i `globals.css` var faktisk korrekt (`#fff`, bekreftet direkte mot den
  servert CSS-bunten via `curl`), men `dashboard.tsx` og `more-menu.tsx`
  hadde sin EGEN, LÅSTE JS-fargepalett (`const C = {...}`, fra "Forest
  Green"-redesignet 2026-08-01) med `bg: "#f7faf8"`, satt via `style={{
  backgroundColor: C.bg }}` på sidens ytterste wrapper/header/loading-
  spinner -- en INLINE STYLE bypasser CSS custom properties helt, uansett
  hva `globals.css` sier. Dette brøt `DESIGN_SYSTEM.md` sin egen,
  eksisterende regel ("ALDRI hardkodede farger... bryter automatisk lys/
  mørk-tema-logikk") -- introdusert av meg selv i en tidligere runde uten
  å fange bruddet da.
  **Fikset:** `bg: "#f7faf8"` fjernet fra `const C` i BEGGE filer, alle
  fire `style={{ backgroundColor: C.bg }}`-stedene (dashboard.tsx sin
  loading-spinner/hovedwrapper/header, more-menu.tsx sin hovedwrapper)
  erstattet med Tailwind-klassen `bg-background` (nå faktisk `#fff`,
  forrige rundes fiks) -- ren fjerning, ikke bare en ny hex-verdi, slik at
  en FREMTIDIG token-endring automatisk forplanter seg hit også. Et femte
  sted (en varselboble sin `boxShadow`-"utskjærings"-ring mot side-
  bakgrunnen) endret fra `${C.bg}` til `var(--background)` -- kan ikke
  bruke en Tailwind-klasse inni en inline `boxShadow`-streng, men
  `var(--background)` holder den fortsatt koblet til token-systemet
  fremfor en ny hardkodet verdi. `login-form.tsx`/`round-stats.tsx` sine
  egne `const C`-paletter sjekket og bekreftet IKKE berørt (ingen egen
  `bg`-bakgrunnsverdi der -- `round-stats.tsx` sin er allerede `var(--
  chart-N)`-referanser, riktig fra før).
  **Verifisert presist, denne gangen mot selve det bygde bunten, ikke
  bare kildekoden:** `grep -rl f7faf8` mot HELE `.next/static`-mappen
  INNI det faktiske produksjonsimaget (`docker run --rm --entrypoint sh
  teecup-teecup_frontend ... grep`) ga null treff -- streng-nivå-bevis at
  verdien er borte fra det som faktisk sendes til nettleseren, ikke bare
  fra kilden. Ekte typesjekket build kompilerte rent.
  **Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt (viste
  konkret DOM-bevis, ba om fiks): ingen migrasjon, `docker compose up -d
  --build teecup_frontend` (gjenskapte også `teecup_api` som vanlig
  bivirkning). Begge containere boot-et rent, `/health`/`/dashboard`/
  `/more` → 200, `teeoff.no` upåvirket.
  **Lærdom, notert eksplisitt for fremtidige runder:** når en visuell
  fiks angivelig er utført men brukeren fortsatt ser feil farge, sjekk
  ALLTID for hardkodede inline `style={{...}}`-verdier og låste lokale
  JS-fargepaletter (`const C = {...}`, kjent mønster fra "Forest Green"-
  arbeidet) FØR man antar det er nettleser-cache -- CSS-token-nivå-
  verifisering alene (kun `globals.css`/den kompilerte CSS-bunten) er
  IKKE tilstrekkelig bevis når V0-avledede skjermer kan ha sin egen,
  parallelle fargekilde som omgår token-systemet helt.
- **CLAUDE.md splittet i regler + historikk (2026-08-02):** filen hadde
  vokst til 6500+ linjer og ble injisert i sin helhet i konteksten hver
  eneste forespørsel -- brukeren foreslo en konkret splitt (invarianter i
  CLAUDE.md, kronologisk historikk i en egen fil), som ble vurdert å
  genuint hjelpe (mindre kontekstforbruk per runde, mindre risiko for at
  de faktiske reglene drukner i historien). Hele "Status"-seksjonen
  flyttet til denne filen (`CHANGELOG.md`, ny), verifisert byte-for-byte
  identisk med originalen via `diff` FØR noe ble slettet. CLAUDE.md
  redusert til ~117 linjer (autoritative kilder, sikkerhetsregler,
  arkitektur-invarianter, tilgjengelighet, navneformat, arbeidsmåte) +
  en pekende seksjon til denne filen. 47 nå-utdaterte
  `CLAUDE.md-status`/`CLAUDE.md sin statuslogg`-kryssreferanser rettet
  til `CHANGELOG.md` på tvers av `ARCHITECTURE_DECISIONS.md`/
  `FEATURE_BACKLOG.md`/`DESIGN_SYSTEM.md`. Ren dokumentasjonsendring,
  ingen kode/migrasjon/utrulling.
- **"Bruk course handicap-justering" skjult for ordinære formater, LIVE
  (2026-08-02), samme dag:** brukeren påpekte at bryteren er unødvendig
  for rene Slagspill-/Stableford-runder — bedt om å skjules (ikke
  slettes) og alltid holdes funksjonelt PÅ for disse to formatene.
  `frontend/components/new-round.tsx`: ny `ORDINARY_FORMATS = new
  Set(["slagspill", "stableford"])`, en `useEffect` i `NewRound` som
  tvinger `useCourseHcp` tilbake til `true` hver gang `format` går inn i
  dette settet (dekker et bytte FRA et annet format der bryteren var
  slått av), og selve `ToggleRow` for "Bruk course handicap-justering"
  gatet bort i `Step2` for disse to formatene. Ren UI-/state-endring —
  `buildAllowanceOverride()` er urørt, returnerer fortsatt `null` (ingen
  override sendt) når alt er på standardverdi, akkurat som før. Ekte
  typesjekket build kompilerte rent. **Rullet ut live**, ingen
  migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
  også `teecup_api` som vanlig bivirkning). Begge containere boot-et
  rent, `/health`/`/my-rounds/new` → 200, `teeoff.no` upåvirket.
- **"HCP-prosent"-feltet skjult når "Bruk handicap" er av, LIVE
  (2026-08-02), samme dag, oppfølging av forrige punkt:** brukeren
  påpekte at feltet ikke ga mening synlig når HCP uansett er slått av,
  og presiserte eksplisitt at dette skal gjelde universelt (alle
  formater), ikke bare de to ordinære. Løst med én eneste betingelse
  (`{useHcp && (...)}`) rundt feltet i `Step2` — siden ALLE spilleformer
  deler nøyaktig denne ene komponenten for "Avanserte handicap-
  innstillinger", dekker denne ene endringen automatisk hver
  spilleform-visning uten noen per-format-liste (ulikt forrige punkts
  `ORDINARY_FORMATS`-gating, som var format-spesifikk med hensikt).
  Bevisst IKKE nullstilt `hcpPercent`-state når feltet skjules — en
  eventuell gjenværende verdi er funksjonelt harmløs (backend sin
  `compute_and_store_side_handicaps` returnerer tidlig når
  `use_handicap` er usann, FØR `strategy`/prosent leses i det hele
  tatt), så ingen ekstra state-rydding var nødvendig utover selve
  skjulingen. Ekte typesjekket build kompilerte rent. **Rullet ut
  live**, ingen migrasjon, `docker compose up -d --build
  teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
  Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200,
  `teeoff.no` upåvirket.
- **Reell inkonsistens funnet av bruker og fikset, LIVE (2026-08-02),
  samme dag:** brukeren viste et skjermbilde av Fourball med "Bruk
  handicap" AV, men "Bruk course handicap-justering" OG "Bruk
  matchplay-handicap" fortsatt vist som PÅ — begge sub-bryterne var kun
  gatet på format (`ORDINARY_FORMATS`/`twoSided`), ALDRI på selve
  `useHcp`-hovedbryteren. Ingen funksjonell bug (backend sin
  `compute_and_store_side_handicaps` returnerer tidlig når
  `use_handicap` er usann, FØR disse leses — samme resonnement som
  forrige punkts `hcpPercent`), men en reell visuell selvmotsigelse.
  Fikset ved å legge til `useHcp &&` foran begge betingelsene i `Step2`
  (`frontend/components/new-round.tsx`) — siden ALLE 16 formater deler
  denne ene komponenten, dekker denne ene endringen konsistent
  atferd for hver spilleform uten en per-format-sjekk. Når "Bruk
  handicap" er av, vises nå KUN selve hovedbryteren i "Avanserte
  handicap-innstillinger" — ingen sub-innstillinger, uansett format.
  **Samme bug funnet i en parallell, ikke-forespurt fil under
  undersøkelsen** (`tournament-program.tsx` sin `CreateSessionCard`,
  org-turnering-øktoppsettet) — samme tre brytere, samme mangel på
  `useHcp`-gating, og der attpåtil UTEN `twoSided`-gating på
  "Bruk matchplay-handicap" i det hele tatt (vises alltid, uavhengig av
  format). Flagget til bruker, IKKE fikset i denne runden (egen fil,
  ikke det som ble spurt om). Ekte typesjekket build kompilerte rent.
  **Rullet ut live**, ingen migrasjon, `docker compose up -d --build
  teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
  Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200,
  `teeoff.no` upåvirket.
- **Samme bug fikset i tournament-program.tsx (org-turneringens
  økt-oppsett), LIVE (2026-08-02), samme dag:** bruker bekreftet at den
  flaggede parallellfilen også skulle rettes. Enklere fiks enn i
  new-round.tsx: `SessionFormat` her har KUN 10 to-sidede lag-format
  (foursome/greensome/scramble_2/scramble_4/fourball/singles/chapman/
  shamble/money_ball/high_low_high) — org-lagturneringer har ingen flat
  individuell-formatvariant (den hører hjemme i den separate
  individuell-turnering-modellen, ADR-037), så det finnes ingen
  `ORDINARY_FORMATS`/`twoSided`-distinksjon å ta hensyn til her, ulikt
  den andre filen. Løsning: alle tre (`Bruk course handicap-justering`,
  `Bruk matchplay-handicap`, `HCP-prosent`) samlet i én
  `{useHandicap && (<>...</>)}`-blokk i `CreateSessionCard`
  (`tournament-program.tsx`) — bekreftet at dette er ENESTE stedet i
  filen disse tre bryterne finnes (ingen separat rediger-økt-variant med
  samme mangel). Ekte typesjekket build kompilerte rent. **Rullet ut
  live**, ingen migrasjon, `docker compose up -d --build
  teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
  Begge containere boot-et rent, `/health`/`/dashboard` → 200,
  `teeoff.no` upåvirket.
- **Reell Tailwind-cascade-bug funnet og fikset, BROWSERVERIFISERT MED
  PRESISE DOM-MÅLINGER, LIVE (2026-08-02), samme dag:** brukeren
  rapporterte at siden ikke lot seg scrolle helt ned på Steg 2 i
  rundeoppsett-veiviseren, med et skjermbilde som kuttet av rett før
  "Antall hull"-knappene. IKKE en scroll-bug -- root cause presist
  diagnostisert i en isolert scratch-nettleserøkt (samme mønster som
  resten av uken): `main`s className hadde `pb-32` (128px, reservert
  klaring for den faste Tilbake/Neste-linjen) OG `sm:py-8` (32px,
  ment for generell luft på større skjermer) samtidig. Tailwind
  emitterer responsive (`sm:`)-varianter i en EGEN media-blokk ETTER
  grunnklassene i den kompilerte CSS-en -- så `sm:py-8` vant cascaden
  over `pb-32` på ALLE skjermer 640px og bredere, UANSETT rekkefølge i
  selve className-strengen. Bekreftet presist med `getComputedStyle`:
  `padding-bottom` var faktisk 32px, ikke 128px, ved en bred viewport
  (1998px) -- mens en smal mobilviewport (390px, under `sm:`-grensen)
  fortsatt fikk riktige 128px, noe som forklarer hvorfor bugen var usett
  til nå (all tidligere browserverifisering denne uken har vært på
  390px). Effekten: ved bredder ≥640px der totalt innhold tilfeldigvis
  var kortere enn viewporten, fikk siden RETT OG SLETT ikke lov til å
  scrolle langt nok til å avdekke "Antall hull"-knappene fullt ut --
  den faste navigasjonslinjen dekket de nederste ~58 av 64 pikslene.
  **Fikset** ved å dele opp de vertikale paddingene til KUN topp
  (`py-6`→`pt-6`, `sm:py-8`→`sm:pt-8`) slik at ingenting lenger kan
  konkurrere med `pb-32` om `padding-bottom` uansett skjermbredde --
  `pb-32` er nå den ENESTE kilden til bunn-klaring. Grep'et gjennom HELE
  frontend-treet etter samme `pb-N`+`sm:py-N`-mønster -- kun dette ene
  stedet (`new-round.tsx`), ingen andre skjermer rammet.
  **Verifisert presist, ikke bare "ser bedre ut":** eksakte
  `getBoundingClientRect()`/`getComputedStyle()`-målinger FØR fiksen
  (padding-bottom 32px, "18 hull"-knappen fra y=1378 til y=1442, fast
  navigasjon fra y=1384 -- 58px reell overlapp bekreftet med tall, ikke
  bare visuelt) og ETTER (padding-bottom 128px, full synlig klaring,
  scrollHeight økte fra 1474 til 1570px -- nøyaktig de manglende 96px
  = 128-32). Regresjonssjekket på 390px mobilviewport (uendret riktig
  oppførsel, som allerede fungerte). Ekte typesjekket build kompilerte
  rent (måtte rette en selvpåført JSX-kommentar-bug underveis -- en
  bokstavelig `*/`-sekvens inni kommentarteksten min egen selv lukket
  kommentaren for tidlig, rettet ved å omformulere).
  **Rullet ut live**, ingen migrasjon, `docker compose up -d --build
  teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
  Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200,
  `teeoff.no` upåvirket.
- **Midlertidige spillere (gjester på frittstående runder): full runde
  ferdig -- e-post, navnesplitt, retroaktiv kobling, autofyll, "gjenkjenn
  gjest"-oppslag, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
  (2026-08-03):** brukeren ba om seks ting samtidig (e-post som valgfritt
  felt manglet, navnesplitt for-/etternavn, retroaktiv lagring/kobling av
  gjeste-runder til en fremtidig konto, automatisk e-post med scorekort+
  statistikk+invitasjon ved fullføring, autofyll fra søkefeltet inn i
  gjesteskjemaet, og "gjenkjenn en tidligere registrert gjeste-e-post") og
  ba eksplisitt om innspill på hva mer som var lurt. Grundig
  kodeutforskning FØR noe ble bygget avdekket presist hva som faktisk
  manglet: `guest_email` fantes ALLEREDE i backend (2026-07-26), bare
  aldri eksponert i selve "legg til gjest"-skjemaet; `guest_name` var ETT
  enkelt tekstfelt brukt i 10+ spørringer.
  **Tre load-bærende avklaringer bekreftet av bruker** (AskUserQuestion):
  (A) en retroaktivt koblet runde (noen registrerte deg som gjest FØR du
  hadde konto) teller IKKE automatisk mot faktisk HCP -- `exclude_from_
  handicap` settes til `true` som default ved kobling, personen må selv
  slå den på (samme mekanisme ADR-038 allerede bygget, kun default
  snudd for denne ene banen inn). Begrunnelse: en org-turnering sin
  eksisterende `link_player_by_email`-presedens (ADR-017) er trygg fordi
  org-scoring aldri teller mot faktisk HCP -- frittstående runder GJØR
  det (ADR-038), så blind auto-inkludering ville latt en fremmed
  påvirke noens HCP uten samtykke. (B) treffer en gjeste-e-post en
  EKSISTERENDE konto, opprettes gjesten likevel -- kobles ved neste
  innlogging, ikke et eget "denne personen har konto"-forgreiningssteg
  (enklere, bevisst valgt fremfor det opprinnelig anbefalte). (C) bygget
  i én samlet runde (skjema→backend→frontend→e-post lagvis, samme
  disiplin som ellers i prosjektet).
  **Migrasjon `052_guest_name_split.sql`:** `round_participant.
  guest_first_name`/`guest_last_name` lagt til, `guest_name` beholdt
  UENDRET som et auto-synkronisert, lagret "fullt navn" (samme mønster
  som `app_user.display_name` synkes fra `first_name`/`last_name`,
  2026-07-25-bugfiksen) -- unngikk å måtte røre alle eksisterende
  SELECT-steder. Backfill: "første ord = fornavn, resten = etternavn".
  **Retroaktiv kobling** (`app/routers/auth.py`, ny
  `_link_round_participants_by_email`, kalt fra BEGGE innloggingsveiene
  -- magic-link og passord, samme "kjør trygt på hver innlogging"-
  idempotens som `link_player_by_email`): INGEN SECURITY DEFINER-bro
  trengs siden `round`/`round_participant` ikke har RLS (ADR-033
  Beslutning A) -- en rett UPDATE er nok. `NOT EXISTS`-vaktet mot en
  allerede eksisterende ekte deltaker-rad på samme runde (ville ellers
  brutt migrasjon 027 sin UNIQUE-indeks). **Reell bug funnet OG fikset
  UNDER scratch-testing:** en første versjon nullet `gender` sammen med
  de andre gjeste-feltene ved kobling -- krasjet med en NOT NULL-
  violation, siden `gender` er en påkrevd snapshot for ALLE deltakere
  (brukt til utslags-rating-oppslag), ikke bare gjester. Rettet ved å la
  `gender` stå urørt (uansett låst mot videre endring av `update_
  participant` sin egen `guest_only_fields`-sjekk så snart `user_id` er
  satt).
  **HTML-e-post** (`app/email.py`, FØRSTE i appen): `_send_sync` fikk en
  valgfri `html_body`-parameter (`EmailMessage.add_alternative`,
  multipart/alternative -- ekte tekst-fallback bevart). Ny
  `send_round_summary_email` -- scorekort som HTML-tabell, statistikk
  gradert etter `stat_level`/individuell- vs. delt-ball (kalleren bygger
  `stat_lines`, e-post-modulen antar ingenting), ekte magic-link-
  innlogging (samme token-mønster som `send_scorecard_invitations`,
  ADR/CLAUDE.md 2026-07-28). **Navneformat (ufravikelig regel):**
  hilsenen er "Hei {fornavn}," -- direkte adressering, kun fornavn, aldri
  fullt navn. Trigget fra en ny `_send_guest_round_summaries`, kalt fra
  `complete_round` for hver deltaker med `guest_email` satt -- bygger
  scorekortet fra ENTEN `_build_participant_holes` (individuell) ELLER
  `_build_side_holes` (delt-ball, ADR-039), gjenbruker eksisterende
  hjelpefunksjoner uendret, ingen egen regnelogikk.
  **Known-guest-oppslag** (`GET /rounds/guests/known`, statisk rute
  plassert FØR `/rounds/{round_id}` i filen, samme mønster som
  `/rounds/stats/summary`): bevisst scoped til KUN den spørrende
  brukerens EGNE tidligere registrerte gjester (`WHERE r.owner_user_id =
  $1`) -- et globalt oppslag ville latt hvem som helst skrive inn en
  tilfeldig e-post og se navn/kjønn/HCP en HELT ANNEN organisator har
  registrert, en reell personvernlekkasje unngått FØR bygging (flagget
  proaktivt til bruker, bekreftet enig).
  **Frontend** (`round-detail.tsx`): `AddGuestForm` skrevet om -- separate
  Fornavn/Etternavn-felt (etternavn valgfritt), nytt E-post-felt med
  debounced known-guest-oppslag + en "Bruk disse opplysningene"-forslags-
  boks, `initialName`-prop fra søkefeltet (`AddParticipantForm`s `query`)
  autofyller navnefeltene ved "Legg til uten konto (gjest)" via en ny
  `splitName()`-heuristikk (samme "første ord/resten" som backfillen).
  `EditParticipantPanel` oppdatert til samme to-felts navnestruktur (var
  i ferd med å bli en regresjon siden backend ikke lenger godtar
  `guest_name` direkte). **Reelt, uforespurt funn fanget under
  implementering:** rundeoppsett-veiviseren (`new-round.tsx`) sender
  OGSÅ `guest_name` ved opprettelse av spillere i Steg 3 -- ville brutt
  hvis ikke oppdatert samtidig; løst med samme splitt-heuristikk direkte
  ved innsending (ingen UI-endring i wizarden denne runden, kun
  kontraktsrettelse).
  **Scratch-verifisert grundig, 41/41 sjekker** (isolert
  `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
  container, alle 52 migrasjoner kjørt friskt, samme mønster som hele
  prosjektet): navnesplitt+auto-synk, delvis PATCH bevarer det andre
  feltet, validering (verken/begge user_id+guest_first_name avvist),
  known-guest-oppslag (funnet for egen bruker, IKKE synlig for en annen
  organisator -- personvern-scopingen bekreftet presist), full
  fullførings-e-post-syklus (dev-log bekreftet både magic-link OG
  rundeoppsummering), retroaktiv kobling (user_id satt, gjeste-felt
  nullstilt, `gender` BEVART, `exclude_from_handicap=true`, `display_
  name` viser nå kontoens ekte navn), idempotent gjeninnlogging, OG et
  eget delt-ball-scenario (foursome) som bekreftet ingen krasj ved
  fullføring og korrekt sideoppsummering i e-post-loggen.
  **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP):
  hele "+ Medspiller"→"Legg til uten konto"-flyten klikket gjennom med et
  navn som ikke fantes -- bekreftet AUTOFYLL fungerte (Fornavn/Etternavn
  korrekt splittet), skrev inn en kjent gjeste-e-post og bekreftet
  "Vi fant Anna fra en tidligere runde..."-forslaget dukket opp, trykket
  "Bruk disse opplysningene" og bekreftet navn/kjønn ble overskrevet
  korrekt (HCP forble tomt, siden Anna ikke hadde noen registrert),
  sendte inn skjemaet og bekreftet gjesten dukket opp i spillerlisten
  UTEN konsollfeil, åpnet rediger-panelet og bekreftet det viser samme
  to-felts navnestruktur korrekt forhåndsutfylt. E-postens faktiske
  HTML/tekst-innhold generert og inspisert direkte (ikke bare at
  utsendingen ble trigget) -- bekreftet riktig "Hei Kari,"-hilsen,
  korrekt hull-tabell inkl. et uspilt hull vist som "–", og korrekt
  byggede statistikklinjer.
  **Rullet ut mot ekte systemer 2026-08-03**, bruker bekreftet
  eksplisitt (plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige
  regel): migrasjon 052 kjørt mot ekte `teecup_db` (nye kolonner
  bekreftet, backfill verifisert nøyaktig mot de 3 eksisterende gjeste-
  radene i produksjon -- "Christer Heitun"→"Christer"/"Heitun", "Sigurd"
  →"Sigurd"/null, "Vidar Hoksrød"→"Vidar"/"Hoksrød" -- `test_isolation.sql`
  fortsatt 12/12), deretter `docker compose up -d --build teecup_api
  teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
  → 200, `GET /rounds/guests/known` anonymt ga korrekt `401` gjennom hele
  produksjonsstacken (ikke en rå 404), `teeoff.no` upåvirket.
- **Score-fanens gjennomgang endte i to runder etter samme rapport
  (2026-08-03):** bruker sendte skjermbilde av det brede
  ScorecardGrid-et og påpekte at det ikke var intuitivt hvor man skal
  trykke for å registrere score. Første prompt til V0 var snevert
  (kun synlig trykk-affordance på tomme celler). Bruker ba om en
  friere runde 2: "Kan det være mulig å presentere scoreføringsvinduet
  på en helt annen måte, uten at informasjon [...] blir borte" +
  "det ser generelt ikke særlig bra ut på mobil [...] tar for stor
  plass i bredden. Gi V0 et friere spillerom." Ny, åpen prompt skrevet
  (mål+datainventar-sjekkliste i stedet for layoutdiktat, eksplisitt
  IKKE rørt ved den allerede gode fullskjerm-registreringsveiviseren
  -- kun oversiktsgridet). Grundig kodeutforskning FØR prompten ble
  skrevet bekreftet at `ScoringWizard` (fullskjerm-veiviser) allerede
  matcher spec §2 og ikke skulle røres, og at `ScorecardGrid` (§1) sin
  hver-celle-er-en-knapp-logikk allerede fungerte -- kun tomme cellers
  manglende visuelle "trykk her"-signal var det faktiske problemet i
  runde 1.
  **V0-eksport "zip 27" mottatt og portet, LIVE (2026-08-03):**
  `components/score-overview.tsx` fra zip-en var en velfungerende,
  selvstendig mock-datavisning (hull-fokuserte spillerkort, dashet
  "+"-affordance for uregistrerte hull, sammenleggbart fullt
  scorekort, hurtig-hopp-hullstripe) som eksplisitt respekterte
  omfanget -- V0 gjenkjente selv at registreringsvinduet ikke skulle
  røres og mocket kun et representativt stedfortreder-vindu for det.
  Portet MANUELT mot ekte data i stedet for å ta filen direkte (samme
  rutine som alltid): ny `PlayerHoleCards`-komponent i
  round-detail.tsx gjenbruker ekte `Player`/`ApiHole`/`ApiFormatResult`
  -typene, ekte `goPrev`/`goNext` (wrap-around, ikke V0s klampede
  prev/next), ekte `sumForPlayer`/`stablefordPoints`/`golfTermForScore`
  -hjelpere, og EKSAKT samme match-fargelegging/ikke-tellende-partner-
  logikk som `ScorecardGrid` allerede brukte (`formatResult.holes[].
  entries[].counted`) -- ingen egen reimplementert kopi av denne
  logikken. `ScorecardGrid` selv er UENDRET, bare flyttet til en ny
  "Vis hele scorekortet"-bryter (default lukket) i stedet for å være
  standardvisningen -- ingen informasjon fjernet, kun omprioritert.
  `ScorecardCell` fikk en ny valgfri `size`-prop ("sm"/"lg") for å la
  det store spillerkortet gjenbruke SAMME form-/fargekomponent som det
  kompakte gridet, i stedet for en divergerende kopi.
  **Én reell avviksfeil funnet og rettet FØR utrulling:** V0s
  hurtig-hopp-hullstripe brukte `size-9` (36px) -- under CLAUDE.md sitt
  ufravikelige 44px-trykkgulv. Rettet til `size-11` FØR
  browserverifisering, ikke bare typesjekk.
  **Scratch-/browserverifisert grundig** (isolert DB+MinIO+API+
  frontend-container, samme mønster som alltid) på en ekte 390px mobil
  viewport: singel-runde (registrerte hull vs. dashet "+"-affordance
  på uregistrert hull, netto/Stableford-poeng, Ut/Inn/Sum),
  fourball-runde (lag-farget venstrekant, "Vant hullet"-merke lest
  direkte fra ekte `formatResult`, ikke tie-brutt reimplementering),
  ekte trykk-gjennom til den URØRTE `ScoringWizard`-en bekreftet (skår
  registrert, veiviseren avanserte automatisk til neste spiller, kortet
  bak oppdaterte seg live), og "Vis hele scorekortet" bekreftet å vise
  det uendrede gamle gridet korrekt. "Spillere og runde"-fanen
  (urørt) verifisert uendret.
  **Rullet ut live 2026-08-03**, ren frontend-endring (ingen
  migrasjon), bruker bekreftet eksplisitt før utrulling. Zip-en slettet
  etter merge, per etablert rutine.
  **Bevisst utenfor omfang, ikke bygget:** delt-ball-formater
  (foursome/greensome/scramble/chapman -- `SideScorecardGrid`) har
  ingen tilsvarende kortvisning ennå -- V0-mockupen dekket kun
  spiller-rad-tilfellet (`ScorecardGrid`), og det fantes ingen
  tilsvarende referanse å bygge en "side-kort"-variant mot. Fortsatt
  det brede gridet for de formatene, uendret.
- **Fem separate brukerpunkter fra samme økt, ALLE BYGGET, SCRATCH-
  VERIFISERT OG LIVE (2026-08-03), samme dag som score-kortrunden
  over:**
  1. **Ekte bug funnet via opplastet skjermopptak, rettet:** brukeren
     rapporterte at "snarveien" (Administrer-dialogens "Spillere og
     runde"-lenke, `?tab=manage`) ikke virket fra Score-fanen, men
     virket fra Scorekort/Leaderboard. Video analysert bilde for bilde
     (ffmpeg) -- bekreftet presist: URL-en endret seg riktig til
     `?tab=manage`, men selve fanen ble stående på "Score". Rotårsak:
     `useState(initialTab)` i round-detail.tsx leste kun
     `useSearchParams()` ved FØRSTE mount -- en navigasjon FRA Score-
     siden til seg selv med kun en ny query-parameter er ingen ekte
     remount i Next.js App Router, så verdien ble aldri regnet på nytt.
     Fra Scorekort/Leaderboard virket det fordi det ER en remount
     (annen rute). Fikset med en `useEffect` som reagerer på selve
     parameter-ENDRINGEN (ensrettet -- hopper TIL "manage", tvinger
     aldri tilbake til "score"). Reprodusert i scratch FØR fiksen
     (bekreftet feilen), verifisert etterpå fra alle tre
     inngangspunkter (Score/Scorekort/Leaderboard) + at lokal
     "Score"-pille fortsatt virker uendret.
  2. **`EditRoundPanel` sin spilleform-bryter utvidet fra 3 til 16
     format:** var blitt hengende igjen på stroke/match/stableford fra
     FØR de åtte nye formatene (ADR-039) ble bygget -- en ren
     forglemmelse. Rettet BÅDE i backend (`RoundUpdate.play_format`
     i rounds.py hadde samme gamle 3-verdis-Literal) OG frontend (ny
     `EDITABLE_PLAY_FORMATS`-liste, samme etiketter som new-round.tsx).
     Verifisert live i scratch: byttet en pågående runde fra Slagspill
     til Fourball midt i runden, `SidesPanel` sin eksisterende "opprett
     sider først"-melding tok over korrekt uten noen krasj.
  3. **`completed_at`-feltet for å overstyre fullført-tidspunkt manuelt
     bekreftet FORTSATT TIL STEDE, uendret siden 2026-07-24** -- ren
     kodeverifisering, ingen endring nødvendig. Feltet vises (med
     hensikt) kun ETTER at "Fullfør runde" er trykket -- forvirringen
     var trolig et resultat av bug (1) over (kom seg aldri til
     "Spillere og runde" for å finne knappen).
  4. **`round-scorecard.tsx` sitt individuelle scorekort: kolonnetekst-
     overflow rettet + 8 nye detaljrader lagt til** (Fairway/Putt/
     GIR/Innspill/Chip/Bunkerslag/Straffeslag/Anywayslag), alle med
     forkortede radetiketter + kompakte lucide-ikoner (bøyde piler for
     Fairway, rette piler+blink for Innspill, sjekkmerke for GIR) --
     samme golfscore-språk som resten av appen. Data var allerede
     tilgjengelig i `RoundHoleOut` (bare ikke lest av denne siden før).
     Vises kun når minst ett hull faktisk har dataen (samme "vis kun
     det som finnes"-prinsipp). GIR-formelen gjenbruker EKSAKT samme
     `score - putts <= par - 2`-regel som round-stats.tsx/round-
     detail.tsx allerede bruker.
  5. **Rundeleaderboard (V0-eksport "zip 28") -- IKKE V0 sin skyld at
     forrige versjon "så rart sammenskrudd ut", presisert til bruker:
     forrige leaderboard var HÅNDKODET (ikke V0), bygget 2026-07-26 da
     brukeren gikk tom for V0-credits. Zip 28 dekket alle hullene i
     V0-prompten (skrevet samme økt) presist -- portet MANUELT mot ekte
     data, IKKE en full filerstatning: gjenbrukte eksisterende
     `rankEntries`/`ValueMark`/`RankBadge`/`ModeToggle`/`HoleStrip`/
     matchstatus-tug-of-war-baren (allerede nær identisk med V0s egen
     visuelle idé, lavere risiko å beholde enn å skrive om). Kun TRE
     genuint manglende/feil biter bygget:
     - **High-low-high** falt tidligere inn i den generiske to-sidede
       "X UP"-matchstatusen (bekreftet FEIL for dette formatet -- HLH
       er 0-2 poeng-per-hull, ikke hull-ledelse). Ny `HighLowSection`:
       lav/høy-duell-rollen per spiller per hull utledes fra
       `entries[].points` (allerede MIN/MAKS av lagkameratenes
       Stableford-poeng den hullet, samme regel som
       `handicap_engine.py` sin `high_low_high_points_for_hole` --
       ingen backend-endring trengtes). Verifisert tall-for-tall i
       scratch: per-hull lav/høy-dueller summerte EKSAKT til
       `hlh_points_a`/`hlh_points_b` sine totaler.
     - **Bingo Bango Bongo/Københavner** sitt "Poeng"-tall var
       `ApiLeaderboardEntry.total_points` -- ALLTID generisk Stableford
       fra backend, uansett format (bekreftet reell bug: disse to
       formatene har sine EGNE poengsystem, aldri vist noe sted).
       Erstattet med faktiske `bbb_points`/`copenhagen_points` fra
       format-result FØR rangeringen bygges. Fanget og rettet EN egen
       bug UNDER scratch-verifiseringen: en deltaker med 0 ekte
       BBB/Københavner-poeng manglet fra dict-en (ikke `0`, fraværende
       nøkkel) -- første versjon falt da feilaktig tilbake til generisk
       Stableford i stedet for `0`. Rettet, verifisert: Københavner-
       poeng summerte EKSAKT til 6 per hull (regelen), BBB viste riktig
       `0` for en spiller uten noen bingo/bango/bongo-vinst.
     - **Shamble/Money Ball** sitt offisielle LAGRESULTAT (hele runden
       = ett lag) lå tidligere IKKE synlig noe sted -- kun spillernes
       råtall. Ny `TeamResultSection`: lagets til-par (fra
       `shamble_team_score`/`money_ball_team_score` + hullenes par-sum,
       begge allerede i format-result), pluss en NY flight-gruppe-
       sammenligning (henter søsken-rundenes EGNE format-result via et
       nytt `useFlightTeamStandings`-kall) mot andre lag i samme utgang
       -- tidligere fantes INGEN slik sammenligning i det hele tatt.
       Verifisert tall-for-tall i scratch (Shamble beste-2-av-3, Money
       Ball sin rotasjon) mot to separate flight-koblede runder.
     `RoundLeaderboardMini` (forhåndsvisningen i "Spillere og runde")
     oppdatert til å skjule seg for Shamble/Money Ball også (samme
     "ikke vis noe fremfor å vise en misvisende rangering"-prinsipp den
     allerede fulgte for to-sidede format).
     Regresjonstestet: vanlig Slagspill og Fourball uendret (identisk
     visning/matchstatus som før refaktoreringen som lot
     `MatchStatusSection` motta `result` som prop i stedet for å hente
     selv).
     **Rullet ut sammen med de fire punktene over, 2026-08-03**: BÅDE
     `teecup_api` (RoundUpdate.play_format-utvidelsen) OG
     `teecup_frontend` bygget og restartet, begge boot-et rent,
     bekreftet via kompilert bundle-grep + en faktisk backend-
     spørring mot den nye Literal-en. Zip-en slettet etter merge.
- **Rundeleaderboard (zip 28) — punkt 5 over ERSTATTET, full omskriving
  (2026-08-03, samme dag):** brukeren testet en vanlig Slagspill-runde
  (den klart vanligste stien) etter forrige punkts utrulling og så
  NULL visuell endring, sendte skjermdump og spurte hva som hadde
  skjedd. Årsak: forrige punkt gjenbrukte bevisst de gamle håndkodede
  `RankBadge`/`ValueMark`/`ModeToggle`/`HoleStrip`/matchstatus-baren for
  individuell/to-sidet visning fordi jeg vurderte dem som "strukturelt
  nære nok" V0s tegning — akkurat den stien brukeren testet var derfor
  uendret. Brukerens eksplisitte, ordrette svar (bevart for ettertiden):
  *"Ja, jeg vil ABSOLUTT at du implementerer V0 sin tolkning av hvordan
  Leaderboardet skal se ut. Jeg vil ikke at du skal gjøre personlige
  endringer eller holde tilbake på noe i det hele tatt når det gjelder
  dette."* Lagret som stående feedback-memory (`v0-full-fidelity`) —
  gjelder ALL fremtidig V0-integrering i dette prosjektet, ikke bare
  denne filen.
  Fulgte opp med en fullstendig omskriving av `round-leaderboard.tsx`,
  denne gangen med HVER V0-komponent portet manuelt mot ekte data (ikke
  gjenbrukt): `Badge`/`MetricPill`/`ScoreMark`+`classify`/
  `IndividualBoard`/`MetricToggle`/`HoleByHole`/`Legend` (individuelt),
  `TwoSidedBoard`/`SideName`/`HoleWinners` (to-sidet), `HighLowBoard`/
  `DuelRow` (High-low-high), `TeamFlightBoard` (Shamble/Money Ball) — de
  gamle håndkodede visningskomponentene slettet i sin helhet, ikke
  beholdt ved siden av.
  Fem reelle bugs funnet og rettet UNDER omskrivingen (ikke antatt
  riktig fra en ren typesjekk alene — funnet ved skjermdump-inspeksjon i
  scratch):
  1. `courseHcp` var meningsløs plassholder-matematikk
     (`strokes_received * 18` uansett hva slagindeksen faktisk var) —
     V0s mockup antok klientside-utregning av spillehandicap, men
     backend returnerer allerede korrekt per-hull `strokes_received`
     direkte. Rettet: `IndividualPlayer` fikk et ekte
     `strokesReceived`-array lest direkte derfra, netto/Stableford
     regnet fra DET, ikke gjenutledet.
  2. `hcpIndex` var hardkodet `null` alltid — feltet
     (`handicap_index_snapshot`) fantes allerede i det hentede
     `/rounds/{id}`-svaret, bare ikke lest av filens egen, snevrere
     lokale type. Utvidet typen, lest verdien.
  3. `TeamFlightBoard` sin råscore-pille hadde et komma-operator-uttrykk
     som alltid evaluerte til hardkodet `0` uansett faktisk score.
     Rettet med den allerede korrekte `grossToPar(m, holes)`.
  4. CSS-trunkeringsbug i `SideName`: det ledende laget sin flagg-
     ikonrad manglet `min-w-0` på det indre flex-elementet, så navnet
     ("Lag Bjørk") trunkerte til nesten ingenting ("L..") mens det ikke-
     ledende laget viste seg fullt, selv i like brede `flex-1`-
     containere. Rettet.
  5. Rotårsaken til (4) var egentlig et feil datavalg: brukte
     `match_status_text` (en backend-streng som ALLEREDE inkluderer
     lagnavnet, designet for en annen UI-plassering) i V0s
     overskrifts-slot, som var designet for en mye kortere tekst siden
     lagnavn vises separat via `SideName`. Rettet ved å utlede V0s
     egen korte frase ("2 opp"/"Dormie 2 opp"/"Ferdig 2&1"/"Vant 2
     opp"/"Delt"/"Alt likt") direkte fra de rå numeriske feltene
     (`match_lead`/`match_holes_played`/`match_holes_remaining`/
     `match_is_dormie`/`match_is_closed`) — fortsatt serverens
     autoritative tall, bare riktig tekstformat.
  **Scratch-verifisert på alle åtte formater** (Slagspill, BBB,
  Københavner, High-low-high, Fourball, Shamble, Money Ball, pluss
  `RoundLeaderboardMini`-regresjon) med tall håndverifisert mot samme
  testdata som resten av ADR-039-arbeidet — se punkt 5 over for selve
  tallutregningen, uendret av denne omskrivingen. **Rullet ut live
  2026-08-03** (kun `teecup_frontend` bygget/restartet — backend-
  endringen fra punktene over var allerede live). Scratch-miljøet
  (DB/rolle/MinIO/API-/frontend-containere) ryddet opp etter bruk.

Neste steg:
0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet
   (2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen
   konsoll-feil. Ikke hvert enkelt interaksjonsdetalj (f.eks. gjeste-
   kjønnsendringens reaktive utslagsfilter) klikket gjennom stykke for
   stykke, men grunnleggende rendring/lasting er bevist, ikke lenger
   bare typesjekket.
0b. **Rundeleaderboard — nå FAKTISK nettleser-bekreftet (2026-07-27):**
   brutto/netto/poeng-veksling testet direkte i nettleseren, viste
   korrekte tall og riktig form/farge-språk.
1. **Ferdig, kun for historikk:** dashbord-redesign (ADR-035) og
   venner/kategorisert deling fase 1 (ADR-036) — begge designet
   2026-07-25 og siden BYGGET, SCRATCH-VERIFISERT OG RULLET UT LIVE
   samme dag (se status over). Venner fase 2 (rundevisibilitet) og
   fase 3 (ekte medspillere) er fortsatt ikke bygget.
3. **Frittstående rundeføring + detaljert statistikk — ADR-033, skrevet
   2026-07-22 og siden BYGGET/ITERERT KONTINUERLIG hver dag fram til
   2026-07-29 (se hele status-loggen over -- dette punktet er nå appens
   klart mest utviklede område, ikke lenger "ikke bygget").** Beholdt her
   UENDRET som historisk kontekst for selve grunnbeslutningen fra
   2026-07-22 (hvorfor/hvordan ADR-033 ble designet) -- ikke som en
   påstand om at arbeidet fortsatt gjenstår. Brukeren avklarte 2026-07-22
   at dette skulle bli appens HOVEDFOKUS (turneringsoppsett skulle bli
   ekstremt enkelt ETTER dette var på plass) — den største enkeltbeslutningen
   i prosjektet siden ADR-001, og det har stemt. Fire load-bærende
   delbeslutninger avklart eksplisitt
   (AskUserQuestion): nytt parallelt eierskapsmønster keyet på `user_id`
   (gjenbruker det allerede beviste `plain_connection()`-mønsteret fra
   personlig profil/HCP-historikk/sekundær e-post — IKKE en skjult
   personlig organisasjon), fast sett navngitte statistikk-felt per hull
   (ikke fri slag-for-slag-logg), full WHS HCP-indeksberegning bygges NÅ
   (ikke utsatt), shotgun-start blir egen separat ADR-034. De tre
   HCP-PDF-ene lest i sin helhet — Score Differential/Course
   Handicap/Net Double Bogey-formlene er hentet derfra (kilde, ikke
   hukommelse).
   **Oppdatert samme dag:** brukeren bekreftet banedata-spørsmålet (LIVE
   oppslag mot teeoff, ikke import — Beslutning C) og lastet opp en
   FJERDE PDF, den offisielle "WHS Rules of Handicapping 2024"
   (USGA/R&A) — lest i sin helhet, løste presist det som manglet: 9-
   hulls-minimumsregelen (Rule 2.2, IKKE bare "minst 9 hull" — en
   9-hulls-runde krever ALLE 9 av et faktisk ratet sett, en 18-hulls-
   runde krever minst 10 av 18), full HCP-indeks-pipeline (Score
   Differential, Net Double Bogey, expected-score for uspilte hull, beste
   8-av-20, Low Handicap Index, soft/hard cap), OG en ny, presis
   9-hulls-Course-Handicap-formel som HALVERER indeksen først (Rule
   6.1b) — bevisst holdt atskilt fra det eksisterende front_9/back_9-
   øktoppsettet i turnering-flyten (ADR-008), som løser et annet problem
   av andre grunner. ADR-033 er dermed fullt kildebelagt, ingen store
   åpne HCP-regelspørsmål gjenstår.
   **Bygging påbegynt samme dag:** brukeren instruerte at "Expected
   Score" (Rule 3.2b, upublisert WHS-formel) erstattes gjennomgående av
   WHS sin egen "Net Par"-term for uspilte hull — løser samtidig hele
   9-hulls-runde-spørsmålet uten en egen separat formel (Rule 5.1b droppet
   bevisst). Hele HCP-indeks-motor-komponenten (Net Double Bogey/Net Par,
   Adjusted Gross Score, Score Differential, Handicap Index fra beste-
   8-av-20 m/opptrappingstabell for <20 runder, Low Handicap Index, soft/
   hard cap, 9-hulls Course Handicap) er BYGGET og TESTET i
   `handicap_engine.py` — 41/41 tester (`test_handicap_engine.py`, kjørt
   uten pytest da det ikke er installert i miljøet), flere verifisert mot
   regelbokens egne tallregneeksempler (Rule 5.2a, Rule 5.1c, Diagram 5.8,
   Diagram 3.1b). Ren Python, ingen DB/API/frontend rørt ennå — matcher
   ADR-005s "test i isolasjon FØR resten".
   **Deretter, samme dag:** full databasemigrasjon skrevet
   (`020_personal_rounds.sql`, sju nye tabeller: `round`/
   `round_participant`/`round_hole` + fire for en global egendefinert-
   bane-katalog) og scratch-verifisert (9 sjekker som
   `teecup_app_scratch`, ikke superbruker — CHECK-constraints, XOR
   user_id/guest_name, kaskade-sletting, GIR-derivering bekreftet mot
   ekte data). INGEN RLS på disse tabellene (Beslutning A). `test_
   isolation.sql` fortsatt 12/12.
   **Rullet ut mot ekte `teecup_db` 2026-07-22,** bruker bekreftet
   eksplisitt: alle sju tabeller bekreftet opprettet, `test_isolation.sql`
   fortsatt 12/12 mot ekte database.
   **Deretter, samme dag: fullt API-lag bygget** (`app/routers/
   rounds.py` — opprett/liste/hent/slett runde, legg til/fjern gjest-
   deltaker, PATCH hull-for-hull-stats, fullfør-runde som kjører hele
   Adjusted-Gross-Score→Score-Differential-kjeden). Fant og fikset et
   reelt hull underveis: `round_participant` manglet rating-tall-
   kolonner (kun tee-NAVN var snapshotet, ikke selve Course/Slope/Par) —
   ny migrasjon `021_round_participant_rating_snapshot.sql`. 26 scratch-
   sjekker + en egen, ekte teeoff-live-oppslag-test (Beslutning C
   bekreftet: ingen `course`-rad skrives noe sted). Rullet ut mot ekte
   `teecup_db`/`teecup_api` 2026-07-22, `/health`/`/dashboard` → 200,
   `teeoff.no` upåvirket. `frontend/next.config.mjs` fikk `/rounds`/
   `/personal-courses` lagt til i `rewrites()` proaktivt (ikke deployet
   ennå, ingen frontend bruker den før neste runde).
   **Notat fra bruker, IKKE designet:** planer om å måle lengde på slag +
   opplyse avstand til ulike punkter på banen (golf-GPS/rangefinder-type
   funksjonalitet) — krever geografiske/GPS-data ingen kilde har i dag
   (verken teeoff eller `personal_course`). Se FEATURE_BACKLOG.md for
   full detalj.
   **Frontend bygget og rullet ut 2026-07-23, samme rekkefølge-prinsipp
   (engine → skjema → API → frontend) fullført:** tre nye,
   organisasjonsuavhengige endepunkter lagt til i `rounds.py`
   (`GET /rounds/official-search`/`{slug}` — samme mønster som
   `courses.py` sitt org-scopede søk, men uten org-kontekst, siden
   frittstående runder ikke har noen; `GET /personal-courses/{id}` —
   detalj med utslag+kjønn, manglet fra søk-runden) og en fjerde,
   nødvendig tilføyelse oppdaget UNDER frontend-designet: `RoundOut` bar
   aldri hull-nivå-data i det hele tatt — ny
   `GET .../participants/{id}/holes`. Hull-PATCH-endepunktet endret til å
   returnere hele den oppdaterte raden i stedet for `{"ok": true}`.
   **Reelt kontraktsfunn, bekreftet i scratch FØR frontend stolte på det:**
   hull-PATCH er IKKE et ekte delvis-PATCH — den skriver ALLE felt ved
   hvert kall, så et utelatt felt (f.eks. putts) nullstilles stille hvis
   frontend ikke sender det. Løst ved at `round-detail.tsx` alltid slår
   sammen med gjeldende hull-data før hver PATCH, aldri sender et isolert
   feltnavn alene — verifisert eksplisitt med en egen scratch-test som
   FØRST beviste nullstillings-oppførselen, DERETTER beviste at
   merge-mønsteret unngår den.
   Nye sider: `/rounds` (liste), `/rounds/new` (bane-kilde teeoff/egen,
   søk-før-opprett for egen bane samme idé som org-banene, utslag filtrert
   på brukerens registrerte kjønn, dato/starthull/hull-antall), `/rounds/
   [id]` (deltaker-faner, hull-navigasjon fra starthull, slag/putt-
   tallvelgere i samme stil som `session-scorecard.tsx` sin `StrokePicker`,
   kølle/retning/innspill/chip/bunker/straffeslag bak en «flere detaljer»-
   utvidelse, GIR utledet og vist klientside — aldri lagret, kun beregnet
   fra `approach_result`+slag+putt ved lesing, fullfør-runde med HCP-
   differensial-sammendrag). Lenket fra dashbordet som «Egne runder»
   (bevisst adskilt navn fra det eksisterende «Mine runder», som gjelder
   turnering-deltakelse — samme ord, to ulike konsepter).
   **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
   isolert scratch-MinIO + engangs API-container, samme mønster som hele
   økten ellers): 22 sjekker som dekker egendefinert-bane-opprettelse med
   to utslag/kjønn, søk, detalj, PATCH-kontrakten (inkl. det bevisste
   nullstillings-beviset over), GIR-derivering, gjest-fjerning,
   kryss-bruker-autorisasjon (403), og full fullføring med differensial
   — PLUSS en egen, separat test av hele teeoff-baserte opprettelsesløpet
   mot den ekte kjørende `teeoff_api`-containeren (Borregaard Golfklubb),
   som bekreftet course_handicap ble beregnet riktig. Ekte typesjekket
   PRODUKSJONSBUILD (`docker build --target builder`, samme steg som
   `Dockerfile` faktisk bruker) kjørt og bekreftet — alle nye ruter listet.
   **Rullet ut live 2026-07-23**, bruker bekreftet eksplisitt: ingen
   migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
   begge containere boot-et rent, `/health`/`/dashboard`/`/rounds` → 200,
   `teeoff.no` upåvirket. **Frittstående rundeføring har dermed backend
   OG frontend live** — se ADR-033 i ARCHITECTURE_DECISIONS.md for full
   detalj om alle fire lagene (engine/skjema/API/frontend).
   **Frontend ERSTATTET med V0-designet versjon, samme dag:** brukeren
   påpekte berettiget at de tre skjermene over var hånd-kodet av meg i
   stedet for designet i V0, som er prosjektets etablerte mønster for
   ALL øvrig frontend. Bekreftet arbeidsmåten uendret (jeg skriver
   V0-prompten, brukeren kjører den og sender koden tilbake, jeg
   integrerer). Tre prompter skrevet (liste/opprett/hull-registrering,
   inkl. eksplisitt tilgjengelighetskrav i hver), tre zip-eksporter
   mottatt og diffet mot levende tre (samme "full re-eksport"-mønster som
   alltid — kun `round-card.tsx`/`own-rounds.tsx`/`new-round.tsx`/
   `round-detail.tsx` + rutene var reelt nye). Samme kjente V0-feil dukket
   opp igjen og ble hoppet over (`app/clubs/[id]/page.tsx`, feilnavngitt
   slug-parameter — identisk feil som ble rettet under ADR-018). Mine
   hånd-bygde komponenter ERSTATTET (ikke supplert), datalag skrevet om
   fra V0s mock til ekte fetch — samme mønster som enhver annen skjerm.
   Full detalj (inkl. de fire reelle tilpasningene utover ren om-kabling)
   i ADR-033. Ekte typesjekket produksjonsbuild kjørt og bekreftet, ingen
   ny interaktiv nettleser-test (intet slikt verktøy tilgjengelig,
   flagget eksplisitt). **Rullet ut live 2026-07-23**, bruker bekreftet
   eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte
   også `teecup_api` som vanlig bivirkning), begge containere boot-et
   rent, `/health`/`/dashboard`/`/rounds`/`/rounds/new` → 200, `teeoff.no`
   upåvirket. V0-zip-ene slettet fra prosjektroten.
   **Reell produksjonsbug rapportert av bruker (2 skjermbilder) og
   FIKSET samme dag:** `/rounds` var samtidig frontend-listesidens sti OG
   backend-APIets ressursprefiks — en verre, begge-veier-variant av
   ADR-016s medlemsside-felle. Statisk side vant over rewrite for det
   eksakte `/rounds`-treffet (klientens `fetch("/rounds")`/`POST /rounds`
   traff aldri backend, fikk Next sin egen HTML tilbake — derav "Klarte
   ikke å hente rundene dine"/"Klarte ikke å opprette runden"), mens
   rewriten vant over den DYNAMISKE `/rounds/[id]`-siden i motsatt
   retning (selve rundedetalj-siden var dermed fullstendig uoppnåelig,
   bekreftet direkte med `curl` før fiksen). Skjermbildets andre detalj —
   utslagsnavn "55/50/44/32" — ble sjekket direkte mot ekte `teeoff_api`
   og bekreftet Å IKKE VÆRE EN BUG (Tjøme Golfklubb sine faktiske
   utslagsnavn, lengde i hundremeter). Fikset ved å flytte alle tre
   frontend-rutene til et nytt, ikke-overlappende prefiks `/my-rounds/*`
   — API-et uendret på `/rounds`. `next.config.mjs` sin advarsel utvidet
   med denne nye varianten. Verifisert med `curl` mot ekte produksjon
   BÅDE før og etter (anonymt `GET /rounds` → nå korrekt backend-JSON,
   anonymt `GET /my-rounds/<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: offline-scoreregistrering er ALDRI
   browser-testet i praksis~~ — **ferdig 2026-07-28** (se status-punkt
   samme dag, "Turnering-scorekortets offline-flyt (ADR-028) FAKTISK
   browserverifisert"). Beholdt her kun for historikk.
7. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for
   ADR-020, korrigering-godkjenning fra motpart, video/1-til-1-meldinger
   (bevisst utsatt i ADR-025).
8. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/
   Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå.
9. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt),
   FORVENT en full re-eksport hver gang — diff mot live-treet i et
   scratch-område før noe pakkes ut over eksisterende filer, og sjekk om
   V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR
   integrering.
10. **Resterende hull fra 2026-07-28-gjennomgangen av frittstående runder**
    (det viktigste — faktisk HCP — er tettet, se ADR-038 over; offline-kø,
    punkt (a), er nå OGSÅ tettet, se status 2026-07-28 over — punktet
    beholdes her kun for historikk): (a) ~~offline-kø (ADR-028) er kun
    koblet til turnering-scorekortet~~ — ferdig, browserverifisert og
    live; (b) ~~ingen Stableford-poengberegning for frittstående runder
    (kun rå slag/differensial), inkl. en uløst "plukket opp ballen"-
    tilstand~~ — ferdig, scratch-/browserverifisert og live 2026-07-29,
    se status over (`play_format='stableford'` + `round_hole.picked_up`,
    migrasjon 038); (c) ~~rundedeling/visibility (ADR-036 fase 2, public/private/friends)
    fortsatt ikke bygget~~ — ferdig, browser-/scratch-verifisert og live
    2026-07-28, se status over (ADR-036 er dermed HELT ferdig, alle tre
    faser); (d) ~~flere flighter i én frittstående runde fortsatt kun
    drøftet~~ — ferdig, retning 1 (løs gruppering) bygget, scratch-/
    browserverifisert og live 2026-07-28, se status over; (e) ~~varsler
    koblet til venneforespørsler, ikke til rundehendelser ennå~~ —
    ferdig, scratch-verifisert og live 2026-07-28, se status over
    (medspiller lagt til/venn ser synlig runde/tilkoblet runde fullført).
    Alle fem punktene (a)-(e) i denne listen er dermed ferdig.
11. **Ferdig, kun for historikk:** ekte spillformer (match/skins/fourball/
    foursome/greensome/scramble) for frittstående runder — backend
    (ADR-039 + minimums-spiller-håndhevelse) OG frontend (sideoppsett,
    skins-konfig i `/my-rounds/new`, matchstatus-/skins-tavle-visning,
    delt-ball-scorekort/-veiviser, `setup_complete`-gating) er nå BEGGE
    bygget, browserverifisert og live (se status 2026-07-28). Mulig
    fremtidig finpuss (ikke bedt om ennå): redigere et sidenavn i
    etterkant (kun opprett/slett finnes i dag), en tydeligere
    skins-poeng-forklaring i UI-et.
12. **Ferdig, kun for historikk:** de tre organisator-oppfølgingspunktene
    (flytte spiller mellom lag, individuell rangering per økt, midlertidige
    spillere + etter-runde-invitasjon) — alle bygget, scratch-/
    browserverifisert og live 2026-07-28, se status over. Ingen nye åpne
    spørsmål igjen fra denne runden.
13. **Ferdig, kun for historikk:** PWA-installasjonsoppfordring, flere
    flighter i én frittstående runde (retning 1), scramble/greensome-
    statistikk over valgt utslag (frittstående runder), og push-varsler
    til telefonens OS (Web Push/VAPID) — alle fire bygget, scratch-/
    browserverifisert og live 2026-07-28, se status over for full detalj.
    Gjenstående, IKKE bygget: samme scramble-utslagsstatistikk for
    org-scopede turneringer (bevisst utenfor omfang, se
    FEATURE_BACKLOG.md), ekte OS-nivå push-levering ikke bevist med en
    reell innvilget tillatelse (automatisert testmiljø hadde
    `Notification.permission` forhåndssatt til "denied") — bruker bør
    selv teste push på en ekte enhet.
14. **Åtte nye turneringsformater (Chapman/Nassau/Københavner/Bingo Bango
    Bongo/Flaggturnering/Shamble/Money Ball/High-low-high) — BACKEND
    FERDIG, SCRATCH-VERIFISERT OG LIVE (2026-07-30, migrasjoner 041-050),
    se status over.** ~~Eneste gjenstående steg: FRONTEND for samtlige
    åtte~~ — **RETTET 2026-08-03: dette punktet stod utdatert.** Frontend
    ble faktisk bygget i sesjonene mellom 2026-07-30 og 2026-08-02 (bl.a.
    synlig i "Det store grepet"-rettingene 2026-08-02 over, som allerede
    forutsetter `tournament-program.tsx` sin `CreateSessionCard` med alle
    10 to-sidede format), men punkt 14 her ble aldri oppdatert til å si
    det. Bekreftet direkte mot koden 2026-08-03 (samme dag som
    leaderboardets V0-omskriving, se status over): spilleform-valg finnes
    i `new-round.tsx` (frittstående), `tournament-program.tsx`
    (org-lag), `individual-tournament-detail.tsx` (org-individuell,
    `scoring_method`); resultatvisninger finnes for Nassau (`NassauPanel`/
    `session-scorecard.tsx`), Københavner/BBB (`individual-tournament-
    detail.tsx`), Shamble/Money Ball (`round-leaderboard.tsx`s
    `TeamFlightBoard`) og High-low-high (`round-leaderboard.tsx`s
    `HighLowBoard`/`session-scorecard.tsx`). Migrasjoner bekreftet live
    t.o.m. 052. Samme rettelse lagt inn i FEATURE_BACKLOG.md samme dag.
    ~~**Reell, fortsatt åpen gap (urelatert til de åtte formatene):**
    organisator-vendt opplasting av hero-/sponsorbilder for
    turnering-landingssiden (ADR-018) — backend/lagring finnes, ingen
    dra-og-slipp-skjerm bygget ennå.~~ — **lukket samme dag, se punkt 16.**
15. **Ferdig, kun for historikk:** «Det store grepet» (ADR-040) — alle
    5 steg (backend/allowance_override, lag-sortering, rundeoppsett-
    veiviseren, delt Score/Scorekort/Leaderboard-fane-rad, integrering)
    BYGGET, VERIFISERT OG LIVE 2026-08-02, se status over. **Ett bevisst
    utsatt, fortsatt åpent spørsmål:** trenger lag-grupperingen i
    scorekortet mer enn celle-fargelegging for å skille lagene tydelig
    nok (utover selve sorteringen, som allerede er fikset) — ikke stilt
    til V0 ennå, egen liten vurdering om ønskelig. Se FEATURE_BACKLOG.md.
16. **Turnering-presentasjon: organisator-vendt hero-bilde/sponsor-
    opplasting + beskrivelse/synlighet/påmeldingsinnstillinger — BYGGET,
    SCRATCH-VERIFISERT OG LIVE 2026-08-03.** Brukeren spurte om
    presentasjonssider var på plass; svaret avdekket at backenden for
    dette (`hero_image_key`/sponsor-CRUD/`visibility`/`description`/
    påmeldingsfelt) hadde vært klar og LIVE siden ADR-018 (2026-07-18),
    men INGEN organisator-skjerm noensinne satte disse feltene — alt var
    100% API-only. Bekreftet omfanget eksplisitt med bruker (fullt
    presentasjons-panel, ikke bare bilder) før bygging.
    Ny `components/tournament-presentation.tsx`: `TournamentPresentation`
    (full side, egen rute `/tournaments/[id]/presentation`, brukt av
    lagturneringer) + `TournamentPresentationPanel` (samme innhold uten
    header/nav, bygget inn som en fjerde in-page-fane i
    `individual-tournament-detail.tsx` -- denne filen har sitt eget
    fanesystem, ikke egne ruter som lagturneringene). Feltene er delt
    mellom lag- og individuelle turneringer (samme `tournament`-tabell,
    ikke `format_type`-spesifikke), så ÉN komponent dekker begge.
    "Presentasjon"-fanen lagt til i alle fire eksisterende nav-rader
    (`tournament-detail.tsx`/`tournament-program.tsx`/`tournament-
    leaderboard.tsx`/`individual-tournament-detail.tsx`) -- disse fire er
    fortsatt hver sin duplikat (samme mønster/samme kjente ulempe som
    2026-08-02-bug-runden over), ikke en delt komponent denne runden.
    La samtidig til `overflow-x-auto` på alle fire nav-rader (fjerde fane
    presset bredden over det tidligere 3-faners layoutet tålte på smale
    mobilskjermer).
    **To små, bevisst minimale backend-tillegg** (ingen migrasjon --
    rene response-modell-/endepunkt-tillegg):
    - `Tournament.hero_image_url`/`Sponsor.logo_url`: nye beregnede felt
      (samme mønster som `auth.py` sin `avatar_url`) -- organisator-
      frontend skal aldri selv måtte kjenne MinIO-bucket/base-URL.
      `_tournament_from_row()`/`_sponsor_from_row()`-hjelpere lagt til,
      alle 8 tidligere `Tournament(**dict(row))`/`Sponsor(**dict(row))`-
      kallsteder erstattet.
    - Ny `DELETE /orgs/{id}/tournaments/{id}/hero-image` (samme mønster
      som eksisterende `DELETE /auth/profile/avatar`) -- fantes ikke fra
      før, kun opplasting.
    **Én reell bug funnet og rettet UNDER scratch-verifisering** (ikke
    antatt riktig fra ren typesjekk): sponsor-radens layout (logo-
    miniatyr + navn + "Last opp logo"-knapp med full tekst + slett-ikon,
    alle på én rad) klemte sponsornavnet til nesten ingenting på en ekte
    390px mobil-viewport ("Tjø…" for "Tjøme Rørlegger AS"). Rettet ved å
    la navn/lenke ligge på egen rad, handlingsknappene på en egen rad
    under (`flex-col` på mobil, `sm:flex-row` fra small breakpoint) --
    samme klasse defekt (for lite bredde satt av til tekst i en trang
    flex-rad) som `SideName`-trunkeringsbugen i leaderboard-omskrivingen
    tidligere denne uken, men et annet konkret sted.
    **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
    isolert scratch-MinIO + engangs API-/frontend-container, ekte
    nettleser-innlogging inkl. reell 2FA-e-post-oppsett): full
    PATCH-rundtur (beskrivelse/synlighet/godkjenning/venteliste-
    policy lagret og lest tilbake korrekt fra ekte DB), hero-bilde
    lastet opp og bekreftet i MinIO (`image/avif`, riktig nøkkel i DB),
    hero-bilde fjernet (nøkkel nullstilt), sponsor lagt til, sponsor-
    logo lastet opp og bekreftet i MinIO, sponsor slettet -- alt testet
    på BÅDE en lagturnering og en individuell turnering, pluss bekreftet
    at den offentlige siden (`/t/{id}`) viser organisatorens satte
    beskrivelse. **Rullet ut live 2026-08-03**, bruker bekreftet
    eksplisitt: `docker compose up -d --build teecup_api teecup_frontend`,
    begge containere boot-et rent, `/health`/`/dashboard` → 200,
    ny `/tournaments/[id]/presentation`-rute bekreftet i build-outputen.
    **Ikke klikket gjennom i selve produksjonen** -- ingen ekte turnering
    finnes ennå i `teecup_db` ("Ingen turneringer ennå" på dashbordet),
    så dette er samme build som scratch-verifisert, ikke i tillegg
    egenhendig bekreftet mot ekte produksjonsdata. Bekreft ved neste
    faktiske turnering.
17. **Konkurranseklasser ("Damer fra 44, Herrer fra 50+") — BYGGET,
    SCRATCH-VERIFISERT OG LIVE 2026-08-04, migrasjon 053.** Brukeren
    spurte om det var mulig å sette opp runder i en turnering slik at
    f.eks. damer spiller fra utslag 44 mens herrer spiller fra 50+.
    Svaret var at DELER allerede virket (hver deltaker i en match/runde
    fikk allerede sitt eget `tee_id`, backend validerte allerede at
    utslaget hadde rating for spillerens kjønn) -- men et manuelt valg
    per spiller hver gang, ingen gjenbrukbar "klasse". Brukeren ba om en
    ekte klasse-mekanisme, "samt andre parametre som er relevante i en
    slik problemstilling."
    Full plan-modus-runde med to `AskUserQuestion`-avklaringer FØR
    bygging (samme disiplin som ADR-037/ADR-039): (1) klasser er FRITT
    NAVNGITTE (ikke bare kjønn) med et valgfritt standardutslag, ikke en
    fast kjønn+alder-modell -- dekker kjønn/alder/HCP eller annet uten at
    systemet må forstå forskjellen; (2) klasse gir EGEN RESULTATLISTE --
    men KUN i individuelle turneringer (ADR-037, flatt felt). I
    lagturneringer (ADR-011, to lag) er poeng knyttet til hele kamper,
    ikke enkeltspillere -- brukeren bekreftet eksplisitt at klasse der
    KUN skal foreslå utslag, ingen leaderboard-splitting (det finnes fra
    før et smalt per-økt individuelt leaderboard for singles/fourball-
    slagspill, `fetch_individual_leaderboard` -- IKKE noe denne runden
    bygger videre på); (3) gjelder org-lagturneringer OG org-individuelle
    turneringer, ikke frittstående personlige runder.
    **Skjema** (`053_tournament_classes.sql`): ny delt `tournament_class`-
    tabell (id/organization_id/tournament_id/name/default_tee_id,
    `UNIQUE(tournament_id, name)`) -- delt mellom begge turneringstyper
    siden begge peker til samme `tournament`-tabell (ADR-037s
    `format_type`-mønster), unngår duplisering. To nye NULLABLE FK-
    kolonner (`ON DELETE SET NULL`, samme mønster som `tournament_round_
    bbb_hole`): `team_roster.class_id` og `tournament_participant.
    class_id`. Samme `org_isolation`-RLS-loop som migrasjon 040.
    **Backend**: ny klasse-CRUD (`GET/POST/PATCH/DELETE .../classes`) i
    `tournaments.py` (delt fil, siden `tournament`-tabellen er delt).
    `RosterEntryCreate`/`RosterEntry` fikk `class_id`/`class_name`;
    `RosterEntryUpdate` (tidligere KUN `is_captain: bool` påkrevd)
    lagt om til `exclude_unset`-PATCH-semantikk (samme mønster som
    `TournamentUpdate`/`SponsorUpdate` fra presentasjons-panel-runden)
    slik at klasse kan settes uten å tvinge et samtidig kaptein-valg.
    `TournamentParticipantCreate`/`Out` (individual_tournaments.py) fikk
    samme felt, pluss en NY `PATCH .../participants/{id}` (fantes ikke
    før -- kun POST/GET/DELETE). `individual_leaderboard` utvidet med
    `class_id`/`class_name` i SELECT+respons -- selve summerings-/
    sorteringslogikken UENDRET, frontend grupperer den allerede sorterte
    listen visuelt (stabil gruppering bevarer riktig rangering per
    klasse, ingen ny motorlogikk i `handicap_engine.py` -- verifisert:
    98/98 eksisterende enhetstester fortsatt grønne, ingen regresjon).
    **Frontend**: "Klasser"-kort (opprett/slett, kaskaderende bane→
    utslag-velger for standardutslag) i BÅDE `tournament-detail.tsx`
    ("Lag og spillere"-fanen) og `individual-tournament-detail.tsx`
    (Oppsett-fanen). Utslag-forhåndsutfylling i to steder --
    `session-blind-draw.tsx`s `AddSlotForm` (lagturneringer) og
    `individual-tournament-detail.tsx`s `AssignRoundParticipantControl`
    (individuelle turneringer) -- begge en `useEffect` som slår opp valgt
    spillers klasse → standardutslag når spilleren velges, KUN hvis det
    utslaget faktisk finnes på DENNE øktens/rundens bane (klassens
    standardutslag kan tilhøre en annen bane), fortsatt fritt
    overstyrbart. Ren frontend-bekvemmelighet, samme "gjenbruk et
    eksisterende felt, ikke en ny backend-mekanisme"-prinsipp som
    `new-round.tsx`s eksisterende kjønnsfilter/-default. `LeaderboardTab`
    (individuelle turneringer) grupperer den mottatte, allerede sorterte
    listen på `class_id`, hver klasse får egen seksjonsoverskrift +
    egen 1/2/3-rangering INNAD i klassen -- ingen klasser opprettet =
    identisk med tidligere flat visning, bakoverkompatibelt uten
    migrasjonsflagg.
    **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
    isolert scratch-MinIO + engangs API-/frontend-container, ekte
    nettleser-innlogging inkl. 2FA): full API-rundtur (klasse-CRUD,
    roster-/deltaker-PATCH med class_id, `ON DELETE SET NULL` bekreftet
    ved klasseslettelse), ekte nettleser-klikk gjennom BEGGE
    turneringstyper -- lagturnering: opprettet klasser via UI-skjemaet
    (kaskaderende bane→utslag bekreftet), tildelte klasse via
    dropdown-menyen, bekreftet utslag forhåndsvalgt korrekt idet en
    spiller med klasse ble valgt i `AddSlotForm` (Kari→44, Ola→56);
    individuell turnering: samme mønster i `AssignRoundParticipantControl`
    (Bjørn Ege→56), pluss det klasse-delte leaderboardet bekreftet
    visuelt korrekt -- "DAMER"-seksjon viste Kari (72 slag, rang 1) foran
    Siv (108 slag, rang 2), "HERRER" viste Ola alene (90 slag, rang 1),
    tallene håndregnet og stemte eksakt (par/+18/+36 mot faktisk
    innsendt score). `test_isolation.sql` 12/12 uendret, ekte
    typesjekket produksjonsbuild (fanget og rettet ETT reelt
    TypeScript-funn under selve verifiseringen: `classes`-propen manglet
    på `TeamColumn` i `session-blind-draw.tsx` -- `AddSlotForm` ligger i
    en underkomponent som ikke automatisk arver overordnet komponents
    state, måtte tres eksplisitt gjennom).
    **Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: migrasjon
    053 mot ekte `teecup_db`, `docker compose up -d --build teecup_api
    teecup_frontend`, begge containere boot-et rent, `/health`/
    `/dashboard` → 200.
18. **Augusta-stil resultattavle for slagspill-turneringer (POS/PLAYER/
    TODAY/THRU/TOTAL/R1-Rn) — BYGGET, SCRATCH-VERIFISERT OG LIVE
    2026-08-04, ingen migrasjon.** Brukeren viste et bilde av en fysisk
    leaderboard-tavle fra Augusta National og ba om samme kolonneoppsett
    for individuelle turneringer med brutto/netto/stableford-scoring —
    med lederen alltid øverst, valgfri automatisk rulling, og eksplisitt
    krav om at det skal se bra ut på storskjerm (ikke bare mobil). Bygget
    via V0 (zip 29, `stroke-play-leaderboard.tsx`) etter etablert mønster:
    ren visuell komponent med mock-data først, ekte backend-kobling
    etterpå.
    **V0-prompten** (skrevet FØR eksporten) spesifiserte eksplisitt at
    fargevalget (grønn=under par/oransje=over par) skal følge appens EGEN
    etablerte konvensjon, IKKE referansebildets amerikanske rød-for-
    under-par-tradisjon — V0 traff dette presist. Eksporten matchet
    datakontrakten i prompten nesten ordrett (`RoundCell`/`LeaderboardRow`
    -typene er identiske), inkludert korrekt implementert lederrad
    fastspent i gull, av/på-knapp for automatisk rulling som respekterer
    `prefers-reduced-motion`, frossen POS/PLAYER-kolonne på mobil, og en
    egen, betydelig større "storskjerm"-skalering (`lg:`-brekkpunkt) --
    første skjerm i appen bygget eksplisitt for dette. **Én reell,
    forventet justering gjort ved integrering** (prompten dekket ikke
    dette presist nok selv): rundekolonnenes eksakt-par-tilfelle
    ("E") fikk feilaktig samme kvadrat+oransje-behandling som over par --
    rettet til appens egen "E → ren tekst, ingen ramme"-regel
    (DESIGN_SYSTEM.md) ved å utvide `RoundCell.isUnderPar: boolean|null`
    til en eksplisitt `tone: "under"|"even"|"over"|null`, bekreftet
    visuelt riktig i scratch (72 mot par 72 vises nå som ren tekst, ingen
    ramme).
    **Backend** (`individual_tournaments.py`): ingen skjemaendring --
    `individual_leaderboard`-endepunktet utvidet med nye, valgfrie felt
    (`position`/`is_leader`/`today_label`/`thru_label`/`total_label`/
    `rounds[]`), KUN populert for scoring_method brutto/netto/stableford
    (uendret `null`/tom liste for København/BBB, som fortsatt bruker den
    enkle listevisningen). "I dag" (TODAY/THRU) utledes som runden med
    høyest sekvensnummer som NOEN deltaker har påbegynt (bekreftet med
    bruker: ingen eksplisitt "aktiv runde"-markering finnes eller trengs
    -- runder spilles sekvensielt i praksis). Par-for-spilte-hull regnes
    per (runde, deltaker) via en ny korrelert subquery mot
    `tournament_round_hole`, gjenbruker de allerede cachede
    `gross_total`/`net_total`/`stableford_points`-verdiene fra
    `tournament_round_score` uendret.
    **Reell rangeringsbug funnet OG rettet UNDER selve scratch-
    verifiseringen** (ikke antatt riktig fra koden alene): den
    EKSISTERENDE sorteringslogikken (uendret siden ADR-037) rangerte på
    RÅ `gross_total`/`stableford_total` -- riktig for en enkelt-rundes
    sammenligning, men i en flerrunde-turnering der deltakere har spilt
    ULIKT antall hull til enhver tid, er rå sum ikke sammenlignbar (en
    deltaker med kun 18 hull spilt fikk et lavere rått slagtall enn
    deltakere med -5 til par over 27-36 hull, og rangerte dermed FORAN
    dem som leder — funnet ved at test-scriptets egne håndregnede
    assert-sjekker feilet). Rettet ved å innføre en til-par-normalisert
    rangeringsnøkkel (`gross_total/net_total − par_for_spilte_hull`,
    negert for stableford siden flere poeng er bedre der) brukt BÅDE til
    sortering og uavgjort-deteksjon, i stedet for rå totaler. Ny
    `entries.sort(...)`-plassering flyttet inn i denne funksjonen
    (erstatter den gamle grenen kun for disse tre metodene — København/
    BBB/default-brutto beholder sin uendrede, allerede riktige rå-sum-
    sortering, siden alle deltakere der alltid har likt antall
    hull/runder tilgjengelig i de formatene).
    **Frontend** (`individual-tournament-detail.tsx`): `LeaderboardTab`
    grener nå på `scoring_method` -- `StrokePlayLeaderboard` (ny
    komponent) for brutto/netto/stableford, uendret gammel flat/klasse-
    gruppert liste for København/BBB/Flagg. Klasse-gruppering (2026-08-04
    tidligere samme dag) virker uendret -- komponenten instansieres én
    gang per klasse-seksjon, akkurat som den gamle listen ble.
    **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
    engangs API-/frontend-container, ekte nettleser-innlogging inkl.
    2FA): en 4-spiller, 2-rundes turnering med et bevisst konstruert
    scenario -- ekte uavgjort i toppen (to spillere -5 til par, ulikt
    antall hull spilt hver), én spiller midt i runde 2 (thru 9), én
    spiller som IKKE hadde startet runde 2 i det hele tatt, én ferdig
    begge runder. ALLE håndregnede tall (POS med "T1"-uavgjort, TODAY,
    THRU inkl. "F"/"-"/hull-tall, TOTAL, R1/R2 med riktig form+farge per
    Golfscore-språket) bekreftet eksakt riktige, både via rå API-JSON og
    i ekte nettleser på BÅDE 390px mobil (frossen POS/PLAYER, resten
    skrollbar) og 1920px storskjerm (alt synlig uten skrolling, betydelig
    større tekst). Rull-automatisk-knappen bekreftet å veksle av/på.
    Regresjonstestet: en BBB-turnering bekreftet fortsatt å returnere
    tomme/`null`-verdier for de nye feltene (gammel visning uendret,
    ingen ny kode-vei berørt for de formatene). 98/98 eksisterende
    `handicap_engine.py`-enhetstester uendret (ingen motorendring), ekte
    typesjekket produksjonsbuild.
    **Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: INGEN
    migrasjon (rene response-felt-tillegg), `docker compose up -d --build
    teecup_api teecup_frontend`, begge containere boot-et rent,
    `/health`/`/dashboard` → 200. V0-zip-en slettet etter merge, per
    etablert rutine.

19. **Baneoppsett i turneringer: rekkefølge, TeeOff-import og delt
    bane-mal-bibliotek — BYGGET OG SCRATCH-VERIFISERT GRUNDIG 2026-08-04,
    migrasjon 054, IKKE ENNÅ RULLET UT MOT ekte `teecup_db`.** Brukeren
    oppdaget, mens hen brukte den live turneringsmodulen, at "Klasser"
    (med sin Standardutslag-velger, ADR-041) vises FØR banen/runden i det
    hele tatt er satt opp -- meningsløst å velge standardutslag for en
    klasse før man vet hvilke utslag banen har. Bemerket samtidig at
    TeeOff-henting av baner ikke så ut til å være tilgjengelig i
    turneringsmodulen, og ba om en helt ny funksjon: la BÅDE
    turneringsmodulen OG single-runde-modulen tilby "bruk en eksisterende
    bane som mal" (fra TeeOff, eller fra en annens custom-bane) når man
    oppretter en manuell bane.

    **Del I -- rekkefølge + TeeOff-import-gap (ingen skjemaendring):**
    Explore-agent bekreftet klagen presist og avdekket at det var verre
    for lagturneringer enn antatt: for individuelle turneringer lå
    `ClassesCard` FØR `RoundsCard` i samme Oppsett-fane
    (`individual-tournament-detail.tsx`); for lagturneringer lå Klasser
    på selve FØRSTE fanen ("Lag og spillere"), mens bane-/rundeoppsett
    krevde en hel sidenavigering til "Program". TeeOff offisiell-import
    (ADR-019, ferdig bygget) var KUN koblet til lagturnerings-økt-UI-et,
    fullstendig fraværende fra den individuelle turneringsflyten.
    Fikset: `ClassesCard` flyttet til å rendres ETTER `RoundsCard` i
    individuelle turneringer; Klasser-kortet flyttet fysisk fra
    `tournament-detail.tsx` ("Lag og spillere") til `tournament-program.tsx`
    ("Program"), rett etter økt-/rundeoppsettet -- `classes`-state (brukt
    av `TeamPanel` til roster-klassevisning) ble værende i
    `tournament-detail.tsx`, men create/delete-handlerne og selve
    `ClassesCard`-komponentdefinisjonen flyttet med til `tournament-program.tsx`.
    `OfficialCourseSearch`-komponentmønsteret (allerede i
    `tournament-program.tsx`) kopiert inn i `individual-tournament-detail.tsx`
    (samme duplisering-mellom-turneringstype-filer-konvensjon som
    `ClassesCard` allerede fulgte), koblet til `NewRoundForm` sitt
    eksisterende "+ Ny bane"-felt.

    **Det største funnet (Explore-agent, avdekket FØR bygging):**
    turneringsmodulens "opprett manuell bane" var i praksis ubrukelig --
    KUN et navnefelt (`POST /orgs/{id}/courses` tok bare imot `{name}`),
    ingen vei til hull/par/hcp-indeks/utslag i det hele tatt. To
    sub-ressurs-endepunkter for å legge til dette i etterkant fantes
    (`courses.py` sine `create_tee`/`create_holes`), men hadde ALDRI fått
    noe frontend-kallsted -- funksjonen ble aldri fullført.

    **Del II -- mal-basert baneoppretting + delt offentlig bane-bibliotek.**
    Et nøkkelfunn forenklet løsningen betraktelig: `personal_course`
    (020_personal_rounds.sql, "frittstående runder") er, til tross for
    navnet, ALLEREDE et globalt, plattform-omfattende bibliotek -- `GET
    /personal-courses` søker på tvers av ALLE brukeres baner uten eier-
    eller org-filtrering, og `POST /personal-courses` tar allerede imot
    full hull-/utslagdata i ett atomisk kall. Ingen ny tabell trengtes --
    kun én kolonne (`forked_from_id`, migrasjon 054) for
    proveniens/attribusjon.
    Tre `AskUserQuestion`-runder avklarte omfanget presist FØR bygging
    (samme disiplin som ADR-037/039/041): (1) ren reorder, ingen
    blokkering; (2) et helt NYTT, kompakt hull-/utslag-editorskjema for
    turneringsmodulen (speiler single-runde-modulens `OwnCreateStep` i
    funksjon, egen stil), med "bruk som mal"-vei fra BÅDE TeeOff og
    offentlig custom-bane; (3) alle tre moduler (individuell,
    lagturnering, frittstående runde) skal ha funksjonen.
    Et oppfølgingsspørsmål fra brukeren ("disse custom banene bør lagres,
    og de må være offentlige... er det noe jeg ikke har tenkt på?") ble
    tatt til en fjerde avklaringsrunde: (a) "offentlig" betyr HELE
    TeeCup-plattformen (ikke bare egen org) -- eneste måte
    single-runde-modulen faktisk kan dele samme mal-bibliotek som
    turneringsmodulen, siden den ikke har noe org-begrep; (b) "Dupliser"
    beholdes som egen, eksplisitt handling i tillegg til at redigering av
    en ANNENS bane automatisk forker en kopi (redigering av DIN EGEN bane
    endrer den i stedet, ingen fork).

    **Backend:**
    - `054_personal_course_provenance.sql`: `personal_course.forked_from_id`
      (selvreferende FK, `ON DELETE SET NULL`). Ingen RLS-endring --
      tabellen har aldri vært org-scopet (bevisst siden migrasjon 020).
    - `rounds.py`: `PersonalCourseOut`/`PersonalCourseDetail` utvidet med
      `is_mine`/`created_by_display_name` (attribusjon i mal-søket, FULLT
      navn per navneformat-regelen -- dette er en administrasjonsvisning,
      ikke direkte adressering) og (kun Detail) `holes`/`full_tees`/
      `forked_from_id` (forhåndsutfylling). Nye endepunkter: `PATCH
      /personal-courses/{id}` (eier: oppdaterer i sted; IKKE eier: forker
      automatisk -- ett endepunkt dekker begge casene uten at frontend må
      forgrene på eierskap), `POST .../duplicate` (eksplisitt, uavhengig
      av eierskap), `DELETE ...` (kun eier, 403 ellers; fanger opp
      `asyncpg.ForeignKeyViolationError` fra `round.personal_course_id`
      sin allerede-eksisterende `NO ACTION`-FK og gir en vennlig 409
      "IN_USE" -- IKKE via den delte `translate_db_errors()`, som ville
      gitt feil retning/melding for akkurat denne casen). `GET
      /personal-courses` fikk en `mine=true`-parameter (for
      "Mine baner"-administrasjonen, unngår den vanlige 20-treffs-
      begrensningen på navnesøket).
    - `courses.py`: `POST /orgs/{id}/courses` utvidet til valgfritt å ta
      imot samme `holes`/`tees`-form som `PersonalCourseCreate` --
      populert setter den BÅDE org-`course`-raden (med hull/utslag,
      samme SQL-mønster som de eksisterende men ubrukte sub-ressurs-
      endepunktene) OG en tilsvarende `personal_course`-rad (eid av
      innlogget bruker, "offentliggjøringen" brukeren ba om) i SAMME
      transaksjon -- ingen vedvarende kobling mellom de to radene etterpå
      (samme "engangs-kopi"-filosofi som offisiell TeeOff-import,
      ADR-019). `official-import`-endepunktet selv er UENDRET.
    - `rounds.py` sin `GET /rounds/official-search/{slug}` (personlig
      rundemodul) utvidet med fulle `holes`/`full_tees` PER TeeOff-bane
      (kun populert når teeoff-dataene er komplette nok -- samme
      fullstendighetskrav som ADR-019-importen allerede håndhever) --
      nødvendig fordi frittstående runder ALDRI persisterer teeoff-baner
      (ADR-033 Beslutning C, live oppslag), så "bruk som mal" der må få
      dataene tilbake i selve søkesvaret i stedet for å runde-trippe
      gjennom en lagret rad slik org-siden kan.

    **Frontend:**
    - Ny delt fil `course-template-editor.tsx`: `CourseTemplateEditor`
      (kompakt hull-/utslag-skjema, forhåndsutfyllbar via `initial`-prop,
      ren UI + lokal validering -- kalleren avgjør hvor data persisteres,
      matcher prosjektets etablerte adaptermønster) + `CourseTemplatePicker`
      (velger: "Fra bunnen av" / "TeeOff-bane som mal" / "Offentlig bane
      som mal" -- sistnevnte med BÅDE "Bruk direkte" og "Tilpass før
      bruk"). Brukt fra `tournament-program.tsx` (både ny økt OG endre
      eksisterende økts bane) og `individual-tournament-detail.tsx`
      (`NewRoundForm`).
    - `new-round.tsx`: `OwnCreateStep` utvidet med valgfrie
      `initial`/`forkedFromId`-props (samme prefyll-mekanisme). Nye
      "Bruk som mal for egen bane"-knapper i BÅDE `CourseList` (TeeOff-
      baner, kun synlig når komplette maldata finnes) og `OwnSearch`
      (offentlige custom-baner, nå med attribusjon "Opprettet av X" /
      "Opprettet av deg" -- samme navneformat-regel som backend).
    - `account-settings.tsx`: ny "Mine baner"-seksjon (`MyCoursesSection`)
      -- liste over EGNE `personal_course`-rader (`?mine=true`), med
      Rediger (gjenbruker `CourseTemplateEditor`, PATCH), Dupliser
      (POST duplicate) og Slett (DELETE, med vennlig 409/i-bruk-melding)
      per rad. Andres baner vises IKKE her -- kun i mal-søket i de
      respektive modulene.

    **Reell bug funnet og rettet UNDER scratch-verifisering** (ikke
    antatt riktig fra koden alene): `CourseTemplateEditor` ble først
    bygget med sitt eget `<form onSubmit>` -- men komponenten rendres
    INNI et allerede eksisterende `<form>` (`NewRoundForm`/
    `CreateSessionCard`), og nestede `<form>`-elementer er ugyldig HTML.
    "Opprett bane"-knappen submittet i praksis det YTRE runde-/økt-
    skjemaet i stedet, en ekte side-navigasjon som vasket bort
    `?org=...`-parameteren fra URL-en -- funnet ved en ekte klikk-
    gjennom-test i nettleser (fetch-kallet nådde aldri serveren, bekreftet
    ved å sjekke databasen). Samme feilklasse som `OfficialCourseSearch`
    allerede hadde en kommentar om fra en tidligere runde. Rettet: `<form>`
    → `<div>`, `type="submit"` → `type="button"` med eksplisitt
    `onClick`-håndtering.

    **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
    isolert scratch-MinIO + engangs API-/frontend-container, ekte
    nettleser-innlogging som TO forskjellige brukere på tvers av TO
    forskjellige organisasjoner):
    - API-nivå: eget Python-testskript, 34/34 håndregnede sjekker bestått
      (publisering-ved-opprettelse, kryss-bruker/kryss-org synlighet,
      attribusjon, fork-ved-ikke-eier-redigering med verifisert uendret
      original, eier-redigering-i-sted, eksplisitt duplisering,
      eierskaps-gatede 403-er, og slette-blokkert-mens-i-bruk med riktig
      409/IN_USE-kode).
    - Ekte nettleser: rekkefølge-fiksen bekreftet i begge turneringstyper;
      "Opprett bane med hull/utslag" fra bunnen av OG fra TeeOff-mal
      (ekte Bergen Golfklubb-data, redigert par på hull 1 før lagring,
      bekreftet BÅDE org-`course`- og `personal_course`-raden ble
      opprettet riktig i databasen); den nye offentlige banen dukket
      umiddelbart opp i single-runde-modulens banesøk MED riktig
      attribusjon; "bruk som mal"-broen fra der forhåndsutfylte
      `OwnCreateStep` korrekt fra en ANNEN brukers bane og forket en ny,
      egen-eid rad ved lagring (bekreftet i databasen: ny rad eid av
      innlogget bruker, `forked_from_id` pekende på originalen, originalen
      selv uendret); "Mine baner" i kontoinnstillinger bekreftet å vise
      KUN egne baner, Rediger (i sted, verifisert i databasen), Dupliser
      og Slett (inkl. `confirm()`-dialoghåndtering) alle fungerende.
    - `test_isolation.sql` 12/12 bestått (additiv migrasjon, ingen
      RLS-endring). Ekte typesjekket produksjonsbuild (`docker build`,
      samme steg som `Dockerfile` faktisk bruker) kjørt flere ganger
      gjennom byggerunden, null TypeScript-feil.
    Scratch-miljøet (database, rolle, MinIO, containere, images,
    midlertidige hemmeligheter) fullstendig ryddet opp etter
    verifisering.
    **Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt ("Ja
    takk"): migrasjon 054 kjørt mot ekte `teecup_db` (kun
    `forked_from_id`-kolonnen, bekreftet tom/nullable, ingen
    databrudd), `docker compose up -d --build teecup_api
    teecup_frontend`, begge containere boot-et rent, `/health` og
    `/dashboard` → 200 over https.

20. **Order of Merit (sesong-sammenlagt rangering på tvers av
    turneringer) — BYGGET, GRUNDIG SCRATCH-VERIFISERT OG LIVE 2026-08-04
    (ADR-043, migrasjon 055).**
    Brukeren ba om en vurdering av GolfBox sin Order of Merit-funksjon
    (opplastet PDF) og tok deretter designet til en full plan-runde (to
    `AskUserQuestion`-runder + et oppfølgingsspørsmål som avklarte at
    "lag" i en OOM er et sesong-par satt opp DIREKTE i OOM-en, ikke
    hentet fra noen turnering -- unngår GolfBox sin egen identifiserte
    svakhet, strengmatching på lagnavn, siden TeeCup allerede har ekte
    `player_id`-identitet på tvers av organisasjonen).

    **Skjema** (migrasjon 055): `order_of_merit`/`order_of_merit_link`/
    `order_of_merit_team`/`order_of_merit_team_member`, org-isolert RLS
    (samme mønster som migrasjon 040/053). To uavhengige akser --
    `result_type` (poeng-etter-plassering/Stableford-sum/brutto/netto/
    pengeliste, HVA som telles) og `aggregation_mode` (sum/snitt/
    eclectic, HVORDAN det summeres over en sesong).

    **Motor** (`handicap_engine.py`): `order_of_merit_points_for_position`
    (uavgjort deler SAMME poengverdi ved sin felles plassering, ikke
    gjennomsnitt) og `order_of_merit_aggregate` (sum/snitt, valgfri
    behold-N-beste, "best" er alltid størst-er-bedre -- kalleren negerer
    brutto/netto-verdier). 9 nye enhetstester skrevet FØR resten av
    bygget, alle grønne (107/107 totalt i `test_handicap_engine.py`).

    **Backend** (nytt `app/routers/order_of_merit.py`): full CRUD for
    selve OOM-en og dens lenkede turneringer, pluss et "regn ut ved
    lesing"-leaderboard-endepunkt (samme filosofi som Nassau/
    High-low-high -- ingen cache-tabell). `_compute_individual_standings`
    faktorisert ut av `individual_tournaments.py` sin `individual_
    leaderboard` (som nå kun er en tynn wrapper rundt den) for gjenbruk
    her uten å duplisere posisjon-/til-par-beregningen -- `LeaderboardEntry`
    fikk samtidig et nytt `player_id`-felt (manglet fra før, kun
    `tournament_participant_id` fantes, utilstrekkelig for å summere én
    ekte spiller på tvers av FLERE turneringers deltaker-rader).
    `kind`/`result_type` låses etter opprettelse (PATCH-endepunktet
    eksponerer dem bevisst ikke i det hele tatt). Denne runden dekker
    spiller-OOM med alle fem resultattyper, sum/snitt-aggregering, og
    behold-N-beste/minimum-resultater/aldersgrense-grenser (fødselsår
    hentet fra `player.birth_date`, migrasjon 007). Eclectic-aggregering
    og lag-OOM sin faktiske leaderboard-beregning er bevisst IKKE bygget
    denne runden (schema og CRUD for lag finnes, men `GET .../leaderboard`
    avviser eksplisitt `kind='team'` med en tydelig feilmelding) -- se
    FEATURE_BACKLOG.md.

    **Frontend** (`order-of-merit-list.tsx`/`order-of-merit-detail.tsx`,
    nye ruter under `/organizations/[id]/order-of-merit`): håndkodet, IKKE
    via V0 -- brukeren gikk tom for V0-credits midt i planleggingen av
    frontend-tilnærmingen og ba om at det håndkodes "så lekkert som V0
    får til" i stedet. Bygget til å gjenbruke etablerte visuelle mønstre
    presist (samme header-/kort-stil som `org-members.tsx`, samme
    gull-ledertrøye-behandling -- `bg-gold`/`text-gold-foreground` -- som
    `stroke-play-leaderboard.tsx`). Detaljsiden har fire seksjoner i en
    bevisst rekkefølge (lenkede turneringer → innstillinger →
    resultatliste, siden resultater ikke gir mening før turneringer er
    lenket), inkl. en egen "Ikke kvalifisert"-seksjon for spillere
    ekskludert av aldersgrense/minimum-resultater med forklarende tekst
    per rad. Ny inngangslenke lagt til i `org-members.tsx` (ingen egen
    "org-hub"-side fantes fra før -- medlemssiden er i praksis allerede
    det, samme mønster som appen ellers navigerer dit fra dashbordets
    org-nedtrekksmeny).

    **Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
    isolert scratch-MinIO + engangs API-/frontend-container, ekte
    nettleser-innlogging inkl. 2FA):
    - API-nivå: eget Python-testskript, 63/63 håndregnede sjekker bestått
      (bevisst konstruert 3-spiller/2-turnering-scenario med kjent
      Stableford/brutto/poeng/penge-utfall for hver av de fem
      resultattypene, inkl. en ekte uavgjort i pengeliste-testen som
      bekreftet "T1"/"T1"/"3"-skip-rank-oppførselen, behold-de-1-beste,
      minimum-2-resultater-ekskludering, og et eksplisitt aldersfilter-
      scenario der to av tre spillere korrekt ble ekskludert).
    - RLS-isolasjon eksplisitt testet på de fire nye tabellene utover
      `test_isolation.sql` (som uendret består 12/12): kryss-org-lesing
      av `order_of_merit`/`order_of_merit_link` gir 0 rader under feil
      `app.current_org`, kryss-org-innsetting blokkeres av `WITH CHECK`.
    - Ekte nettleser: hele flyten klikket gjennom som innlogget
      organisasjonseier -- opprett OOM (alle fem resultattyper/kind-valg
      i skjemaet), lenk en turnering med poeng-tabell-radeditoren (inkl.
      å faktisk lenke en tredje turnering og se den dukke opp i listen
      med riktig "Poeng: 10-6-3"-oppsummering), utvid/kollaps
      innstillinger-seksjonen og bekreft alle feltverdier viste riktig
      (inkl. aldersgrense-feltene 1985/2000), og bekreftet resultatlisten
      viste EKSAKT de håndregnede tallene fra API-testen (gull-fremhevet
      leder, riktig ekskluderte spillere med "Utenfor aldersgrensen."-
      forklaring).
    - Ekte typesjekket produksjonsbuild (`docker build`, samme steg som
      `Dockerfile` faktisk bruker) -- måtte kjøres på nytt med lengre
      timeout enn standard 2 minutter, selve bygget var uendret vellykket.
    Scratch-miljøet (database, rolle, MinIO, containere, images,
    midlertidige hemmeligheter) fullstendig ryddet opp etter
    verifisering, TO GANGER (én gang etter backend-verifisering, én gang
    etter frontend-verifisering).

    **Kjent selvrettet feil under bygging:** funksjonen ble først
    kodekommentert som "ADR-042", som viste seg allerede å være tatt
    (bane-mal-biblioteket fra samme dag, punkt 19 over) -- oppdaget og
    rettet til ADR-043 gjennomgående (migrasjon, motor, router,
    frontend) FØR dokumentasjon ble skrevet, ingen funksjonell påvirkning
    (kun kommentarer/docstrings, ingen kode leser ADR-nummeret).

    **Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt ("Ja
    takk"): migrasjon 055 kjørt mot ekte `teecup_db` (fire nye tabeller,
    bekreftet tomme/riktig strukturert, ingen databrudd), `docker
    compose up -d --build teecup_api teecup_frontend`, begge containere
    boot-et rent, `/health` og `/dashboard` → 200 over https. Gjenstår
    fra opprinnelig plan (se plan-loggen 2026-08-04): Eclectic-
    aggregering, lag-OOM sin faktiske leaderboard-beregning.

21. **Bugfiks: "Fullfør runde" i header-modalen ("Administrer runde")
    svelget feil stille og lukket dialogen uansett utfall — 2026-08-04.**
    Brukeren rapporterte (skjermbilder fra en ekte runde på
    teecup.teeoff.no) at en Københavner-runde med kun 1 av påkrevde 3
    spillere så ut til å la seg "fullføre" via "Administrer"-knappens
    modal (bekreftelsesdialogen dukket opp, ble klikket OK på), men
    runden forble ufullført -- ingen feilmelding vist noe sted.

    **Rotårsak, `round-header.tsx`/`round-page-shell.tsx`:** rundens
    sticky header (ADR-040 Beslutning C/D, brukt på alle tre
    Score/Scorekort/Leaderboard-fanene) har sin EGEN, uavhengige
    "Fullfør runde"-vei via "Administrer"-modalen -- parallell til, men
    ikke delt med, `round-detail.tsx` sin allerede korrekte inline
    "Spillere og runde"-fane (som riktig deaktiverer knappen og viser
    `setup_message` når `setup_complete` er usann). Modal-veien hadde
    ALDRI blitt koblet til `setup_complete`-gaten, OG knappens
    `onClick` kalte `onClose()` rett etter å ha startet
    `onFinishRound()` UTEN å vente på den asynkrone
    bekreftelse+`fetch`-kjeden -- dialogen lukket seg altså alltid
    umiddelbart, uavhengig av om `POST /rounds/{id}/complete` faktisk
    lyktes (backend-en avviste korrekt med 409 `SETUP_INCOMPLETE` +
    forklarende melding -- det var ren frontend som aldri fulgte opp
    svaret).

    **Fiks:** `onFinishRound`-kontrakten endret fra `() => void` til
    `() => Promise<{ok, message} | null>` (`null` = brukeren avbrøt
    bekreftelsen, dialogen blir stående). Modalen venter nå på svaret
    før den lukker seg -- ved `ok: false` vises `message` inline i
    dialogen i stedet. Ny `finishDisabledReason`-prop deaktiverer
    knappen og viser samme `setup_message` som den inline fanen,
    fjerner selve muligheten til å trykke seg forbi en ufullstendig
    formatoppsett via denne veien. Ingen backend-endring -- valideringen
    fantes allerede og var korrekt, kun frontend fulgte den aldri opp.

    **Scratch-verifisert** (isolert `teecup_scratch`-DB, alle 55
    migrasjoner kjørt friskt, isolert `teecup_app_scratch`-rolle,
    isolert scratch-MinIO, engangs API-/frontend-container bygget fra
    de faktiske endrede filene, ekte nettleser via magic-link-innlogging):
    reproduserte brukerens NØYAKTIGE scenario (Københavner-runde, 1 av 3
    spillere) -- bekreftet modalens "Fullfør runde" nå viser seg
    deaktivert med "Københavner krever nøyaktig 3 spillere -- runden har
    1." synlig i dialogen (samme tekst som den inline fanen). La til to
    gjestespillere (nå 3/3), bekreftet knappen ble aktiv, klikket
    gjennom bekreftelsesdialogen, og bekreftet både at modalen lukket
    seg OG at `completed_at` faktisk ble satt server-side. Full
    typesjekket produksjonsbuild av frontend kjørt som del av samme
    Docker-bygg. Scratch-miljøet ryddet opp fullstendig etterpå.

    **Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: `docker
    compose up -d --build teecup_frontend` (ingen migrasjon involvert),
    containeren boot-et rent, `/health` og `/dashboard` → 200 over
    https.

22. **Nytt format: "Scramble mot enkeltspiller" (`scramble_solo`),
    slagspill-varianten — 2026-08-05 (migrasjon 056).** Første
    ASYMMETRISKE to-siders-format i prosjektet: et scramble-lag
    (variabel størrelse, N>=2, delt ball) mot ÉN individuell spiller
    (egen ball), avgjort ved netto SLAGSPILL-totalsum (ikke matchspill)
    -- laveste nettosum vinner. Bevisst utenfor omfang denne runden:
    match-play-varianten av formatet (egen runde senere), org-
    lagturneringer (kun frittstående runder nå).

    **Kilder undersøkt FØR design** (CLAUDE.md sin regel for nye
    formater): de tre HCP-PDF-ene dekker IKKE lag-vs-individuell eller
    vilkårlig lagstørrelse -- bekreftet ved grundig lesing. Lagets
    Playing Handicap er derfor en bevisst NY, enkel TeeCup-regel
    (`TeamAverage` -- rent snitt av medlemmenes course handicap, for
    N>=2), bekreftet eksplisitt med bruker, IKKE en kildebelagt formel
    som `scramble_2`/`scramble_4` sine faste `RankedSplit`-tabeller
    (som forutsetter fast lagstørrelse og derfor bevisst IKKE gjenbrukt
    her). Individualspillerens allowance er en konfigurerbar prosent
    (`scramble_solo_individual_pct`, std 100 %, arrangør-satt) --
    egen bespoke kolonne, IKKE den generiske `allowance_override`-JSON-
    en (som forutsetter ÉN delt strategi for hele runden, mens dette
    formatet trenger to uavhengige strategier).

    **Skjema** (migrasjon 056): `round.scramble_solo_individual_pct`
    (smallint, std 100) og `round_side.side_role`
    (`'team'`/`'individual'`, unik per rolle per runde). `round_hole`
    sin eksisterende XOR-struktur (migrasjon 031) trengte INGEN endring
    -- lagsiden bruker `round_side_id`-veien (samme som foursome/
    scramble), individualsiden bruker `round_participant_id`-veien
    (samme som singles), begge coexisterer allerede fritt i samme
    tabell.

    **Motor** (`handicap_engine.py`): ny `TeamAverage`-strategi og ny,
    domeneagnostisk `net_stroke_play_margin(net_total_a, net_total_b)`
    -- eksplisitt SØSKEN til, ikke del av, `compute_match_state`
    (ingen hull-for-hull-tilstand for dette formatet). Selve per-side-
    nettosummen gjenbruker eksisterende `allocate_strokes_by_index`/
    `stroke_play_net_total`, ingen ny funksjon trengtes der. 8 nye
    enhetstester (115/115 totalt, opp fra 107).

    **Backend** (`app/routers/rounds.py`): ny `_ASYMMETRIC_SIDE_FORMATS`-
    konstant (bevisst adskilt fra `_TWO_SIDED_FORMATS`, siden formatet
    verken er symmetrisk eller matchspill). Asymmetrisk sidekapasitet i
    `_check_side_capacity` (laget: ingen øvre grense, individualsiden:
    maks 1) -- kan IKKE uttrykkes av det eksisterende
    `_SIDE_PLAYER_COUNT`-oppslaget, som forutsetter ett fast tall for
    HELE formatet. Ny `_individual_round_hole_needed`-hjelpefunksjon
    (avgjør delt-ball vs. individuell-ball-lagring per DELTAKER, ikke
    per format, siden de to sidene har ulik struktur). Ny
    `_recompute_scramble_solo_handicaps` (søsterfunksjon til
    `_recompute_side_handicaps`, som ikke kunne gjenbrukes -- to
    uavhengige strategier, ikke én). Ny `_build_scramble_solo_result`
    (samme "regn ut ved lesing, ingen cache"-filosofi som Nassau/
    High-low-high), dispatchet FØR den generiske to-sidede grenen i
    `_build_format_result`.

    **Frontend -- ALT promptet til V0** (brukerens eksplisitte instruks
    denne runden, gjentatt: "Alt av frontend skal promptes mot V0" --
    ingen håndkoding/design-beslutning av meg). To V0-prompter skrevet
    (forankret i `DESIGN_SYSTEM.md` + tilgjengelighetskravet), levert
    som zip 30 (oppsett-steget) og zip 31 (resultatvisning, bygget på
    30). Integrert -- IKKE blindt kopiert (V0 sin egen full-app-eksport
    bruker fortsatt mock-data for alt utenom det som faktisk ble
    promptet) -- kun de faktiske diff-relevante stykkene ble portet inn
    i de allerede API-koblede filene:
    - `new-round.tsx`: ny `ScrambleVsSoloSides`-komponent (Steg 4,
      asymmetrisk sideoppsett -- "Laget" åpen liste/"minst 2"-hint,
      "Individuell motstander" låst til 1), ny `SoloAllowanceControl`
      (Steg 2, 0-100 %-stepper). `submitWizard()` fikk en egen
      side-opprettelses-gren for formatet (sender `side_role`, som
      TWO_SIDED-formatenes gren ikke gjør). "Avanserte handicap-
      innstillinger"-panelet skjules helt for formatet (ingen av
      feltene der har noen effekt siden formatet aldri bruker
      `allowance_override`) -- funnet og rettet UNDER integrasjonen,
      ikke en del av V0-eksporten.
    - `scramble-solo-result.tsx`: NY fil, ren V0-eksport uendret
      (selvstendig presentasjonskomponent, `ScrambleSoloResultView`).
      Adapter fra `ApiFormatResult` til komponentens props-form skrevet
      lokalt i `round-leaderboard.tsx` (`buildScrambleSoloResult`) og
      `round-detail.tsx` (lokal kopi, prosjektets vanlige mønster) --
      foretrekker hull-entryens `label`-felt (deltakerens faktiske
      navn) over sidens egen (faste) label, med fallback.
    - `round-scorecard.tsx`: eneste sted som trengte GENUINT ny logikk
      (ikke bare registrering) -- formatet har BLANDEDE enhetstyper i
      samme fane-liste (laget = side, individualspilleren =
      participant). Løst ved å slå opp faktisk enhetstype fra selve
      `unitId` (`round.sides.some(s => s.id === unitId)`) i stedet for
      et format-bredt `isSharedBall`-flagg, som ikke kan uttrykke en
      blanding -- denne oppslags-tilnærmingen forble samtidig korrekt
      uendret for alle eksisterende rene formater.
    - `round-detail.tsx`: INGEN endring i selve scoreføringen
      (`SideScoreWizard` for laget, vanlig per-deltaker-veiviser for
      individualspilleren fungerer begge uendret) -- kun
      `FormatResultPanel` fikk en ny gren (MÅ avskjæres FØR den
      generiske to-sidede fallback-koden, ellers ville den krasjet/vist
      feil for dette asymmetriske formatet).
    - `FORMAT_LABELS` lagt til i 8 filer (`round-card.tsx`,
      `round-leaderboard.tsx`, `round-detail.tsx`, `round-scorecard.tsx`,
      `round-stats.tsx`, `friend-profile.tsx`, `watch-round.tsx`) --
      `public-live.tsx` bevisst IKKE (den nøkler på org-turneringers
      `session.format`, utenfor omfang).

    **Scratch-verifisert grundig, TO RUNDER** (samme "backend alene,
    så full stack"-disiplin som Order of Merit-runden):
    1. Backend alene: isolert `teecup_scratch`-DB (alle 56 migrasjoner
       friskt), isolert `teecup_app_scratch`-rolle, isolert scratch-
       MinIO, engangs API-container. Håndregnet Python-testskript, 45/45
       sjekker bestått -- 3-manns lag (HCP 8/14/20, snitt 14) mot
       individualspiller (HCP 10), begge 100 % og 50 % allowance,
       inkl. eksplisitt bevis på at en hull-SI mellom de to
       allowance-tersklene (SI 7) korrekt skifter fra 1 til 0 mottatte
       slag. Kapasitetsgrenser bekreftet begge veier (laget avviser
       ALDRI en 4. spiller, individualsiden avviser alltid en 2.).
       "Fullfør runde"-gating eksplisitt testet med et ufullstendig lag
       (1 spiller) -- avvist med riktig forklarende melding, samme
       type sjekk som avdekket punkt 21 sin bug.
    2. Full stack: ny isolert scratch-runde (DB/rolle/MinIO/API-
       container pluss et ekte `docker build` av frontend-imaget --
       den faktiske typesjekkede produksjonsbuilden, ikke bare lokal
       `tsc`), ekte nettleser via magic-link-innlogging
       (`TEECUP_DEV_LOG_MAGIC_LINKS`). Opprettet en ekte runde gjennom
       hele veiviseren (inkl. V0 sin `ScrambleVsSoloSides`-UI), satte
       score på tre hull via ekte PATCH-kall, og bekreftet at
       `ScrambleSoloResultView` viste EKSAKT de håndregnede tallene
       ("Laget leder med 1 slag", 12 mot 13 netto) med riktig
       grønn/oransje leder-fremheving per hull. `test_isolation.sql`
       uendret 12/12 (migrasjonen er rent additiv). Scratch-miljøet
       ryddet opp fullstendig etter BEGGE runder.

    **Rullet ut live 2026-08-05**, bruker bekreftet eksplisitt: migrasjon
    056 kjørt mot ekte `teecup_db` (`round.scramble_solo_individual_pct`,
    `round_side.side_role` + `round_side_role_unique`-indeksen, bekreftet
    tilstede etterpå), `docker compose up -d --build teecup_api
    teecup_frontend`, begge containere boot-et rent, `/health` og
    `/my-rounds/new` → 200 over https, `round_play_format_check` bekreftet
    å inkludere `'scramble_solo'`.

23. **Matchspill-varianten av samme format (`scramble_solo_match`) —
    2026-08-05 (migrasjon 057), PLUSS en kritisk regresjonsbug funnet og
    rettet i samme runde som rammet BEGGE variantene (se eget punkt
    under).** Samme asymmetriske lag-mot-individualspiller-struktur som
    punkt 22, men avgjort hull-for-hull (matchspill) i stedet for netto
    slagspill-totalsum. Vesentlig mindre bygg enn punkt 22 -- nesten alt
    gjenbrukes uendret.

    **Motor -- ingen endring:** `match_play_strokes`/`compute_match_state`
    (`handicap_engine.py`) var allerede domeneagnostiske og brukes uendret
    -- konverterer lagets `TeamAverage`/individualspillerens
    `PerPlayerPercentage(pct/100)` (samme `_recompute_scramble_solo_
    handicaps` som punkt 22, uendret) til RELATIVE slag (laveste side
    spiller "av scratch"), i motsetning til slagspill-variantens
    ABSOLUTTE, uavhengige allowance per side -- bevisst forskjell, riktig
    fordi matchspill per definisjon betyr relativ slagfordeling.

    **Skjema** (migrasjon 057): ren CHECK-utvidelse, INGEN nye kolonner
    -- `round.scramble_solo_individual_pct`/`round_side.side_role`
    (migrasjon 056) er generiske nok til å gjenbrukes uendret.

    **Backend:** ny delt konstant `_SCRAMBLE_SOLO_FORMATS = {"scramble_
    solo", "scramble_solo_match"}`, widened seks eksakte
    `== "scramble_solo"`-strengsjekker i `app/routers/rounds.py` til
    settmedlemskap. Ny `_build_scramble_solo_match_result` gjenbruker det
    eksisterende blandede hull-oppslaget fra `_build_scramble_solo_result`
    (samme to queries: `round_hole` via `round_side_id` for laget, via
    `round_participant_id` for individualspilleren), bygger en
    `list[HoleResult]` fra netto-sammenligning per hull, mater
    `compute_match_state` -- gir lead/holes_remaining/is_dormie/
    is_closed/`describe()` gratis.

    **Frontend -- maksimalt gjenbruk, bekreftet eksplisitt med bruker
    (ingen ny V0-prompt for denne varianten):** `new-round.tsx` sin
    oppsett-UI (`ScrambleVsSoloSides`/`SoloAllowanceControl`) gjenbrukt
    HELT uendret. Resultatvisning gjenbruker `TwoSidedBoard`
    (round-leaderboard.tsx) og den generiske to-sidede
    `sideIdentity`/`LeadZone`-grenen (round-detail.tsx) -- begge allerede
    korrekte for en N-mot-1-side siden de grener på FAKTISK ANTALL
    SPILLERE per side, ikke på rolle. Én kritisk, selv-oppdaget
    korrekthetsbug FØR noen verifisering i det hele tatt: første utkast
    av `_build_scramble_solo_match_result` brukte en FAST konvensjon
    (laget alltid "a", individualspilleren alltid "b") -- resten av appen
    (inkl. `TwoSidedBoard`) forutsetter derimot at "a" = `round.sides[0]`
    SORTERT ETTER UUID, ikke rolle. Rettet til `team_is_a = team_side["id"]
    < individual_side["id"]`, tredd gjennom hull-sammenligningen og
    `FormatHoleEntry`-side-tagger. `round-scorecard.tsx` sin
    `MatchScorecardGrid` fikk en liten, dedikert (ikke-V0) gren for å vise
    ÉN rad per SIDE for dette formatet (samme mønster som `isSharedBall`-
    grenen der) -- uten den ville laget vist N dupliserte rader, siden
    alle lagmedlemmer deler samme `TeamAverage`-tall.

    **KRITISK REGRESJONSBUG funnet under full nettleser-verifisering,
    rammet OGSÅ den allerede live slagspill-varianten (punkt 22) --
    ikke bare den nye matchspill-varianten:** `round-detail.tsx` sin
    "Score"-fane (`SHARED_BALL_FORMATS`) inneholdt aldri `scramble_solo`/
    `scramble_solo_match` (kun ekte delt-ball-formater som foursome/
    scramble_2/scramble_4/chapman) -- rimelig nok isolert sett, siden
    scramble mot enkeltspiller er HALVPARTEN delt-ball (laget) og
    HALVPARTEN individuell (motstanderen), ikke rent det ene eller det
    andre. Konsekvens: et klikk på en LAGSPILLERS scorekort rutet
    (feilaktig) til den vanlige per-deltaker `ScoringWizard`/
    `wizardPlayerId`-stien i stedet for den delte `SideScoreWizard`/
    `wizardSideId`-stien. Siden lagspilleres hull ALDRI lagres per
    `round_participant_id` (kun per `round_side_id`), returnerte
    `GET .../participants/{id}/holes` alltid `[]` for dem -- og siden
    SPINNER-porten i samme fil (`holes.length === 0`) også var avledet
    fra akkurat DENNE deltakerens egen (evig tomme) liste, satt den seg
    fast i en evig spinner, permanent, uten noen feilmelding. Siden
    `SHARED_BALL_FORMATS` aldri var endret for `scramble_solo` i punkt
    22 sin utrulling heller, var dette allerede live og reelt ødelagt
    for ekte lagspillere på scramble_solo-runder FØR denne rettelsen.
    Rettet: `hole`/spinner-porten hentes nå fra HVILKEN SOM HELST kilde
    som allerede har lastet inn hull (deltaker- ELLER side-basert,
    uavhengig av hvem som er "aktiv spiller") i stedet for kun aktiv
    spillers egen liste; ny `openPlayerOrTeamSideEntry`-klikkhåndterer
    ruter lagets spillere til `wizardSideId`, individualspilleren
    fortsatt til `wizardPlayerId`; `advanceWizardPlayer`/`advanceWizardSide`
    justert til å ikke lenger kunne "vandre inn i" den andre kjeden.
    Funnet OG rettet FØR migrasjon 057/redeploy -- ikke en kjent,
    ureparert feil et øyeblikk i produksjon.

    **Verifisering:**
    1. Håndregnet API-testskript (`test_scramble_solo_match.py`, 31/31
       bestått): 3-manns lag (HCP 8/14/20 → TeamAverage 14) mot
       individualspiller (HCP 10, 100 %), `match_play_strokes([14,10])`,
       5 spilte hull → "2 UP", `hole_results`/`strokes_received` stemte
       hånd-for-hånd mot forventet forløp. Dynamisk `team_is_a`-utledning
       i selve testen (ikke antatt fast) for å faktisk teste sorterings-
       konvensjonen, ikke bare den ene retningen.
    2. Full stack, ekte nettleser (fjerde scratch-miljø, ekte
       `docker build` av frontend-imaget): opprettet en runde gjennom
       hele veiviseren, satte team/individualspiller opp, entret score
       via BEGGE veiviserne (fant og rettet bug-en HER, se over), bekreftet
       `LeadZone` viste korrekt "X UP" og skiftet leder korrekt, bekreftet
       `MatchScorecardGrid` viste eksakt ÉN rad for laget (ikke tre) og
       riktig "N UP"-progresjon per hull, fullførte runden via ekte
       "Fullfør runde"-knapp.
    3. Regresjonsbekreftet SEPARAT at samme fiks løser samme bug for den
       allerede-live slagspill-varianten (`scramble_solo`): opprettet en
       ny runde med det formatet i samme scratch-miljø, bekreftet at
       `SideScoreWizard` nå åpner korrekt for lagsiden (før fiksen: samme
       evige spinner).
    4. `test_isolation.sql` uendret 12/12. Scratch-miljøet (DB/rolle/
       MinIO/API-/frontend-containere/images) ryddet opp fullstendig
       etter bruk.

    **Rullet ut live 2026-08-05** (samme økt som punkt 22): migrasjon 057
    kjørt mot ekte `teecup_db` (`round_play_format_check` bekreftet å nå
    inkludere `'scramble_solo_match'`), `docker compose up -d --build
    teecup_api teecup_frontend` (inkluderer BÅDE matchspill-formatet OG
    bug-fiksen over -- fiksen var derfor allerede live for ekte
    `scramble_solo`-lagspillere idet denne utrullingen fullførte), begge
    containere boot-et rent, `/my-rounds/new` → 200 over https.

24. **"Bruk som mal for egen bane" viste seg for bredt — 2026-08-05,
    rent frontend, ingen skjema-/backend-endring.** Brukeren rapporterte
    at knappen (fork en eksisterende personlig bane til utgangspunkt for
    en NY) dukket opp i det vanlige "Egen bane"-søket i opprett-runde-
    veiviseren, uansett om målet var å VELGE en eksisterende bane å
    spille på eller å opprette en helt ny -- feil sted for en
    mal-handling som kun gir mening når brukeren faktisk har sagt at
    målet er å opprette.

    **Avklart med bruker (tre alternativer presentert)**: knappen skal
    KUN vises når brukeren eksplisitt har trykket "Opprett ny bane" og
    derfra velger å basere den nye banen på en eksisterende, ikke i det
    vanlige søket.

    **Fiks (`frontend/components/new-round.tsx`):** ny `S1Sub`-verdi
    `"own-search-template"`, distinkt fra det vanlige `"own-search"`.
    `OwnSearch`s `onUseAsTemplate`-prop gjort valgfri (var påkrevd) --
    "Bruk som mal for egen bane" rendres nå kun `{onUseAsTemplate &&
    (...)}`, akkurat som den allerede korrekt gaterte offisiell-bane-
    varianten i `CourseList`. Det vanlige `"own-search"`-kallet (nådd fra
    Step1 sitt "Egen bane"-kort) slutter å sende med `onUseAsTemplate` i
    det hele tatt. Ny lenke "Basér på en eksisterende bane i stedet" lagt
    til øverst i `OwnCreateStep`s blanke skjema (kun synlig når
    `!initial`, altså før noen mal er valgt) -- trykk navigerer til
    `"own-search-template"`, som gjenbruker `OwnSearch` UENDRET, denne
    gangen MED `onUseAsTemplate` satt (samme forhåndsutfyllings-bro som
    allerede fantes for offisielle baner). Ny `ownCreateOrigin`-state
    (løftet til toppnivå-hook-en, ved siden av `s1Sub`) sporer hvor
    "own-create" skal gå TILBAKE til (vanlig søk, mal-søk, eller broen
    fra offisielle baner) -- uten denne ville tilbake-knappen fra
    mal-søket hoppet feil sted.

    **Scratch-verifisert i ekte nettleser** (femte scratch-miljø, ekte
    `docker build` av frontend-imaget): bekreftet at "Bruk som mal for
    egen bane" IKKE lenger vises i det vanlige "Egen bane"-søket;
    bekreftet at "Basér på en eksisterende bane i stedet"-lenken vises i
    det blanke opprett-skjemaet; trykket den, bekreftet at knappen NÅ
    vises i det samme søket; trykket "Bruk som mal", bekreftet at
    opprett-skjemaet forhåndsfylte navn/18 hull/utslag korrekt fra den
    valgte banen; bekreftet at tilbake-knappen fra opprett-skjemaet gikk
    til mal-søket (ikke det vanlige søket eller helt til `source`).
    Ingen konsoll-feil. Scratch-miljøet (DB/rolle/MinIO/API-/frontend-
    containere/images) ryddet opp fullstendig etter bruk.

    **Rullet ut live 2026-08-05** (kun `teecup_frontend` bygget/
    restartet -- ren frontend-endring, ingen skjema-/API-endring).
    `/my-rounds/new` → 200 over https etterpå.

    **Oppfølging samme kveld, 2026-08-06:** brukeren sendte skjermdump og
    rapporterte at knappen FORTSATT dukket opp -- men i en ANNEN gren enn
    den akkurat rettet: `CourseList` (offisielle baner, "Tjøme Golfklubb"
    → "Hovedbanen") viste den for enhver offisiell bane, uansett om målet
    var å spille eller å opprette. Punktet over rettet kun `OwnSearch`
    (egne baner) -- samme feilmønster fantes uavhengig i den offisielle
    grenen, siden begge alltid hadde vist knappen unconditionalt bortsett
    fra en `templateHoles.length > 0`-sjekk.

    **Utvidet fiks:** erstattet den forrige, EGEN-bane-spesifikke
    `"own-search-template"`-S1Sub-verdien med en generell boolsk
    `templateMode`-state (løftet til toppnivå, ved siden av `s1Sub`).
    `CourseList`s `onUseAsTemplate` gjort valgfri på samme måte som
    `OwnSearch`s allerede var -- begge rendrer nå kun
    `{onUseAsTemplate && ...}`, styrt av `templateMode`. Ny mellomskjerm
    `"template-source"` (samme to `SourceCard`-valg som det aller første
    Bane&tid-steget, men med egen tittel "Basér på en eksisterende bane")
    lar brukeren velge OFFISIELL eller EGEN bane som malkilde -- nådd fra
    samme "Basér på en eksisterende bane i stedet"-lenke i det blanke
    opprett-skjemaet. `templateMode` settes eksplisitt `false` i det
    aller første Bane&tid-steget (fresh start) og ved "Avbryt" fra
    opprett-skjemaet, `true` kun fra `"template-source"`.

    **Scratch-verifisert på nytt** (sjette scratch-miljø): gjenskapte
    brukerens eksakte scenario (offisiell-bane-søk → "Tjøme Golfklubb" →
    "Hovedbanen") mot ekte teeoff-data, bekreftet at "Bruk som mal for
    egen bane" IKKE lenger vises der; bekreftet at "template-source"-
    mellomskjermen vises korrekt fra opprett-skjemaets lenke; valgte
    "Offisiell bane" derfra, søkte samme klubb, bekreftet at knappen NÅ
    vises (med justert beskrivelsestekst: "Velg hvilken bane du vil
    bruke som mal..."); trykket den, bekreftet at opprett-skjemaet
    forhåndsfylte navn/18 ekte hull/4 ekte utslag korrekt fra Hovedbanen.
    Ingen konsoll-feil. Scratch-miljøet ryddet opp fullstendig.

    **Rullet ut live 2026-08-06** (kun `teecup_frontend`).
    `/my-rounds/new` → 200 over https etterpå.

25. **Kommentarer/bilder på frittstående runder + samlet `/my-feed`-side
    (ADR-044, migrasjon 058) — BYGGET OG SCRATCH-VERIFISERT 2026-08-06,
    venter på utrulling mot ekte `teecup_db`.** Brukeren ba om samme
    "Banter Board"-mulighet (bilde+tekst) som org-turneringer allerede har
    (ADR-025), for en enkelt frittstående runde, pluss en sentral feed-side
    som samler dette på tvers av runder. Underveis avdekket research at
    rundevisibilitet (ADR-036 fase 2 -- `round.visibility_mode`/
    `round_visible_category`) allerede var bygget og live siden 2026-07-28
    -- en gammel "Neste steg"-seksjon i denne loggen sa fortsatt "ikke
    bygget", aldri rettet opp. Denne runden gjenbrukte den infrastrukturen
    uendret; omfanget ble dermed smalere enn først antatt.

    **To beslutninger avklart eksplisitt med bruker (AskUserQuestion) FØR
    planlegging:** (1) skriverett = alle som kan SE runden kan også POSTE
    (ikke kun eier/medspillere -- overstyrer et snevrere utkast notert
    tidligere samme dag i FEATURE_BACKLOG.md), (2) feed på en ny, dedikert
    side (`/my-feed`), ikke en dashbord-seksjon. Fullstendig plan skrevet
    og godkjent i Plan Mode før noe ble bygget (to Explore-runder + én
    Plan-agent-syntese, grundig kodegrunnet mot eksisterende `_can_view_
    round`/`list_friends_on_course`/`messaging.py`-mønstre).

    **Datamodell:** ny, EGEN `round_message`-tabell (migrasjon 058), IKKE
    en utvidelse av org sin `message`-tabell -- `round` har verken
    `organization_id` eller RLS (ADR-033 Beslutning A). Samme feltform
    (forfatter-snapshot, valgfri tekst, valgfri `image_key`) og samme
    MinIO+AVIF-pipeline (`app/storage.py`) gjenbrukt uendret, egen
    `prefix="round_messages"`. Scratch-verifisert alene først: skjema,
    indekser, grants bekreftet, `test_isolation.sql` 12/12.

    **Backend:** ny fil `app/routers/round_messages.py` (`rounds.py` er
    allerede 5500+ linjer) -- `GET`/`POST /rounds/{id}/messages`, `DELETE
    /rounds/{id}/messages/{message_id}` (forfatter ELLER rundeeier kan
    slette, samme "forfatter-eller-admin"-modell som org-feedens
    `delete_feed_message`), og en ny `GET /feed`-aggregering modellert på
    `list_friends_on_course` sin reverserte retning (viewer → synlige
    runder), justert til å inkludere egne runder uansett `visibility_mode`
    og uten "spiller nå"-filteret (all-time, keyset-paginert). Gjenbruker
    `_can_view_round`/`_get_viewable_round_or_404` fra `rounds.py` uendret
    som BÅDE lese- og post-sjekk. Ingen ny WebSocket-kanal -- gjenbruker
    eksisterende `broadcast_round_update`, `RoundMessages`-komponenten
    henter på nytt via et nytt `messagesRefreshTick`-signal i
    `round-detail.tsx` sin allerede eksisterende WS-lytter. Liten
    refaktor i samme runde: `messaging.py` sin lokale `_read_optional_
    image` flyttet til `app/storage.py` som delt `read_optional_image`,
    brukt av begge routerne nå.

    **En reell bug funnet OG rettet FØR noen verifisering:** `GET /feed`
    sin `before`-parameter krasjet med 500 (`asyncpg.exceptions.DataError`)
    -- asyncpg godtar ikke en ren tekststreng mot en `timestamptz`-cast i
    SQL-en, krever et faktisk `datetime`-objekt. Rettet ved å la Pydantic
    parse query-parameteren som `datetime` direkte i stedet for `str`.

    **Håndregnet auth-testskript, 30/30 sjekker bestått:** eier poster/
    sletter egen (private) runde; lenket medspiller (ikke eier) poster,
    en ANNEN ikke-eier-medspiller kan IKKE slette den (403); eieren KAN
    slette andres innlegg på egen runde (moderasjon); en fremmed uten
    innsyn avvist på både GET og POST (403/404) for en privat runde; en
    `friends`-synlig runde med RIKTIG venne-kategori gir GET+POST; med
    FEIL/manglende kategori avvises selv GET; `GET /feed` bekreftet riktig
    sett per bruker (eier ser alle egne uansett visibility, en venn ser
    kun offentlige + riktig-kategoriserte `friends`-runder, en fremmed ser
    kun offentlige), paginering (`before`-cursor) uten hopp/duplikat.
    Ekte bildeopplasting testet direkte mot scratch-MinIO (utenfor HTTP,
    `storage._client.stat_object`) -- bekreftet 488 byte AVIF,
    `content_type=image/avif`.

    **Frontend bygget via V0** (brukerens eksplisitte, stående
    arbeidsfordeling -- se CLAUDE.md-notat): to prompter skrevet og
    levert i chat (kommentarseksjon, feed-side), begge forankret i
    `DESIGN_SYSTEM.md` sine fargetokens/tilgjengelighetsregler og eksakte
    data-kontrakter fra det allerede bygde API-et. Zip 32 (`round-
    messages.tsx`) og zip 33 (samme + ny `feed.tsx`) diffet ordrett mot
    hverandre FØR integrering -- ingen utilsiktet drift utover den ene
    nye filen. To små, bevisste integrasjonsjusteringer: fjernet en
    ubrukt `cn`-import, og lot `Feed`-komponenten selv hente `/auth/me`
    (samme mønster som `round-detail.tsx`) i stedet for å kreve
    `currentUserId` som ekstern prop -- konsekvent med at alle andre sider
    i appen er tynne wrappere som selv henter egen brukeridentitet.
    `RoundMessages` montert som en ny seksjon NEDERST på `round-detail.
    tsx` sin `pageTab === "score"` (ikke en ny rundefane, ikke inni
    admin-"manage"-fanen -- se ADR-044 for begrunnelsen). Ny `/my-feed`-
    side + inngangspunkter: `SeeAllLink` lagt til dashbordets "Venner på
    banen"-seksjon, ny lenke i `/more`-menyen.

    **KRITISK rute-/rewrite-kollisjon funnet under full-stack-
    verifisering, samme klasse som `/rounds`→`/my-rounds` og
    `/friends`→`/my-friends`:** frontend-siden ble først lagt på `/feed`
    -- nøyaktig samme streng som det flate API-endepunktet. Uten en
    eksplisitt rewrite-regel vant Next.js sin egen side-rute, så
    `fetch("/feed")` fra klienten fikk appens egen HTML tilbake (stille
    feilet JSON-parsing, "Kunne ikke laste feeden"-feilmelding i UI-et).
    Rettet ved å (1) flytte siden til `/my-feed` OG (2) legge til en
    manglende, eksplisitt `/feed`-rewrite-regel i `next.config.mjs` (som
    rett og slett ikke fantes fra før -- retting nummer to var nødvendig
    uavhengig av sideflyttingen). Ekte typesjekket produksjonsbuild fanget
    IKKE denne feilen (kun kjøretid avslørte den) -- funnet ved faktisk
    nettleser-testing, ikke ved bygging.

    **En andre reell bug funnet under samme verifiseringsrunde:**
    `RoundMessages` hentet meldinger KUN ved mount -- `round-detail.tsx`
    sin eksisterende `/ws/rounds/{id}/live`-lytter (allerede der fra
    tidligere, brukt av scorekort/format-resultat) var aldri koblet til
    den nye kommentarseksjonen. Rettet med et nytt `messagesRefreshTick`-
    signal, samme mønster som det eksisterende `formatResultRefreshTick`.
    Verifisert med en EGEN, minimal scratch-Caddy (`handle /ws/* {
    reverse_proxy ... }`, samme regel som den ekte `/opt/teeoff/deploy/
    Caddyfile`) satt opp spesifikt for denne testen -- uten den ville
    WebSocket-oppgraderingen aldri nådd API-et i det hele tatt (Next.js
    sitt eget rewrite-lag proxyer ikke WS pålitelig i standalone-modus,
    dokumentert allerede i den ekte Caddyfilen). Bekreftet: en kommentar
    postet via et separat API-kall dukket opp automatisk i en åpen
    nettleserfane, ingen interaksjon eller reload nødvendig.

    **Scratch-miljøet ryddet opp fullstendig** etter bruk (DB/rolle/MinIO/
    API-/frontend-/Caddy-containere og -images, V0-eksport-zip-ene fjernet
    fra prosjektroten uten å bli spurt, samme rutine som alltid for merget
    V0-eksport).

    **Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt: migrasjon
    058 kjørt mot ekte `teecup_db` (`round_message`-tabellen bekreftet
    tilstede etterpå), `docker compose up -d --build teecup_api
    teecup_frontend`, begge containere boot-et rent. `/health`/`/my-rounds/
    new`/`/my-feed` → 200 over https, `GET https://teecup.golf/feed`
    bekreftet å proxye korrekt til backend (401 JSON `NOT_AUTHENTICATED`,
    IKKE frontend-sidens HTML -- bekrefter rewrite-fiksen virker i
    produksjon, ikke bare i scratch), `teeoff.no` upåvirket.

26. **To bugfikser, 2026-08-06, rullet ut samme dag:**
    - **"Venner på banen"-kortet lenket til feil rute.** Brukeren
      rapporterte (video vedlagt) at et klikk på en venns pågående runde
      på dashbordet ga "Denne runden finnes ikke, eller du har ikke
      tilgang til den" -- selv om runden faktisk var synlig for
      vedkommende (`GET /friends/on-course` viste den korrekt). Rotårsak:
      `dashboard.tsx` sin `LiveFriends`-komponent lenket til
      `/my-rounds/{id}` (eier/medspiller-only-visningen,
      `_get_accessible_round_or_404`) i stedet for `/watch/{id}` (den
      allerede eksisterende tredjeparts-visningen bygget for nettopp
      dette, ADR-036 fase 2). Samme feilmønster funnet OG rettet i den
      nybygde `/my-feed`-siden (punkt 25) -- `FeedCard` lenket likeens
      alltid til `/my-rounds/{id}` uansett eierskap. Begge rettet til å
      route riktig basert på om runden faktisk er brukerens egen.
    - **Kamera lot seg ikke aktivere ved bildeopplasting på mobil**, seks
      steder i appen (rundekommentarer, org-feed, avatar, lag-chat,
      turnering-hero/sponsor-bilder). Rotårsak: `<input type="file"
      accept="image/jpeg,image/png,image/webp,image/gif">` -- flere
      eksplisitte MIME-typer i `accept` gjør at mange mobilnettlesere kun
      tilbyr galleri, ikke kamera, i filvelgeren. Rettet til
      `accept="image/*"` alle seks steder (server-siden validerer uansett
      strengt mot `storage.ALLOWED_INPUT_CONTENT_TYPES`, så dette svekker
      ingen kontroll).

    Begge ren frontend, ingen migrasjon. Ekte typesjekket produksjonsbuild
    kjørt før utrulling. **Rullet ut live 2026-08-06** (kun
    `teecup_frontend` bygget/restartet), `/dashboard` → 200, `teeoff.no`
    upåvirket.

27. **Ekte TeeCup-logo tatt i bruk som app-ikon, erstatter den midlertidige
    grønne golfflagg-placeholderen — 2026-08-06.** Brukeren lastet opp
    `TeeCup logo.svg` (ikon+ordmerke i én fil, Inkscape-eksport med
    raster-baserte masker/filtre, ikke rene vektor-tekstobjekter) og
    spurte om splitting av ikon fra tekst var mulig uten flere opplastede
    varianter.

    **Splitting løst programmatisk, ikke manuelt:** brukte nettleserens
    `getBBox()` på hver topp-nivå-`<g>` i den innlastede SVG-en for å
    måle faktiske posisjoner -- avslørte at ikonet (ball/pokal/tee) består
    av tre atskilte grupper (x: 0–169 av 656 totalt) mens "TeeCup"-
    ordmerket er seks separate bokstav-grupper (x: 179–407) -- ingen
    gjetning nødvendig. Beskåret til et eget ikon-SVG via et rent
    `viewBox`-endring på en kopi av originalfilen (ingen gruppe fjernet
    eller XML-struktur røsket i -- alle filter-/maske-/clipPath-
    definisjoner ligger nestet INNI selve gruppene, bekreftet empirisk:
    et forsøk på å trimme bort de ubrukte tekst-gruppene for å spare
    filstørrelse brøt renderingen umiddelbart, samme "hele filen eller
    ingenting"-begrensning som Inkscape sin raster-trace-eksport gir --
    reverserte til den fullstendige, verifisert fungerende versjonen i
    stedet for å risikere en ødelagt fil for en fil-størrelse-optimering).
    `icon.svg` er dermed 416 KB (arves fra kildefilens embedded
    raster-masker) -- fungerer korrekt, men en ekte vektor-reeksport fra
    designer ville gitt en vesentlig lettere fil om ønskelig senere.

    **Ekte nettleser brukt til rasterisering** (ingen lokal SVG->PNG-
    rasterizer tilgjengelig i miljøet): Chrome DevTools sitt
    skjermbilde-verktøy, med små HTML-wrapper-sider satt til eksakt
    mål-pikselstørrelse per format, generert alle seks filene appen
    faktisk bruker: `icon.svg` (transparent, favicon), `icon-light-
    32x32.png`/`icon-dark-32x32.png` (transparent, samme innhold --
    fungerer i begge fargetema), `apple-icon.png` (180×180, opak hvit
    bakgrunn -- iOS støtter ikke transparens), `icons/icon-192.png` og
    `icons/icon-512.png` (PWA-manifest, opak hvit bakgrunn), `icons/
    icon-maskable-512.png` (ikonet holdt innenfor sentrale ~63 % --
    trygt innenfor W3C sin ~80 %-sikre-sone for maskable-ikoner som
    OS-en klipper med egen maske). Hvert format visuelt bekreftet
    (`Read` på hver genererte PNG) før plassering.

    Hele ordmerket (ikon+"TeeCup"-tekst) lagret uendret som `public/
    teecup-wordmark.svg` for senere bruk (f.eks. innloggingsside),
    ikke koblet inn noe sted ennå -- kun selve app-ikon-oppgaven løst nå.
    `app/manifest.ts` sin kommentar oppdatert til å reflektere at
    placeholder-fasen er over.

    Ren frontend, ingen migrasjon. Ekte typesjekket produksjonsbuild
    kjørt. **Rullet ut live 2026-08-06** (kun `teecup_frontend`),
    `/icon.svg`/`/apple-icon.png`/`/icons/icon-512.png` → 200 over https,
    `teeoff.no` upåvirket.

28. **Tilskuer-visning (`/watch/[id]`): tre faner + "Spillere og runde",
    full paritet med eiersiden (ADR-045) — BYGGET OG SCRATCH-VERIFISERT
    2026-08-06, venter på utrulling.** Direkte oppfølging av punkt 26 sin
    bug (feil lenke fra "Venner på banen") -- brukeren ba samtidig om at
    selve tilskuer-siden (fram til da ÉN enkelt side, kun kompakt status)
    skulle få samme tre faner som eiersiden (Score/Scorekort/Leaderboard)
    og "Spillere og runde" (deltakerliste, HCP/utslag/tildelte slag,
    flight-gruppering), uten eier-kun-handlingene. Bekreftet med bruker
    (AskUserQuestion, to spørsmål): full formatspesifikk detalj på
    Scorekort-fanen (ikke en forenklet fellesvisning), og
    flight-gruppering bygget nå (ikke utsatt).

    **Nøkkelbeslutning, funnet under research (Explore-agent + egen
    lesning av begge filene):** `round-scorecard.tsx` (`RoundScorecard`)
    og `round-leaderboard.tsx` (`RoundLeaderboard`) sin faktiske rutenett-
    /tavle-rendring var ALLEREDE ren, skrivefri visning -- redigering
    skjer et helt annet sted. Det eneste som hindret gjenbruk for
    tilskuere var at alle interne data-hentinger (fire hooks/effekter
    per fil, pluss WS-tilkoblingen) hardkodet `/rounds/*`-prefikset. I
    stedet for å bygge nye, forenklede tilskuer-komponenter (ville gitt
    UFULLSTENDIG formatparitet eller duplisert ~1000+ linjer formatlogikk),
    la til en valgfri `publicMode`-prop på begge de EKSISTERENDE
    komponentene -- default `false` (INGEN endring i eiersidens
    oppførsel), `true` bytter alle interne URL-er til `/public/rounds/*`
    og WS-kanalen til den offentlige. Samme kode, to datakilder, ekte
    full formatparitet (alle 17 formater) uten duplisert vedlikehold.

    **Backend:** ett nytt endepunkt, `GET /public/rounds/{id}/
    flight-group` (`app/routers/rounds.py`) -- speiler den eksisterende
    autentiserte varianten, men med en ny `_viewable_flight_rows`
    (bruker `_can_view_round` PER søsken-flight uavhengig, siden en
    flight-gruppe kan ha søsken med ulik `visibility_mode`). Ingen
    migrasjon.

    **Frontend, ny fil-for-fil:** `watch-tabs.tsx` (ny, liten fane-stripe
    -- BEVISST ikke en gjenbruk av `RoundHeader`, som alltid render et
    "Administrer"-tannhjul/admin-dialog uansett hvilke handlere som gis
    inn -- den nye komponenten inneholder rett og slett ingen slik kode).
    `watch-players.tsx` (ny "Spillere og runde", IKKE et forsøk på å
    gjøre `round-detail.tsx` sin lokale, ikke-eksporterte `PlayerList`
    gjenbrukbar via `canManage=false` -- egen, read-only-only komponent,
    ingen +Medspiller-/Rediger-/Fullfør-/Slett-kode i det hele tatt).
    `watch-round.tsx` omskrevet: Score-fanen viser nå fane-stripen +
    kompakt status + "Spillere og runde" -- selve matchstatus-/skins-/
    slagspill-visningen FLYTTET til den nye Leaderboard-fanen (erstattet
    av `RoundLeaderboard publicMode`, som dekker alle formater -- lukker
    et eksisterende gap der den gamle `watch-round.tsx` sin lokale
    `TWO_SIDED_FORMATS`-liste kun dekket 8 av 17 formater). To nye tynne
    ruter, `/watch/[id]/scorecard` og `/watch/[id]/leaderboard`
    (`watch-scorecard.tsx`/`watch-leaderboard.tsx`, hver bare fane-stripe
    + `RoundScorecard`/`RoundLeaderboard` med `embedded publicMode`).

    **Verifisering, samme scratch-Caddy-disiplin som ADR-044:** 6/6
    håndregnede sjekker for det nye flight-group-endepunktet (anonym ser
    kun offentlige søsken-flighter, venn med riktig kategori ser i
    tillegg `friends`-synlige, eier ser alle, fremmed uten vennskap ser
    kun offentlige, privat anker-runde avvist helt). `tsc --noEmit` rent
    på hele frontend-prosjektet. Ekte nettleser: **regresjon FØRST** --
    eiersidens `/my-rounds/{id}/scorecard`/`/leaderboard` bekreftet
    PIKSEL-IDENTISK (samme tall, samme layout) FØR noe nytt ble testet.
    Deretter tilskuer-sidene: alle tre faner, "Spillere og runde" uten
    eier-knapper, ETT allerede-dekket format (`stroke`) OG ETT tidligere
    udekket format (`flag`) begge bekreftet rendret korrekt gjennom
    `/watch/{id}/leaderboard` -- beviser formatparitet-lukkingen direkte.
    En privat rundes tilskuer-sider (alle tre) bekreftet korrekt avvist
    for en ANONYM leser i en egen, isolert nettleser-kontekst (ikke bare
    et API-kall) med riktig feilmelding og "Tilbake til dashbordet"-
    lenke. `test_isolation.sql` uendret 12/12. Scratch-miljøet (DB/rolle/
    MinIO/API-/frontend-/Caddy-containere og -images) ryddet opp
    fullstendig etter bruk.

    **Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt: `docker
    compose up -d --build teecup_api teecup_frontend` (ingen migrasjon),
    begge containere boot-et rent. `/health` → 200, `/dashboard` → 200,
    nytt endepunkt bekreftet (`/public/rounds/{tilfeldig-id}/flight-group`
    → 404 for en ikke-eksisterende runde), alle tre nye/endrede
    `/watch/[id]`-ruter → 200, `teeoff.no` upåvirket.

29. **Bugfiks: kommentarer/bilder (ADR-044) fantes ikke i tilskuer-
    visningen — 2026-08-06.** Brukeren rapporterte at en person en runde
    er delt med ikke kunne se selve samtalen. Rotårsak: `RoundMessages`
    ble aldri montert på `/watch/[id]` -- kun på `round-detail.tsx` sin
    egen Score-fane (eier/deltaker). Backend var allerede riktig
    (`GET`/`POST /rounds/{id}/messages` bruker `_get_viewable_round_or_
    404`, synlighets-gatet, ikke eier/deltaker-only, siden ADR-044 ble
    bygget) -- ren frontend-mangel.

    **Én reell backend-mangel funnet underveis:** `PublicRoundOut`
    eksponerte `owner_display_name` men ALDRI eierens faktiske
    `user_id` -- `RoundMessages` sin "forfatter ELLER rundeeier kan
    slette"-sjekk (`roundOwnerUserId === currentUserId`) hadde dermed
    ingen korrekt verdi å sammenligne mot fra tilskuer-siden. Lagt til
    `owner_user_id: str` på `PublicRoundOut` (samme personvernsnivå som
    det allerede eksponerte visningsnavnet -- en ugjennomsiktig UUID
    avslører ingenting nytt).

    **Rettet:** `watch-round.tsx` monterer nå `<RoundMessages>` på
    Score-fanen, med `roundOwnerUserId={round.owner_user_id}` og
    `currentUserId` fra en ny `/auth/me`-henting (tolerant for anonym,
    samme mønster som resten av appen).

    **Verifisert, isolert scratch-miljø:** 6/6 håndregnede sjekker
    (`/public/rounds/{id}` eksponerer korrekt `owner_user_id`, en venn
    kan poste via synlighet -- ikke eier/deltaker, begge kan lese, eier
    kan moderere/slette vennens innlegg). `tsc --noEmit` rent. Ekte
    nettleser: kommentarseksjonen bekreftet synlig på `/watch/{id}`,
    ingen konsollfeil utover forventet 401 på `/auth/me` for en anonym
    leser (allerede tolerert i koden). Scratch-miljøet ryddet opp.

30. **Bugfiks: kamera manglet helt ved bildeopplasting, ekte "prøv igjen"-
    feil skjult bak en generisk melding — 2026-08-06.** Brukeren
    rapporterte (skjermdump fra ekte enhet, Pixel 8 Pro, TeeCup installert
    som PWA i Chrome) at "Legg til bilde" ALDRI viste noe kamera-
    alternativ, kun galleri -- og at selve posten deretter feilet med
    "Kunne ikke poste kommentaren. Prøv igjen." uten noen forklaring.

    **To atskilte, reelle rotårsaker, begge bekreftet:**
    1. Siden Android 13 bruker Chrome (og andre nettlesere) systemets
       eget "Photo Picker" for `<input type=file accept="image/*">` UTEN
       `capture`-attributt -- denne velgeren viser KUN galleri, ALDRI et
       kamera-alternativ, uansett hvilken `accept`-verdi som er satt
       (den tidligere fiksen, `accept="image/*"` alene, var altså
       nødvendig, men ikke tilstrekkelig for Android 13+). Rettet ved å
       splitte den ene filvelgeren i to atskilte inputs/knapper -- én
       med `capture="environment"` (rett til kamera), én uten (rett til
       galleri) -- i `round-messages.tsx`, `team-chat.tsx`, og
       `public-tournament.tsx` sin `TournamentFeed` (de tre stedene som
       faktisk brukes aktivt for bildeopplasting fra mobil; `account-
       settings.tsx`/`tournament-presentation.tsx` sine engangs-avatar-/
       hero-bilde-opplastinger har fortsatt kun galleri -- lavere
       prioritet, tas senere om ønskelig).
    2. Selve feilmeldingen ved en mislykket post var en blindt generisk
       `catch { setPostError("Kunne ikke poste... Prøv igjen.") }` --
       den faktiske, spesifikke backend-feilen (f.eks. "Bildet er for
       stort (maks 8 MB)") ble aldri lest fra svaret og dermed aldri
       vist. Rettet til å faktisk lese `{"detail":{"code","message"}}`-
       kontrakten (samme form som resten av API-et, `app/errors.py`) og
       vise den ekte meldingen, med en generisk norsk fallback kun hvis
       svaret mot formodning ikke er JSON. Lagt til et klient-side
       størrelsessjekk (samme 8 MB-grense som `storage.MAX_UPLOAD_BYTES`)
       for umiddelbar, presis feilmelding uten en unødvendig tur-retur
       til serveren for noe som uansett ville blitt avvist -- sannsynlig
       den FAKTISKE årsaken til brukerens opprinnelige feil, siden
       moderne telefonkamera-bilder lett kan overstige 8 MB.

    **Oppfølging samme dag:** brukeren spurte -- helt riktig -- om selve
    8 MB-grensen egentlig var et praktisk problem, siden alle bilder
    uansett konverteres til AVIF (mye mindre) før lagring. Bekreftet i
    `app/storage.py`: `MAX_UPLOAD_BYTES`-sjekken skjer på RÅ input, FØR
    konverteringen -- den beskytter altså ikke lagringsstørrelsen (det
    gjør AVIF-steget, uavhengig av inputstørrelse), den var bare en
    vilkårlig øvre grense som kunne avvise et helt normalt telefonbilde
    før det fikk sjansen til å konverteres ned. Hevet til 20 MB (server-
    siden `app/storage.py` OG de tre nye klient-side sjekkene over,
    samt feilteksten) -- rikelig for moderne telefonkamera-JPEG-er,
    fortsatt en reell grense mot noe genuint urimelig stort.

    Ren frontend + denne ene backend-konstanten, ingen migrasjon.
    `tsc --noEmit` rent, `py_compile` rent. **Rullet ut live 2026-08-06**,
    bruker bekreftet eksplisitt: `docker compose up -d --build teecup_api
    teecup_frontend`, begge containere boot-et rent. `/health` → 200,
    `/dashboard` → 200, `teeoff.no` upåvirket.

31. **Kommentarstrømmen på en runde sortert om: nyeste øverst — 2026-08-06.**
    Brukeren ba om dette rett etter forrige punkt -- `round_message`-
    listen (`round-messages.tsx`) var kronologisk eldst-først (samme
    mønster som en vanlig chat), brukeren ville ha nyeste øverst i
    stedet (en oppdateringsstrøm, ikke en samtale man leser fra start).
    Endret `ORDER BY created_at` → `created_at DESC` i `GET /rounds/{id}/
    messages` (`app/routers/round_messages.py`), og en ny post legges nå
    øverst i listen lokalt (`[created, ...prev]` i stedet for `[...prev,
    created]`). Lag-chatten (`messaging.py`) og org-oppslagstavlen
    (`public-tournament.tsx`) er BEVISST uendret -- en ekte samtale
    leses fortsatt kronologisk.

    Verifisert i isolert scratch-miljø (postet tre meldinger, bekreftet
    `GET`-rekkefølgen er nyeste-først). `tsc`/`py_compile` rent.
    **Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt (samme
    utrulling som punkt 30). `teeoff.no` upåvirket.

32. **"Nyeste først" utvidet til ALLE meldingsstrømmer — 2026-08-06,
    overstyrer punkt 31 sin "bevisst uendret"-begrunnelse.** Brukeren:
    "Nyeste først. Over alt." -- en eksplisitt, direkte instruks om at
    lag-chatten og org-oppslagstavlen (som punkt 31 bevisst lot forbli
    kronologiske, med en UX-begrunnelse om at "en ekte samtale leses
    fortsatt kronologisk") OGSÅ skulle bli nyeste-først, på tvers av min
    egen tidligere vurdering.

    Endret `ORDER BY created_at` → `created_at DESC` for BÅDE
    `list_team_messages` og `get_feed` (`app/routers/messaging.py`).
    `team-chat.tsx`: WS-mottak og lokal post-oppdatering endret fra
    append (`[...prev, msg]`) til prepend (`[msg, ...prev]`); fjernet
    `bottomRef`-baserte auto-scroll-til-bunn-`useEffect`en (unødvendig
    når nyeste alltid er øverst, synlig uten scrolling).
    `public-tournament.tsx` sin `TournamentFeed`: samme prepend-endring i
    BÅDE WS-mottak og `send()`.

    Verifisert i isolert scratch-DB (fra `teecup_db`-mal): satte inn tre
    meldinger med forskjøvne tidsstempler direkte i `message`-tabellen
    for både `scope='team'` og `scope='tournament_feed'`, kjørte de
    EKSAKTE SELECT-spørringene fra koden, bekreftet nyeste-først-
    rekkefølge for begge. `tsc --noEmit`/`py_compile` rent.
    `test_isolation.sql` uendret 12/12. Scratch-DB/rolle ryddet opp.

    **Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt ("Ja
    takk."): `docker compose up -d --build teecup_api teecup_frontend`,
    begge containere boot-et rent (ingen migrasjon). `teeoff.no`
    upåvirket.

33. **Emoji-reaksjoner + trådede kommentarer på innlegg — 2026-08-06, se
    ADR-046 for full begrunnelse/beslutningsdetalj.** Samme melding som
    punkt 32 ("... Husk også at man skal kunne like (eller bruke andre
    emojier) og kommentere på innlegg."). Migrasjon 059
    (`round_message_reaction`/`_comment`, `message_reaction`/`_comment`
    -- to tabellpar, ett uten RLS speiler `round_message`, ett RLS'et
    speiler `message`). Nye endepunkter i `round_messages.py` og
    `messaging.py`: `PUT`/`DELETE .../reaction` (upsert, ett fast
    kuratert emoji-sett, én reaksjon per bruker per innlegg), `GET`/
    `POST .../comments` + `DELETE .../comments/{id}` (flat lagring,
    frontend bygger tre-strukturen, kaskade-sletting av svar).
    Autorisasjon speiler hver innleggstypes EKSISTERENDE post-/
    slette-regler uendret (ingen ny modell).

    **Én reell regresjon funnet og rettet FØR utrulling, under ekte
    nettleserverifisering (ikke fanget av API-testskriptet alene):** de
    nye kommentar-endepunktene i `round_messages.py` kalte først
    `broadcast_round_update()` ved en inkonsekvens mot egen plan (planen
    sa eksplisitt ingen live-push for kommentarer/reaksjoner i v1) --
    dette trigget rundens eksisterende WS-tick, som fikk `RoundMessages`
    til å refetche HELE meldingslisten og med det kollapse enhver
    allerede-utvidet kommentartråd andre steder på siden ved hver eneste
    kommentarhandling. Fjernet fra de nye endepunktene, bekreftet rettet
    ved re-test (tråd forble utvidet gjennom en påfølgende reaksjon/
    kommentar).

    **Frontend:** ny delt `post-engagement.tsx` (via ÉN Claude-skrevet
    V0-prompt), integrert i `round-messages.tsx`, `feed.tsx`
    (`FeedCard`), `team-chat.tsx`, `public-tournament.tsx`
    (`TournamentFeed`). To integrasjonsjusteringer utover ren copy-inn:
    byttet V0-eksportens egendefinerte SVG-ikoner til `lucide-react`
    (DESIGN_SYSTEM.md sin "ingen annen ikonpakke"-regel), og flyttet
    `PostEngagement` UT av `feed.tsx` sin `FeedCard`-`<Link>` (var
    opprinnelig nøstet inni hele kort-lenken -- ville trigget navigering
    ved reaksjonsklikk og vært ugyldig nøstet interaktivt innhold).

    **Kjent, bevisst forenkling (ikke en bug):** `public-tournament.tsx`
    sin Oppslagstavle setter `canModerate={false}` alltid i UI-et --
    backend håndhever fortsatt korrekt "forfatter ELLER org-admin"
    uansett, men en org-admin ser ikke en slett-knapp for ANDRES
    kommentarer der ennå (ren UI-fullstendighets-luke, ikke et
    sikkerhetshull).

    **Scratch-verifisert grundig, to runder:** (1) API-nivå -- full
    autorisasjonsmatrise for alle tre innleggstyper (upsert-reaksjon
    bekreftet ikke-stablende, tråding, kaskade-sletting, riktig
    avvisning per type sin faktiske regel, RLS-isolasjon for
    `message_reaction` eksplisitt bekreftet), `test_isolation.sql`
    12/12, ekte produksjonsbuild av frontend kjørt og bekreftet ren.
    (2) Ekte nettleser (egen scratch-Caddy for sesjon/WS) -- reagert,
    byttet reaksjon, postet trådet samtale, slettet med kaskade-
    bekreftelse, bekreftet `feed.tsx` sin Link-nøsting-fiks IKKE
    trigger navigering, bekreftet komponenten rendrer korrekt i alle
    fire flater uten konsollfeil. Begge scratch-miljøer (DB/rolle/API-/
    frontend-/Caddy-containere/images) ryddet opp fullstendig.

    **Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt ("Ja
    takk"): migrasjon 059 kjørt mot ekte `teecup_db` (fire nye tabeller,
    additiv, ingen endring av eksisterende data), deretter `docker
    compose up -d --build teecup_api teecup_frontend`. Begge containere
    boot-et rent. Bekreftet via live `openapi.json`: alle ni nye ruter
    (`.../reaction` × 3, `.../comments` × 3, `.../comments/{id}` × 3)
    riktig registrert og nåbare. `teeoff.no` upåvirket.

34. **Leaderboard lukket gapet mot en konkurrentapp-referanse — 2026-08-06,
    se ADR-047 for full begrunnelse/beslutningsdetalj.** Brukeren viste en
    video av GolfGameBook sitt leaderboard (trykk-for-å-utvide rad → fullt
    hull-for-hull-scorekort + sosiale handlinger per spiller), ba om noe
    minst like bra i TeeCup -- eksplisitt IKKE en kopi av utseendet.
    Research avdekket at kjernemekanikken (utvidbare rader med
    hull-for-hull-rutenett, egen form+farge-språk) allerede var bygget og
    live for `IndividualBoard` siden 2026-07-26 -- denne runden lukket de
    reelle gapene i stedet for å bygge om fra bunnen: ingen sosial-tilgang
    noe sted i leaderboardet, ingen kompakt oppsummeringslinje,
    `TwoSidedBoard`/`HighLowBoard` (to-sidede formater) hadde INGEN
    utvidelse i det hele tatt, `TeamFlightBoard` hadde en flat,
    ikke-utvidbar råscore-liste.

    Avklart eksplisitt med bruker (AskUserQuestion) før bygging: (1)
    `post-engagement.tsx` (ADR-046) reagerer på ETT spesifikt innlegg, ikke
    runden -- alle utvidede rader lenker derfor til den SAMME, allerede
    eksisterende kommentarseksjonen på rundesiden i stedet for en
    (ikke-eksisterende) tråd per spiller. (2) To-sidede formater fikk ÉN
    utvidbar detaljseksjon for hele kampen, ikke et per-rad-mønster (kun 2
    sider, ikke en spillerliste).

    Alt i `frontend/components/round-leaderboard.tsx` + anker-id på
    kommentarseksjonen i `round-detail.tsx`/`watch-round.tsx`: `HoleByHole`
    fikk en oppsummeringslinje + "Kommentarer"-lenke; `TeamFlightBoard`
    sin råscore-liste konvertert til utvidbare rader (ny `TeamMemberRow`,
    gjenbruker `HoleByHole` uendret); `TwoSidedBoard`/`HighLowBoard` fikk
    en ny, delt `MatchDetailSection` som lazy-monterer den nå eksporterte
    `MatchScorecardGrid` (`round-scorecard.tsx`, samme ADR-045-presedens).
    Ingen ny V0-runde, INGEN backend-endring -- ren gjenbruk/eksport av
    allerede godkjente komponenter.

    **Funnet og rettet under nettleserverifisering:** nettleserens egen
    anker-scroll (`#kommentarer`) rakk ikke frem siden elementet ikke
    fantes i DOM-en før async rundedata var lastet -- løst med en liten
    `useEffect` som trigger scrollet på nytt når dataen ankommer.

    **Verifisert:** `tsc --noEmit` rent, ekte produksjonsbuild rent.
    Isolert scratch-miljø, ekte nettleser: regresjon FØRST (slagspill-
    formatets eksisterende utvidelse identisk før noe nytt ble testet),
    deretter ny oppsummeringslinje+lenke (inkl. faktisk fungerende
    anker-scroll) og ny `MatchDetailSection`/`MatchScorecardGrid`-utvidelse
    bekreftet på et fourball-format med ekte 4-spiller-data, samme format
    bekreftet identisk i `publicMode` (anonym tilskuer, egen isolert
    nettleser-kontekst, kommentar-lenken riktig pekende til
    `/watch/{id}#kommentarer`). `TeamFlightBoard`/`HighLowBoard` ikke
    direkte browser-testet (ingen testdata for disse formatene i
    scratch-DB-malen) -- vurdert lav risiko siden de gjenbruker nøyaktig
    samme, allerede-testede kodeveier (`HoleByHole` uendret;
    `MatchDetailSection`/`toGridRound`/`MatchScorecardGrid` uendret), se
    ADR-047 for full begrunnelse.

    **Egen feil funnet og rettet underveis, verdt å notere:** det første
    forsøket på scratch-frontend-bygg pekte ved en kopier-lim-feil
    `TEECUP_API_ORIGIN` mot den EKTE produksjons-API-en (`teecup_api`) i
    stedet for scratch-containeren -- oppdaget da innlogging som en
    scratch-testbruker konsekvent feilet. All interaksjon i det vinduet
    var lesing (sidevisning/rad-utvidelse, ingen skriving/mutasjon), men
    bildet ble likevel bygget på nytt med riktig scratch-API-peker og HELE
    nettleser-verifiseringen kjørt om igjen fra bunnen mot den korrekt
    isolerte stacken før noe ble konkludert.

    Ren frontend, ingen migrasjon. **Rullet ut live 2026-08-06**, bruker
    bekreftet eksplisitt ("Ja takk"): `docker compose up -d --build
    teecup_frontend` (Compose gjenskapte også `teecup_api`-containeren i
    samme kommando -- ingen kodeendring der denne runden, kun en ren
    restart). Begge containere boot-et rent, `/health` → 200. `teeoff.no`
    upåvirket.

35. **Reaksjonsraden viste kun et antall, ikke HVEM som reagerte —
    2026-08-07.** Brukeren, rett etter å ha godkjent V0-eksperimentet på
    `/logg-inn`: "Vi kan ikke se HVEM som liker noe i feeden. Det må på
    plass." En reell, riktig funnet mangel i ADR-046-bygget fra dagen
    før.

    Løst uten noen ny tur-retur til serveren: `ReactionSummary` fikk et
    nytt felt `reactors: list[str]` (fulle navn, roster-kontekst --
    CLAUDE.md sin navneformat-regel), fylt i SAMME grupperte batch-
    spørring som allerede beregnet antallet (`array_agg(...) ORDER BY
    created_at`), i både `round_messages.py` (JOIN `app_user`, samme
    "fornavn+etternavn eller display_name"-fallback som
    `_resolve_round_message_author_name`) og `messaging.py` (LEFT JOIN
    `player` for org-spesifikt visningsnavn, samme mønster som
    `_resolve_author_display_name` -- fallback til e-postens lokaldel).

    `post-engagement.tsx`: et alltid-synlig sammendrag under
    reaksjonsraden ("Kari Nordmann og 3 andre reagerte" --
    `formatReactorSummary()`, ALDRI kun synlig ved hover/tap alene,
    tilgjengelighetsregelen), utvidbart (44px trykkflate) til en full
    liste gruppert per emoji. Ingen ny henting -- dataen var allerede i
    `reactionRow`.

    **Scratch-verifisert:** isolert DB fra `teecup_db`-mal, egen API-
    container. Bekreftet for begge tabellpar (`round_message_reaction`:
    én reaksjon → riktig navn; `message_reaction`, org-scopet: to
    brukere reagerer med samme emoji → begge navn i riktig rekkefølge,
    org-spesifikt spillernavn brukt, ikke kontoens globale navn).
    `tsc --noEmit`/`py_compile` rent. Ekte nettleser: sammendragslinjen
    og den utvidbare per-emoji-listen begge bekreftet fungerende, ingen
    konsollfeil. Scratch ryddet opp fullstendig.

    Ren tillegg til eksisterende `ReactionSummary`-kontrakt (ingen
    migrasjon, ingen brytende endring) + `post-engagement.tsx`.

    **Rullet ut live 2026-08-07**, bruker bekreftet eksplisitt ("Ja
    takk"): `docker compose up -d --build teecup_api teecup_frontend`.
    Begge containere boot-et rent, `/health` → 200. `teeoff.no`
    upåvirket.

36. **"Ingen"/"Alle"-hurtigknapp for venne-kategori-velgeren i
    rundedeling — 2026-08-07.** Brukeren: "Der man, i et rundeoppsett,
    velger hvem man skal dele runden med, så bør det være en 'bryter'
    med 'Ingen (helt privat)' eller 'Alle'. Deretter velger eller
    fravelger man etter det som passer en best." Avklart eksplisitt med
    bruker (AskUserQuestion) hvilket av to funn passet: Privat/Venner/
    Offentlig-hovedvalget forblir uendret (3-veis, uendret semantikk);
    hurtigknappen legges TIL kategori-pillisten som vises når "Venner"
    er valgt.

    Fant samtidig et reelt, eksisterende avvik mellom de to stedene
    denne velgeren finnes: opprettelses-veiviseren (`new-round.tsx`)
    forhåndsvelger alle 10 kategoriene med opt-out-ordlyd, mens
    rediger-dialogen (`round-detail.tsx`) verken har noen hurtigknapp
    eller noe "ingen valgt"-varsel i det hele tatt (kun veiviseren hadde
    det varselet fra før). Begge fikk nå identisk `Pill`-/knapp-basert
    "Ingen"/"Alle" (setter kategorisettet til tomt/alle ti, finjusteres
    deretter med enkeltpiller som før) OG samme "ingen kategori valgt"-
    varsel -- lukker det avviket som en naturlig del av samme endring,
    ikke en egen runde.

    Ren frontend, ingen migrasjon, ingen ny backend-logikk (API-et tok
    allerede imot tomme/fulle `visible_categories`-lister uendret).
    `tsc --noEmit` rent. Scratch-verifisert i ekte nettleser for
    rediger-dialogen (isolert DB-mal, egen API-/frontend-/Caddy-
    container): "Ingen" tømmer alle ti pillene og viser varselet,
    "Alle" fyller alle ti, begge fremhever seg selv korrekt når
    tilstanden allerede matcher. Veiviserens versjon (identisk kode-
    mønster, samme delte `Pill`-komponent) bekreftet via kodegjennomgang
    i stedet for full klikk-gjennom (banesøk mot ekte teeoff-oppslag
    gjorde en full E2E-kjøring uforholdsmessig tidkrevende for en
    kodemessig identisk endring). Scratch ryddet opp fullstendig.

    **Rullet ut live 2026-08-07**, bruker bekreftet ("ja"): `docker
    compose up -d --build teecup_frontend`. Begge containere boot-et
    rent, `/health` → 200. `teeoff.no` upåvirket.

37. **Slag-for-slag GPS-avstandsmåling — backend + skjema, 2026-08-07, se
    ADR-048 for full begrunnelse/beslutningsdetalj.** Brukeren: "Måle
    lenge på slag ... Fra der man er ELLER fra et valgt punkt på et
    satellittfoto, til der man står ved siden av ballen. Man skal også
    kunne si hvilken kølle man slo med. Dette kan deles i feeden."
    Formell planleggingsrunde kjørt (Explore-agenter for
    `round_hole`-skjema/scoreførings-UI/`BAG_CLUBS`/geolocation-bruk,
    Plan-agent for konkret design), etterfulgt av AskUserQuestion for tre
    gjenstående valg: v1 dekker BÅDE individuelle runder OG lagformater
    fra start, delingstekst er auto-generert-men-redigerbar PLUSS et
    satellitt-utsnitt av slaget, og måling er tilgjengelig både i
    scoreførings-veiviseren og som en retroaktiv hull-kort-handling.

    Migrasjon `060_round_shot.sql`: ny tabell, kjenner kun
    `round_hole_id` (arver eierskap individuell/lagformat transitivt via
    `round_hole` sin eksisterende XOR, migrasjon 031 -- ingen egen
    XOR-logikk trengs). Eksplisitt, hull-tolerant `shot_number` (ikke
    `ORDER BY captured_at`). Kun `UPDATE (shared_round_message_id)`
    grantet -- ellers samme "slett og opprett på nytt"-prinsipp som
    `round_message`.

    Backend (`app/routers/rounds.py`): `ShotIn`/`ShotOut`/`ShotShareIn` +
    seks endepunkter -- deltaker-GET/POST og side-GET/POST (speiler
    `RoundHoleOut`/`RoundSideHoleOut` sitt eksisterende doble mønster for
    å dekke lagformater), eierskap-agnostisk DELETE (join via
    `round_hole` → `round_participant`/`round_side` for å bekrefte
    tilhørighet til riktig runde), og `POST .../shots/{id}/share` som
    genererer et satellitt-utsnitt SERVER-SIDE (Mapbox Static Images
    API, ny `TEECUP_MAPBOX_SECRET_TOKEN`-setting i `config.py`, egen
    polyline-encoder skrevet for topunkts-overlayet, URL-format
    verifisert mot Mapbox sin offisielle dokumentasjon via WebFetch
    fremfor antatt fra hukommelse) og laster opp via eksisterende
    `storage.upload_image()` inn i en vanlig `round_message`-rad.
    Rekkefølgen (slag opprettes ALLTID først, uten deling) unngår en
    foreldreløs feed-melding ved en feilet slag-opprettelse. Mapbox-
    kallet degraderer grasiøst til tekst-only ved manglende token/feil
    (samme mønster som SMTP/push). `_resolve_round_message_author_name`
    flyttet fra `round_messages.py` til `rounds.py` (unngår sirkulær
    import, `rounds.py` var allerede den importerte parten) slik at
    `share_shot` kunne gjenbruke navnefeltet uendret i stedet for en
    duplisert kopi.

    Frontend (kun det som IKKE avhenger av V0-eksporten ennå):
    `frontend/lib/geo.ts` (ren Haversine, ingen avhengigheter). Det
    eksisterende kølle-plukker-pill-mønsteret i `ScoringWizard`
    (`round-detail.tsx`) trukket ut til en delt `ClubPicker`-komponent
    (ren refaktor, `tsc --noEmit` rent før/etter). `mapbox-gl` lagt til
    `package.json` (versjon slått opp direkte mot npm-registeret, ikke
    gjettet) -- `@types/mapbox-gl` bevisst IKKE lagt til, `pnpm install`
    varslet at den er en utdatert stub siden `mapbox-gl` nå leverer egne
    typedefinisjoner. `pnpm-lock.yaml` regenerert (`pnpm install
    --lockfile-only`) -- oppdaget under scratch-frontend-bygg at den
    ELLERS ville vært ute av synk med `package.json`, noe som ville feilet
    `pnpm install --frozen-lockfile` i BÅDE scratch- og ekte Docker-bygg
    (fanget her, ikke først ved ekte utrulling). `NEXT_PUBLIC_MAPBOX_TOKEN`
    tredd gjennom `frontend/Dockerfile` (KUN builder-steget, siden bruken
    er ren klient-side -- speiler `TEECUP_API_ORIGIN`s build-tid-fallgruve
    som allerede var dokumentert der) og `docker-compose.yml`. To tomme
    plassholder-linjer lagt til i `.env` (`NEXT_PUBLIC_MAPBOX_TOKEN`,
    `TEECUP_MAPBOX_SECRET_TOKEN`) -- MÅ fylles inn med reelle Mapbox-
    kontoverdier før kart-/delings-bilde-flyten kan testes fullt ut eller
    rulles ut live.

    V0-prompt for selve `ShotMeasurementSheet` (alle steg + et bevisst
    MOCK satellitt-kart-plassholder, ikke et ekte kartbibliotek -- ekte
    Mapbox GL JS kobles på hånd-kodet etter eksport, for å unngå at V0
    genererer en kart-komponent som remonterer per interaksjon og bryter
    ADR-048 Beslutning B) skrevet og sendt til bruker. **Venter på
    V0-eksport** før inngangspunktene (veiviser-knapp + hull-kort-merkelapp)
    kan kobles til i `round-detail.tsx`.

    **Scratch-verifisert grundig, kun backend** (isolert `teecup_scratch7`-
    DB, alle 60 migrasjoner kjørt friskt, isolert `teecup_app_scratch7`-
    rolle, isolert scratch-MinIO, engangs API-container). **Ett reelt
    funn og umiddelbar retting UNDER selve scratch-oppsettet:**
    migrasjon 002 hardkoder BÅDE rollenavn OG databasenavn i sin
    `GRANT CONNECT ON DATABASE teecup_db`-linje -- rutinemessig
    sed-omdøping av kun rollenavnet (`teecup_app` → `teecup_app_scratch7`)
    før migrasjonene kjøres mot scratch-DB-en resulterte i at den nye
    scratch-rollen fikk CONNECT-rettighet på den EKTE `teecup_db`
    (kun en tilkoblings-rettighet, ingen tabell-/dataadgang fulgte med,
    men uansett et utilsiktet avtrykk på ekte database uten bekreftelse).
    Oppdaget og revokert umiddelbart (`REVOKE CONNECT ON DATABASE
    teecup_db FROM teecup_app_scratch7`), verifisert at `teecup_db` sin
    `datacl` var tilbake til nøyaktig samme tilstand som før. Ingen data i
    `teecup_db` ble noensinne lest/skrevet. Notert her i tråd med
    CLAUDE.md sin åpenhetsplikt -- vil unngå denne fallgruven ved å
    ekskludere/håndtere migrasjon 002 sin databasenavn-linje separat neste
    gang en fersk scratch-DB settes opp fra migrasjoner 001+.

    Testmatrise kjørt mot ekte HTTP (magic-link-innlogging, ekte
    sesjonscookie, `TEECUP_DEV_LOG_MAGIC_LINKS=true`): opprett slag
    gps/gps og map_tap/gps (deltaker-eid hull), opprett slag på side-eid
    hull (fourball-format), avstand >500m avvist (422), ugyldig lat
    avvist (422), slett midt i sekvensen + bekreftet gap (neste slag fikk
    nummer 3, ikke gjenbruk av 1), id-gjetting på tvers av runder avvist
    (404), ikke-medlem avvist (403), linket medspiller kan måle for hele
    flighten (bekreftet med en andre, faktisk innlogget testbruker),
    slett/liste av ikke-eksisterende ressurser gir 404, delings-endepunkt
    kjørt uten Mapbox-token satt (bekreftet grasiøs tekst-only-degradering,
    `shared_image_url: null`, meldingen dukket opp korrekt i
    `/rounds/{id}/messages` med riktig `author_display_name`-fallback).
    `test_isolation.sql` 12/12 uendret. `py_compile` rent. Scratch-miljøet
    (DB/rolle/API-/MinIO-container/image) ryddet opp fullstendig
    etterpå, bekreftet tomt.

    **Ikke testet i denne runden** (avhenger av ting bruker/V0 ennå ikke
    har levert): selve satellitt-bilde-genereringen (krever en ekte
    `TEECUP_MAPBOX_SECRET_TOKEN`), hele frontend-flyten (avhenger av
    V0-eksporten), ekte nettleserverifisering av "null Mapbox-kall på
    ren-GPS-veien"-kostnadskontroll-invarianten (ADR-048 Beslutning B) --
    gjøres når V0-eksporten er integrert.

38. **Fast bunn-navigasjon manglet på fem av hovedsidene — 2026-08-07.**
    Brukeren spurte, i forbindelse med dashbord-V0-forhåndsvisningen,
    "hva skjedde med den sticky menyen i bunnen?" på andre sider enn
    dashbordet. Undersøkt: `BottomTabBar` (eksportert fra
    `dashboard.tsx`, migrasjon 2026-08-01) var kun faktisk importert i
    `dashboard.tsx` selv og `more-menu.tsx` -- til tross for at BÅDE
    `dashboard.tsx` sin egen kodekommentar ("vist på ALLE skjermer som
    importerer denne") og CHANGELOG-oppføringen fra samme dag ("brukt av
    flere sider") beskrev en bredere utrulling enn det som faktisk ble
    koblet inn. Ingen ADR/CHANGELOG-oppføring dokumenterte en bevisst
    innsnevring -- vurdert som en ufullstendig utrulling, ikke en
    beslutning, og rettet direkte uten en egen avklaringsrunde (bekreftet
    med bruker: fiks nå).

    Lagt til i `own-rounds.tsx` (`active="rounds"` -- direkte fanemål),
    `account-settings.tsx` (`active="profile"` -- direkte fanemål, Profil-
    fanen peker allerede til `/account`), og `friends.tsx`/`feed.tsx`/
    `notifications.tsx` (`active="more"` -- ingen av de tre er et direkte
    fanemål, men alle tre er allerede listet som "Mer"-menyens egne
    undersider i `more-menu.tsx`, så dette matcher appens eksisterende
    IA i stedet for å oppfinne en ny). `<main>`-bunnpolstring justert til
    `pb-24` (samme verdi som dashbordet selv bruker for å ikke overlappe
    den faste raden) i de fire filene som ikke allerede hadde nok
    (`notifications.tsx` hadde fra før `pb-28`, urørt). Ren tillegging av
    en allerede ferdig, gjenbrukt komponent -- ingen ny logikk.

    **Reelt funn og rettet UNDER selve arbeidet, ikke i selve
    bunn-nav-fiksen:** `frontend/pnpm-lock.yaml` hadde vært ute av synk
    med `package.json` siden `mapbox-gl`/`@types/mapbox-gl` ble lagt til
    (ADR-048, punkt 37 over) -- oppdaget først her fordi dette var første
    gang siden den endringen at en full `pnpm install --frozen-lockfile`-
    Docker-bygg faktisk ble kjørt. Ville ha feilet ETHVERT fremtidig
    scratch- ELLER ekte frontend-bygg. Rettet: `@types/mapbox-gl` fjernet
    helt (pnpm varslet at den er en utdatert stub -- `mapbox-gl` leverer
    egne typer nå), `mapbox-gl` endret fra `^3.28.1` til eksakt `3.27.0`
    (3.28.x var under ett døgn gammel og feilet pnpms egen
    minimumReleaseAge-leverandørkjede-policy inni Docker-bygget -- en
    fornuftig sikkerhetssperre mot ferskpubliserte pakker, ikke en feil
    i verktøyet), lockfile
    regenerert (`pnpm clean --lockfile && pnpm install`).

    **Scratch-verifisert i ekte nettleser** (åttende scratch-miljø denne
    økten: isolert DB fra migrasjoner 001-060 kjørt friskt, isolert
    `teecup_app_scratch8`-rolle, isolert scratch-MinIO, scratch-API- og
    -frontend-container -- frontend bygget med riktig scratch-
    `TEECUP_API_ORIGIN`). Logget inn med ekte magic-link-flyt, fullførte
    profil, besøkte alle fem rettede sider (`/my-rounds`, `/my-friends`,
    `/account`, `/my-feed`, `/my-notifications`) pluss dashbordet --
    bunn-raden vises korrekt nederst uten å overlappe innhold på noen av
    dem, riktig fane fremhevet grønt på hver (Runder/Profil/Mer×3), ingen
    konsollfeil på noen av sidene. Scratch-miljøet ryddet opp fullstendig
    etterpå (denne runden traff samme fallgruve som punkt 37 med
    migrasjon 002s hardkodede `teecup_db`-referanse -- rettet i den
    kopierte migrasjonsfilen FØR kjøring denne gangen, ikke etterpå, og
    eksplisitt bekreftet at ekte `teecup_db` sin `datacl` var uendret
    etterpå).

    **Rullet ut 2026-08-08** sammen med punkt 40 og 41 (`docker compose
    build teecup_frontend` + `up -d` -- ingen migrasjon, kun ny
    frontend-image). Bekreftet ingen konsollfeil på
    `https://teecup.golf/` etter omstart.

39. **Dashbordet reskinnet til "clubhouse"-paletten — 2026-08-07, bruker
    bekreftet eksplisitt "full overtagelse, også bakgrunnen".** Egen,
    parallell V0-utforskning (ikke samme retning som "Forest Green" fra
    2026-08-01/02) — sendt som en eksplisitt EKSPLORERENDE prompt ("samme
    ånd som login-siden-utforskningen... ikke bedt om å matche eksisterende
    stil"), forhåndsvist for bruker (screenshots av en `npm install`+
    `next dev`-kjøring av selve V0-eksporten, mock-data) FØR noe ble
    integrert i den ekte, datakoblede `dashboard.tsx`.

    **Viktig funn før integrering:** paletten (`--tee`/`--tee-strong`/
    `--cup`/`--cup-strong`/`--clubhouse-*`) og `TeeCupWordmark`-komponenten
    fantes ALLEREDE i prosjektet — fra en tidligere, ennå ikke besluttet
    `/logg-inn`-utforskning (egen isolert sammenligningsside, additive
    CSS-tokens i `globals.css`, rørte ikke skya-tokens). V0 gjenbrukte dem
    konsekvent for dashbord-eksporten fordi de allerede var etablert i
    prosjektkonteksten, ikke fordi det gjenbrukte "gamle" farger — bekreftet
    ved faktisk pikselsampling av skjermdumpen (`#2f6b1e`/`#ff5427`, begge
    forskjellige fra "Forest Green" sin `#1f6b08`/ingen oransje i det hele
    tatt). `/logg-inn`s egen etablerte presedens ble fulgt: Bricolage-
    visningsfonten IKKE lagt til (samme utelatelse som der), kun paletten
    og wordmarken.

    **Omfangsgrenser, avklart eksplisitt med bruker underveis (ikke
    antatt):** `RoundCard`/`TournamentCard` (delt med `/my-rounds`) IKKE
    re-stylet — beholder sin eksisterende shadcn-token-baserte styling
    (viste seg uansett visuelt kompatibelt, siden begge alt bruker et
    grønt-for-primær/oransje-for-sekundær-mønster). `BottomTabBar` (delt
    med de fem sidene fra punkt 38) IKKE re-stylet direkte av meg — bruker
    ba eksplisitt om en egen V0-prompt for den ("lag et prompt til V0, og
    la den bestemme utseendet") fremfor at jeg skulle avgjøre det selv,
    siden komponenten må fungere rimelig godt mot BÅDE den nye og den gamle
    paletten avhengig av hvilken side den vises på. Egen prompt skrevet og
    sendt, venter på eksport.

    **Faktisk re-stylet:** header (erstattet flagg-i-sirkel med ekte
    `TeeCupWordmark`), hilsen, `InstallPrompt` (KUN dens egen selvstendige
    JSX-retur, mørkegrønn gradient endret til en tee-strong-forankret
    gradient — all ekte iOS/Android-deteksjons-/14-dagers-utsettelses-logikk
    uendret, samme "logikk uendret, kun styling"-disiplin som Forest Green-
    runden fulgte), `QuickAction` (fikk `primary`/`expanded`-varianter som
    matcher V0-eksportens mønster — "Ny runde" solid, de to andre kort-
    stil), delt seksjon-"chrome" (`SectionHeader`/`SeeAllLink`/`EmptyState`/
    `ShortcutButton`), "Venner på banen", `Sparkline`/`StatTile`/
    `StatsSection`, "Spilte baner", "Venner"-oppsummeringen, og
    organisasjon-foten. `AVATAR_HUES` (venne-avatar-fargene) beholdt
    uendret — allerede nøytrale nok til å fungere i begge paletter.

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (niende
    scratch-miljø denne økten, samme mønster som punkt 37/38 — inkludert
    samme forsiktighet med migrasjon 002s databasenavn-linje, bekreftet
    `teecup_db` uendret både før og etter): logget inn, fullført profil,
    besøkt dashbordet i både tom tilstand og med ekte seed-data (pågående
    runde, aktiv turnering som arrangør, statistikk, spilt bane) —
    korrekt rendret i begge tilstander, `RoundCard`/`TournamentCard` sitter
    visuelt fint på den nye bakgrunnen, "Ny turnering"-hurtighandlingen
    utvidet korrekt og det eksisterende skjemaet (uendret) fungerte som før.
    Ingen konsollfeil. Scratch-miljøet ryddet opp fullstendig.

    **Rullet ut 2026-08-08** sammen med BottomNav (punkt 40) og
    ny-runde-veiviseren (punkt 41), som planlagt -- se punkt 41 for
    utrullingsdetaljer.

40. **BottomTabBar erstattet med `BottomNav` fra egen V0-prompt —
    2026-08-08.** Bruker ba eksplisitt om at bunn-navigasjonens utseende
    skulle avgjøres av V0 selv, ikke av meg (se punkt 39) -- prompt skrevet
    med eksplisitt kontekst om at komponenten deles mellom clubhouse- og
    Forest Green-sider og må fungere rimelig godt mot begge, uten å få
    oppgitt en fasit-retning. V0 valgte en bevisst palett-nøytral løsning:
    en "flytende hvit flate" (halvtransparent hvit, backdrop-blur, egen
    skygge) som ikke arver noen sideb palett -- kun den grønne
    `tee`/`tee-strong`-aksenten (felles for begge paletter) brukes for
    aktiv-tilstand.

    Ny fil `components/teecup/bottom-nav.tsx`, eksporterer `BottomNav`
    (ikke lenger `BottomTabBar`). Strukturell endring fra forgjengeren:
    aktiv fane leses nå fra `usePathname()` internt (ikke en `active`-prop
    utenfra) -- hrefs rettet fra V0s plassholdere (`/hjem`, `/runder` osv.)
    til de faktiske rutene under integrering. Fjernet den gamle
    `BottomTabBar`-definisjonen (og dens nå-ubrukte `C`-fargekonstant og
    fire lucide-ikon-importer) fra `dashboard.tsx`; alle seks sider
    (`dashboard.tsx`, `own-rounds.tsx`, `friends.tsx`, `account-
    settings.tsx`, `feed.tsx`, `notifications.tsx`, `more-menu.tsx`)
    importerer nå `BottomNav` fra den nye filen i stedet, uten `active`-
    prop.

    **To reelle bugs funnet og rettet UNDER scratch-verifisering (ikke i
    selve V0-eksporten -- begge i MIN EGEN integreringskode):**
    1. Forsøkte først å "forbedre" aktiv-fane-sammenligningen ved å
       strippe bort et evt. `#hash` før sammenligning (siden "Turneringer"
       sin href er `/dashboard#kommende-turneringer`). Dette var FEIL --
       browser-verifisert til å få BÅDE "Hjem" og "Turneringer" til å vise
       som aktive samtidig på `/dashboard`, siden begge da strippet til
       samme sti. Reversert til V0s opprinnelige, rå strengsammenligning
       (som aldri hadde dette problemet -- "Turneringer" matcher rett og
       slett aldri via ren pathname, akkurat som forgjengeren uansett aldri
       fremhevet den).
    2. `/my-friends`, `/my-feed` og `/my-notifications` viste INGEN fane
       som aktiv i det hele tatt (ren pathname-matching kjenner dem ikke
       igjen som noen fanes mål) -- forgjengeren løste dette implisitt ved
       at hver side selv sendte inn riktig `active`-prop. Lagt til en ny,
       liten `matchPaths`-mekanisme på `Tab`-typen; "Mer" sin oppføring
       fikk `matchPaths: ["/my-friends", "/my-feed", "/my-notifications"]`
       -- speiler nøyaktig hvilke tre sider `more-menu.tsx` selv allerede
       lister som sine undersider, ingen ny gruppering oppfunnet.

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tiende
    scratch-miljø denne økten, samme migrasjon-002-forsiktighet som
    punkt 37-39, `teecup_db` bekreftet uendret): logget inn, besøkt alle
    syv sider som bruker `BottomNav` (dashbord, my-rounds, my-friends,
    account, my-feed, my-notifications, more) -- riktig (og ETT AV GANGEN,
    etter fiks 1) fane fremhevet på hver, ingen konsollfeil. Scratch-miljøet
    ryddet opp fullstendig.

    **Rullet ut 2026-08-08** sammen med punkt 39 og 41 -- se punkt 41 for
    utrullingsdetaljer.

41. **`/my-rounds/new` erstattet med ny-runde-veiviser fra egen V0-prompt
    — 2026-08-08.** Samme mønster som punkt 40: prompt skrevet med full
    teknisk spesifikasjon (alle 18 spilleformer, alle steg/felt/
    valideringsregler, alle 12 backend-endepunkt, `submitWizard()`-
    kontrakten) og eksplisitt UTEN visuell føring -- V0 fikk velge
    utseendet fritt. Resultat: en ny, selvstendig palett (`--nr-*` i
    `globals.css`, kald blå/grå, `--nr-accent: #1f5fd6`), additiv og
    scoped til veiviseren alene, samme "co-eksisterende paletter via
    Tailwind arbitrary-value-syntaks"-mønster som clubhouse-paletten
    (punkt 39) -- ingen `@theme inline`-kobling, ingen påvirkning på
    resten av appen.

    Gammel `components/new-round.tsx` (3364 linjer) SLETTET -- bekreftet
    via grep at kun `app/my-rounds/new/page.tsx` importerte den (de tre
    andre grep-treffene, i `course-template-editor.tsx`/
    `round-leaderboard.tsx`/`round-detail.tsx`, var bare beskrivende
    norske kommentarer, urørt). Ny modul under
    `components/ny-runde/` (`wizard-shell.tsx`, `wizard-context.tsx`,
    fem steg-filer, `primitives.tsx`, `progress.tsx`, `footer-bar.tsx`,
    `create-course-form.tsx`) + `lib/ny-runde/` (`types.ts`,
    `formats.ts`, `api.ts`).

    V0s eksport brukte mock-data gjennomgående -- hele portingsjobben
    var å koble hvert steg til de faktiske backend-kontraktene fra
    `app/routers/rounds.py` og den gamle `new-round.tsx`, IKKE bare et
    overflatisk bytte av komponentnavn: `Gender`-oversettelse
    (`"m"|"f"` API ↔ `"mann"|"kvinne"|"annet"` UI), `Tee`-formen
    forenklet til kun `{id, name, genders}` (aldri CR/Slope til klienten),
    `CourseMeta`-discriminated-union for teeoff- vs. egen-bane, ekte
    `POST /rounds` → `POST .../sides` → `POST .../participants` →
    `PATCH .../participants/{id}`-sekvens i `submit()`, ekte
    kontosøk/gjeste-oppslag i steg 3, ekte banesøk (teeoff-fasilitet +
    egne baner, inkl. geolokasjon for "i nærheten") i steg 1.

    **Fire reelle bugs funnet og rettet under scratch-verifisering:**
    1. `TextInput` i `primitives.tsx` manglet `forwardRef` (feil i selve
       V0-eksporten, ikke i portingen) -- ga 3 TS-feil der nedstrøms kode
       sendte `ref` til komponenten. Rettet ved å pakke inn med
       `forwardRef`, `tsc --noEmit` gikk fra 3 feil til rent.
    2. Steg 3 viste "Utslag: Ikke valgt" for eieren selv etter at tee var
       valgt i steg 1 -- min egen portingsfeil, jeg hadde ikke tatt med
       den ekte `chooseCourse()`s side-effekt som synker valgt tee inn i
       spillerlisten. Rettet ved å legge `players:
       state.players.map(...)` til i `pickCourse()`
       (`step1-course-time.tsx`). Verifisert rettet ved reload.
    3. Manglende "Ingen"/"Alle"-hurtigknapp og "ingen kategori
       valgt"-advarsel i steg 5s kategorivelger (samme mangel som ble
       oppdaget og rettet i selve appen 2026-08-06, se punkt 36 -- V0s
       eksport hadde ikke fått denne konteksten). Lagt til i
       `step5-sharing.tsx`, samme mønster som punkt 36 (`role="group"`,
       `aria-pressed`, `role="alert"` ved null valgt). Browser-verifisert
       i scratch: "Ingen" tømmer alle 10 avkrysninger og viser advarselen,
       "Alle" gjenoppretter alle.
    4. Fulgte først en blindvei: trodde "Legg til gjest"-knappen i steg 3
       var usynlig/klikk-slukende bak den klissede (`sticky bottom-0`)
       `FooterBar`-en (samme feilklasse som `pb-32`-saken 2026-08-02,
       nevnt i den gamle `new-round.tsx`s egne kommentarer) -- la til
       `pb-28` på `<main>`. Padding-fiksen var faktisk RIKTIG og
       nødvendig (uten den er det for lite scroll-klaring til å noensinne
       få knappen helt fri av footeren), men den første reproduksjonen av
       "feilen" var selv et testverktøy-artefakt: et koordinat-basert
       klikk uten forutgående scroll traff footeren fordi den, ved
       `scrollY: 0`, visuelt ligger over knappen (klissete element som
       ikke har "festet seg" ennå fordi normal dokumentflyt allerede
       plasserer det nederst i viewport). Bekreftet ved å måle
       `getBoundingClientRect()` for begge før/etter scroll: uten scroll
       overlapper de nesten fullstendig (y:776 vs. top:775); etter
       `scrollIntoView` (174px, alt tilgjengelig scroll) står knappen på
       y:602, godt klar. Et ekte, koordinat-basert klikk etter scroll
       fungerte perfekt. Konklusjon: `pb-28`-fiksen var korrekt og er
       beholdt (den gir nødvendig klaring når brukeren scroller helt ned),
       ingen ytterligere kodefeil forelå.

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (ellevte
    scratch-miljø denne økten, samme migrasjon-002-forsiktighet som
    punkt 37-40, `teecup_db`s ACL bekreftet uendret før og etter): full
    veiviser-gjennomkjøring for Fourball -- egen bane opprettet og valgt,
    tee/HCP riktig synket til eier, gjest uten konto lagt til, begge
    spillere tildelt hver sin side i steg 4, "Ingen"/"Alle"-toggle testet
    i steg 5, fullført innsending. **DB-verifisert direkte**: `round`-rad
    med riktig `play_format`/`visibility_mode`/bane-/tee-snapshot,
    to `round_side`-rader, begge `round_participant`-rader korrekt
    knyttet til hver sin `round_side_id` med riktig
    HCP-/tee-/stat_level-snapshot. Ingen konsollfeil. Scratch-miljøet
    (containere, images, DB, rolle) ryddet opp fullstendig etterpå.

    **Rullet ut 2026-08-08** sammen med punkt 39 og 40, etter eksplisitt
    brukerbekreftelse ("Rull ut alle tre samlet"): `docker compose build
    teecup_frontend && docker compose up -d teecup_frontend` (kun
    frontend-image, ingen migrasjon). Bekreftet `teecup_api`s image
    UENDRET (bygget 2026-08-07, ikke i dag) -- containeren ble kun
    re-opprettet fra samme eksisterende image pga. avhengighetskjeden i
    `docker-compose.yml`, ikke bygget på nytt, så de uncommittede
    backend-endringene som lå i arbeidstreet (`rounds.py`,
    `handicap_engine.py` m.fl., urelatert til denne økten) ble IKKE
    utilsiktet rullet ut. `https://teecup.golf/` bekreftet oppe, ingen
    konsollfeil på innloggingssiden. Autentisert gjennomgang av
    dashbord/BottomNav/ny-runde-veiviseren i produksjon overlatt til
    brukeren selv (krever ekte pålogging).

42. **`/logg-inn` gjort til den EKTE innloggingssiden (erstatter `/` sin
    gamle `LoginForm`) — 2026-08-08.** Brukeren la merke til at
    innloggingssiden fortsatt viste "det gamle grensesnittet" etter
    punkt 39-41s utrulling -- undersøkelse avdekket at `/logg-inn`
    (clubhouse-palett, `TeeCupAuth`) var en fullstendig FORELDRELØS V0-
    utforskning: INGENTING i appen lenket eller redirectet dit (bekreftet
    ved grep), alle ~11 uautentisert-steder pekte fortsatt til `/` (gammel
    `LoginForm`, Forest Green). Se dashboard.tsx sin egen kommentar
    (linje 26-34) som allerede erkjente dette -- "fra en tidligere, ennå
    ikke integrert /logg-inn-utforskning".

    **Kritisk oppdagelse FØR arbeidet startet:** `TeeCupAuth` var 100 %
    mock -- `apiSendMagicLink`/`apiPasswordLogin`/`apiJoinByCode` var alle
    `sleep()`-baserte stubber (hardkodet demo-passord `"teecup123"`,
    hardkodede demo-koder `TEECUP`/`RYDER-25`/`HOST2026`, en falsk
    "2FA-kode sendes"-tekst som ALDRI faktisk sendte noe). Dette ble
    eksplisitt flagget til brukeren (AskUserQuestion) FØR noe ble bygget,
    siden dette er sikkerhetskritisk kode og omfanget var langt større enn
    en ren redirect-ombytting -- brukeren bekreftet "gjør full port nå".

    **Full port utført:**
    - `components/teecup/teecup-auth.tsx` skrevet om fra bunnen: ekte
      `POST /auth/request-link` (magic link), ekte
      `POST /auth/login-password` (med `LoginResult`-status-håndtering
      identisk med den gamle `LoginForm`), ekte
      `GET /public/tournaments/by-code/{code}`. Alle demo-hint/mock-tekster
      fjernet. `TwoFactorVerifyForm`/`TwoFactorSetupForm`
      (`components/two-factor-flow.tsx`) gjenbrukt UENDRET -- samme
      "delt komponent ikke re-stylet"-mønster som `RoundCard`/
      `TournamentCard` i dashbord-reskinnet (punkt 39) -- lavest mulig
      risiko for sikkerhetskritisk 2FA-kode.
    - `app/logg-inn/page.tsx`: fikk samme server-side allerede-innlogget-
      sjekk (`cookies()` + `/auth/me`) som `/`-siden hadde.
    - `app/page.tsx` (root): redusert til en tynn videresending
      (autentisert → `/dashboard`/`/account`, uautentisert → `/logg-inn`)
      -- beholdt for gamle bokmerker/lenker til `teecup.golf/`. Gamle
      `LoginForm`/`components/login-form.tsx` SLETTET (bekreftet ingen
      andre importer via grep).
    - Alle 11 `router.replace("/")`-steder (401-håndtering + logout) i
      `dashboard.tsx`, `friends.tsx`, `more-menu.tsx`,
      `account-settings.tsx`, `notifications.tsx`, `course-rounds.tsx`,
      `own-rounds.tsx`, `rounds-stats-summary.tsx` byttet til
      `router.replace("/logg-inn")`. `verify-form.tsx` sin
      "Be om en ny lenke"-fallback-lenke (`href="/"`) byttet til
      `/logg-inn`.

    **Én reell bug funnet og rettet under scratch-verifisering:**
    `app/logg-inn/page.tsx` kalte `redirect()` INNI `try`-blokken som
    henter `/auth/me` -- Next.js sin `redirect()` fungerer ved å kaste en
    egen `NEXT_REDIRECT`-kontrollflyt-exception som MÅ boble videre
    urørt til Next.js sin render-maskineri. Den tomme `catch {}`-en rundt
    slukte denne stille, så en allerede innlogget bruker som besøkte
    `/logg-inn` fikk se innloggingsskjemaet på nytt i stedet for å bli
    sendt videre -- oppdaget fordi en ekte innlogget test-bruker IKKE ble
    omdirigert i scratch, mens en direkte nettleser-`fetch("/auth/me")`
    (som går via `next.config.mjs` sin rewrite, ikke gjennom denne
    server-komponentens egen kode) bekreftet sesjonen var helt gyldig --
    avslørte at feilen satt i AKKURAT denne serverkomponentens egen
    try/catch-struktur. Den opprinnelige `/`-sidens ekvivalente kode
    unngikk dette ved å KUN sette `authenticated`/`profileComplete`-
    variabler inni try/catch og kalle `redirect()` etterpå, UTENFOR
    blokken -- samme mønster gjeninnført her. `tsc --noEmit` rent etter
    fiks.

    **Scratch-verifisert i ekte nettleser** (tolvte scratch-miljø denne
    økten, samme migrasjon-002-forsiktighet som punkt 37-41 --
    `teecup_db`s ACL bekreftet uendret før og etter; `TEECUP_DEV_LOG_MAGIC_LINKS=true`
    brukt for å hente ekte magic-link-tokens fra API-loggen i stedet for å
    sende ekte e-post til testadresser): full runde -- magic-link-
    forespørsel + `/verify?token=`-innlogging (ny bruker auto-opprettet,
    korrekt sendt til `/account` pga. ufullstendig profil), passord-
    innlogging med feil passord (ekte feilmelding "E-post eller passord
    er feil." fra `/auth/login-password`), invitasjonskode med ugyldig
    kode (ekte feilmelding fra `/public/tournaments/by-code/`), logout
    (→ `/logg-inn`), uautentisert besøk til `/dashboard` (→ `/logg-inn`,
    ikke lenger `/`), `/` og `/logg-inn` besøkt allerede innlogget med
    ufullstendig profil (→ `/account`) og med fullført profil
    (→ `/dashboard`, satt via direkte `PATCH /auth/profile`-kall for å
    unngå å klikke gjennom fødselsdato-datepickeren manuelt). Ingen
    konsollfeil. **Ikke click-through-testet:** `2fa_required`-grenen
    (krever en konto med 2FA allerede aktivert) -- vurdert lav risiko
    siden `TwoFactorVerifyForm`/`TwoFactorSetupForm` er gjenbrukt helt
    uendret fra den allerede beviste `LoginForm`-implementasjonen, kun
    kablingen frem til dem (`handleLoginResult`) er ny kode, og den er
    identisk portert fra `LoginForm`. Scratch-miljøet (containere, images,
    DB, rolle) ryddet opp fullstendig etterpå, inkl. en disk-full-hendelse
    underveis (`docker builder prune` frigjorde 11,75 GB build-cache fra
    denne øktens mange scratch-bygg -- ingen kjørende containere eller
    volumer berørt).

    **Rullet ut 2026-08-08**, etter egen, eksplisitt brukerbekreftelse
    separat fra punkt 39-41s batch-bekreftelse (sikkerhetskritisk --
    autentisering). `docker compose build teecup_frontend && up -d` --
    denne gangen ble kun `teecup_frontend` gjenskapt (`teecup_api` forble
    "Running" med bekreftet uendret image-tidsstempel før/etter, ulikt
    forrige utrulling i punkt 39-41 der en avhengighets-kjede-bivirkning
    gjenskapte -- men ikke bygde om -- `teecup_api`). `https://teecup.golf/`
    bekreftet å redirecte til `/logg-inn` med clubhouse-designet, ingen
    konsollfeil.

43. **PRODUKSJONSBUG: "Laster baner…" hang for alltid ved offisiell
    banevalg i ny-runde-veiviseren — funnet og fikset 2026-08-08.**
    Brukeren rapporterte (skjermbilde) at steget etter å ha valgt et
    teeoff-anlegg ("Baner") aldri kom videre fra "Laster baner…". Dette
    hadde vært live siden punkt 41s utrulling -- treffer ALLE nye runder
    som starter fra en offisiell (teeoff-) bane, ikke egne baner.

    **Rotårsak:** `fetchFacilityCourses()` i `lib/ny-runde/api.ts` antok
    at `GET /rounds/official-search/{slug}` returnerer banene direkte som
    en array (`data.map(...)`), men det ekte endepunktet returnerer et
    OBJEKT -- `OfficialFacilityDetail = {slug, name, courses: [...]}`
    (`app/routers/rounds.py` linje 530). `data.map` er ikke en funksjon
    på et objekt, så kallet kastet en `TypeError` -- og siden
    `OfficialCourses` sin `useEffect` i `step1-course-time.tsx` kun hadde
    `.then(...)` uten `.catch(...)`, ble denne exceptionen en stille,
    ufanget promise-rejection: `setCourses` ble aldri kalt, og
    `courses === null`-grenen ("Laster baner…") viste seg for alltid.
    Bug i min egen porting (punkt 41) -- **denne konkrete stien
    (offisiell teeoff-bane) ble aldri scratch-testet den runden**, kun
    "egen bane"-veien ble klikket gjennom (se punkt 41s test-notat).

    **To rettelser:**
    1. `fetchFacilityCourses()`: leser nå `data.courses.map(...)` i
       stedet for `data.map(...)`.
    2. `OfficialCourses` (`step1-course-time.tsx`): la til en reell
       `.catch()` + en `loadError`-tilstand (`role="alert"`, samme
       `--nr-danger`-stil som resten av veiviseren) -- uten denne ville
       ENHVER fremtidig feil her (f.eks. teeoff nede, 502) gitt nøyaktig
       samme uendelige "Laster baner…"-hang på nytt, uansett om
       datakontrakt-bugen over er rettet. Dette er defensivt, ikke bare
       en fiks for det spesifikke tilfellet.

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (trettende
    scratch-miljø denne økten, samme migrasjon-002-forsiktighet, `teecup_db`
    bekreftet uendret) MOT EKTE teeoff-data (samme `teeoff_db`/`teeoff_api`
    som produksjon deler, nådd direkte på `teeoff_default`-nettverket):
    søkte opp "Hvaler Golfklubb" (reell fasilitet), banen ("Hovedbanen, 2
    utslag") lastet korrekt i stedet for å henge, gikk videre til
    felt-steget og bekreftet ekte utslag (Gul/Rød) hentet riktig fra
    teeoff. Ingen konsollfeil (kun én forhåndseksisterende, ikke-relatert
    "Deprecated feature"-info-melding fra nettleseren selv). Scratch-miljøet
    ryddet opp fullstendig.

    **Rullet ut umiddelbart** (produksjonsbug som traff alle live
    brukere som prøvde å starte en runde på en offisiell bane) --
    `docker compose build teecup_frontend && up -d`, kun frontend-image,
    `teecup_api`s image-tidsstempel bekreftet uendret før/etter. Kunne
    ikke click-through-verifisere selve produksjonssiden med ekte
    brukerkonto (krever brukerens egen pålogging) -- basert på grundig
    scratch-verifisering mot ekte teeoff-data rett før utrulling.

44. **PRODUKSJONSBUG: rundedeling med venner i flere kategorier virket
    ikke som forventet — funnet og fikset 2026-08-08, se ADR-036
    Beslutning E.** Brukeren rapporterte at en runde delt med kategorien
    "Make" ikke ble synlig for vedkommendes ektefelle, til tross for at
    "Make" var huket av. Diagnostisert direkte mot ekte `teecup_db`
    (skrivebeskyttede spørringer): eieren (Erol) hadde kategorisert
    ektefellen (Gina) under TRE kategorier (`close_family`,
    `extended_family`, `spouse`), mens runden kun delte
    (`close_family`, `golf_friends`, `spouse`) -- `extended_family`
    manglet. `_can_view_round` krevde den gang at ALLE en venns
    kategorier måtte være i rundens synlige sett (presisert av bruker
    2026-07-29, kun dokumentert i kode-kommentarer, ALDRI i
    ARCHITECTURE_DECISIONS.md -- selve dokumentasjonshullet som gjorde
    dette vanskelig å spore tilbake), ikke bare én relevant kategori.

    Brukeren fikk vist mekanismen konkret (hvilke kategorier Gina var
    tagget i, hvilke runden delte, den strenge AND-regelen) og valgte via
    et eksplisitt spørsmål å reversere til "minst én kategori er nok"
    (som var den OPPRINNELIGE ADR-036 Beslutning B-regelen fra
    2026-07-25 -- 2026-07-29-presiseringen hadde altså strammet inn en
    regel utover det som noensinne ble ordentlig dokumentert som en
    bevisst arkitekturbeslutning).

    **Rettet i alle fire duplikate SQL-steder** (samme
    kopier-inn-i-SQL-mønster som allerede omtalt i ADR-036 Beslutning B):
    `app/routers/rounds.py` sin `_can_view_round` (selve
    tilgangssjekken), `_friends_who_can_see_round` (varsel-fan-out ved
    rundeopprettelse/fullføring), `list_friends_on_course`
    (dashbordets "Venner på banen"); `app/routers/round_messages.py` sin
    `/feed`-listing. Alle gikk fra "`NOT EXISTS` en kategori UTENFOR
    synlig sett" til et enklere "`EXISTS` en kategori INNENFOR synlig
    sett" -- enklere spørringer, ikke bare annen semantikk.

    **Scratch-verifisert i ekte API-kall** (fjortende scratch-miljø denne
    økten, samme migrasjon-002-forsiktighet, `teecup_db` bekreftet
    uendret): gjenskapte den ekte Erol/Gina-situasjonen (venn kategorisert
    i to kategorier, runde deler kun én av dem) -- vennen fikk nå 200 på
    `GET /public/rounds/{id}` (tidligere 403). Bekreftet at motsatt
    tilfelle (venn med INGEN overlappende kategori) fortsatt korrekt gir
    403 -- ingen utilsiktet åpning av tilgangskontrollen. `/friends/
    on-course` og `/feed` (de to andre endrede spørringene) begge 200
    uten SQL-feil. Scratch-miljøet ryddet opp fullstendig.

    **Rullet ut umiddelbart** (produksjonsbug, brukerens egen delte runde
    var konkret berørt) -- `docker compose build teecup_api && up -d`,
    KUN backend-image (ingen migrasjon, `visibility_mode`/
    `round_visible_category` er uendret skjema), `teecup_frontend`s
    image-tidsstempel bekreftet uendret før/etter. **Bekreftet direkte
    mot ekte data etter utrulling**: samme spørring som
    `_can_view_round` nå bruker, kjørt skrivebeskyttet mot den ekte
    Erol/Gina/runde-situasjonen, returnerer nå `true`.

45. **Slag-for-slag GPS-avstandsmåling — inngangspunkt 1 av 2, i
    scoring-veiviseren — 2026-08-08 (ADR-048).** Backend (migrasjon
    `060_round_shot.sql`, `ShotIn`/`ShotOut`/`ShotShareIn`-endepunktene i
    `rounds.py`, `lib/geo.ts` Haversine, Mapbox-token-plumbing) var
    allerede bygget og scratch-verifisert fra tidligere i økten (se
    ADR-048 i ARCHITECTURE_DECISIONS.md). Denne runden: selve
    frontend-flaten.

    V0-prompt skrevet med `DESIGN_SYSTEM.md`s tokens som en HARD
    begrensning (i motsetning til dashbord-/veiviser-/login-promptene
    tidligere i økten, som bevisst fikk null designføring) -- dette
    arket lever INNI den eksisterende Forest Green-skjermen
    (`round-detail.tsx`), ikke som en ny frittstående side. Eksporten
    (zip 4) var meget tro mot spesifikasjonen: `next/dynamic({ssr:false})`
    for kartsteget, ingen `mapbox-gl`-import i det hele tatt på
    GPS-only-stien, kartet mountes kun én gang per arkåpning
    (tom-deps `useEffect`, klikk/drag re-initialiserer aldri).

    `components/shot/shot-measurement-sheet.tsx` +
    `components/shot/map-point-picker.tsx` kopiert inn uendret (kun
    én import-sti rettet: V0s egen plassholder-`ClubPicker` byttet til
    den nå faktisk utrukne, delte `components/teecup/club-picker.tsx`
    -- ren utrekking fra `round-detail.tsx`s tidligere lokale kopi,
    ingen atferdsendring for veiviserens eksisterende bruk). Ny
    `components/ui/textarea.tsx`-shadcn-primitiv lagt til (fantes ikke
    fra før).

    Koblet inn som `ScoringWizard`s nye `roundId`-prop + lokal
    `shotSheetOpen`/`shotCount`-state: "Mål et slag"-knapp rett etter
    kølle-plukkeren i detalj-steget, henter eksisterende slag-antall on
    mount (`GET .../holes/{n}/shots`), sender til ekte
    `POST .../holes/{n}/shots` ved innsending og (hvis deling valgt)
    en påfølgende `POST /rounds/{id}/shots/{shot_id}/share`.

    **Scratch-verifisert** (femtende scratch-miljø denne økten, samme
    migrasjon-002-forsiktighet, `teecup_db` bekreftet uendret): full
    klikk-gjennomgang fra "Mål et slag" til arket åpner riktig, korrekt
    feilmelding+"Prøv igjen" når GPS-tillatelse mangler (bekreftet ekte
    -- CDP-automatiserte nettlesersesjoner har `geolocation`-tillatelse
    permanent `denied` uten noen dialog å akseptere, en verktøy-
    begrensning i selve test-miljøet, ikke i appen). Siden selve
    GPS-suksess-stien derfor ikke lot seg klikke gjennom i denne
    økten, ble de eksakte kallene `submitShot()` sender (opprett slag,
    list slag, del) i stedet verifisert direkte mot API-et med samme
    data-kontrakt -- alle tre 200/201, ingen feil i API-loggen. Bekreftet
    i ekte nettleser at slag-tellingen faktisk oppdateres og vises i
    knappeteksten ("Mål et slag (1 målt)") etter et slag er opprettet.
    Scratch-miljøet ryddet opp fullstendig.

    **Rullet ut 2026-08-08** -- se punkt 48 for migrasjons-/
    utrullingsdetalj (samlet med inngangspunkt 2 og gjest-tee-fiksen).

46. **Slag-for-slag GPS-avstandsmåling — inngangspunkt 2 av 2, alltid-
    synlig merkelapp på hull-kortet — 2026-08-08 (ADR-048).** Fullfører
    v1-kravet om å dekke BEGGE hull-eiertyper fra start (punkt 45 dekket
    kun deltaker-eide hull via `ScoringWizard`).

    Refaktorerte punkt 45s inline logikk (state + fetch + submit, som satt
    direkte i `ScoringWizard`) ut til en ny, delt lokal funksjon
    `ShotMeasurementEntry` i `round-detail.tsx` -- samme
    "lokal gjenbruk innad i filen, ikke en egen delt-fil"-mønster som
    resten av filens komponenter (`NumberPicker`/`Stepper`/`ChoiceRow`
    m.fl., se `DESIGN_SYSTEM.md`s "Komponentmønstre"-seksjon), siden alle
    tre bruksstedene nå ligger i samme fil. Komponenten bygger riktig
    URL-base (`.../participants/{id}/holes/{n}/shots` vs.
    `.../sides/{id}/holes/{n}/shots`) fra en enkel `owner: {kind, id}`-
    prop -- ingen egen gren utover selve URL-en trengs, ADR-048s speilede
    endepunkt-par dekker begge hull-eiertyper identisk.

    Koblet inn tre steder:
    - `ScoringWizard`s detalj-steg (uendret fra punkt 45, nå bare kalt
      via den delte komponenten i stedet for egen kopi).
    - `PlayerHoleCards` (deltaker-eide hull, hoved-"Score"-fanens
      spillerkort): en liten merkelapp-rad lagt til som SØSKEN av kortets
      store klikkbare knapp (ikke nøstet inni -- ugyldig HTML/ARIA å neste
      `<button>` i `<button>`), synlig uavhengig av om veiviseren er åpen,
      for retroaktiv måling.
    - `SideScorecardGrid` (side-eide hull, delt-ball-formater): siden selve
      scorekort-gridet er en tett 52px-per-hull-tabell uten plass til en
      egen knapp per rute, ble to merkelapper (én per side, med lag-
      etikett, f.eks. "Rødt lag: 2 slag målt") lagt til som en egen rad
      rett under gridet, ved siden av "Forrige/Neste hull"-knappene.

    **Scratch-verifisert i ekte nettleser** (sekstende scratch-miljø
    denne økten, samme migrasjon-002-forsiktighet, `teecup_db` bekreftet
    uendret) -- **med et ekte Mapbox-token satt inn i `.env` av brukeren
    midt i denne runden** (`NEXT_PUBLIC_MAPBOX_TOKEN`, offentlig/URL-
    restriktert; `TEECUP_MAPBOX_SECRET_TOKEN` for delings-satellittbilder
    fortsatt ikke mottatt): opprettet én vanlig slagspill-runde (deltaker-
    eid) og én foursome-runde med to sider (side-eid, via egen-opprettede
    sider + en gjest lagt til side B) direkte mot API-et. Bekreftet
    merkelappen vises korrekt og uavhengig av kortknappen i
    `PlayerHoleCards`, åpner arket direkte (ikke via veiviseren). Testet
    "Velg punkt på kart"-veien med det ekte tokenet -- Mapbox avviste med
    403 (URL-restriksjonen tillater kun det ekte domenet, ikke dette
    scratch-miljøets `localhost`-opprinnelse), som bekreftet at
    feilhåndteringen i `MapPointPicker` fungerer rent (ren "Kunne ikke
    laste kartet"-melding, ingen krasj) -- selve kart-SUKSESS-stien kunne
    ikke click-through-testes i dette miljøet av samme grunn. For
    side-eide hull: bekreftet begge merkelapper ("Blått lag"/"Rødt lag")
    henter riktig antall fra sine respektive `.../sides/{id}/holes/{n}/
    shots`-endepunkt (bekreftet i API-loggen), og at et slag opprettet på
    én side kun oppdaterer DEN sidens merkelapp, ikke den andres. Samme
    GPS-tillatelse-miljøbegrensning som punkt 45 gjaldt fortsatt for
    GPS-suksess-stien; de eksakte `submitShot`-kallene ble derfor igjen
    verifisert direkte mot API-et for begge eiertyper. Scratch-miljøet
    ryddet opp fullstendig.

    **Rullet ut 2026-08-08** -- se punkt 48.

47. **Ny-runde-veiviser: utslag lagt til direkte i gjesteskjemaet —
    2026-08-08, brukertilbakemelding.** Brukeren rapporterte at
    utslag-valget for en nyopprettet gjest ("midlertidig spiller") ikke
    var intuitivt -- måtte inn i det ferdigopprettede gjestekortet
    ("Rediger") for i det hele tatt å SE hvilket utslag som var valgt.

    Viste seg at `addGuest()` allerede valgte utslag automatisk
    (`compatibleTees(course, gender)[0]?.id`), men helt stille -- selve
    gjesteskjemaet (`GuestForm` i `step3-players.tsx`) hadde aldri et
    synlig Utslag-felt, kun Fornavn/Etternavn/E-post/Kjønn/HCP/
    Statistikk. Lagt til et kontrollert `teeId`-felt direkte i skjemaet,
    samme `NativeSelect`-mønster som `PlayerCard`s tilsvarende felt,
    med samme kjønn→utslag-resynk-logikk (`stillOk`-sjekk) som
    `PlayerCard` allerede hadde -- inkludert i "Fyll inn automatisk"-
    knappen for kjente gjester (samme feilklasse, ikke tidligere
    rettet der).

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser**
    (syttende scratch-miljø denne økten, samme migrasjon-002-
    forsiktighet, `teecup_db` bekreftet uendret): opprettet en egen bane
    med kjønns-eksklusive utslag (Gul kun menn, Rød kun kvinner) for å
    bevise resynk-logikken fungerer -- byttet kjønn til Kvinne i
    gjesteskjemaet, utslag-feltet oppdaterte seg umiddelbart til "Rød",
    la til gjesten, og det ferdige spillerkortet viste "Utslag: Rød"
    med det samme, uten å måtte åpne "Rediger". Ingen konsollfeil.
    Scratch-miljøet ryddet opp fullstendig.

    **Rullet ut 2026-08-08** -- se punkt 48.

48. **Slag-for-slag GPS-avstandsmåling rullet ut mot ekte `teecup_db`/
    `teecup_api`/`teecup_frontend` — 2026-08-08. Samler utrullingen for
    punkt 45-47.** Bruker satte inn begge Mapbox-tokens midt i økten:
    `NEXT_PUBLIC_MAPBOX_TOKEN` (offentlig, URL-restriktert til
    `teecup.golf` -- bekreftet BEVISST, ikke en feil: avviste med 403 fra
    et scratch-miljøs `localhost`-opprinnelse under punkt 46s
    verifisering, nøyaktig som en URL-restriksjon skal virke) og
    `TEECUP_MAPBOX_SECRET_TOKEN` (server-side, ingen URL-restriksjon
    siden Static Images-kallet er server-til-server og aldri sender en
    nettleser-`Referer`; kun offentlige `styles:tiles`/`styles:read`-
    scopes, ingen skrive-/bruker-/tokens-tilganger -- backend-koden
    trenger ikke mer).

    **Attende scratch-miljø denne økten** (kun API + MinIO, ingen
    frontend nødvendig -- rent backend-kall): bekreftet
    `TEECUP_MAPBOX_SECRET_TOKEN` faktisk fungerer ende-til-ende --
    opprettet en runde+slag, kalte `share_shot`, fikk et REELT,
    ikke-null `shared_image_url` tilbake (176 KB AVIF-fil i MinIO, ikke
    en tom/feilet fil). Scratch-miljøet ryddet opp fullstendig.

    **Migrasjon + utrulling mot ekte systemer, etter eksplisitt
    brukerbekreftelse ("ja"):**
    1. `060_round_shot.sql` kjørt mot ekte `teecup_db` -- kun ny tabell +
       to `GRANT`-er (`SELECT/INSERT/DELETE` full, `UPDATE` begrenset til
       `shared_round_message_id`), ingen endring i eksisterende skjema.
       `\d round_shot` bekreftet alle CHECK-constraints/FK-er riktige.
       `test_isolation.sql` kjørt på nytt mot ekte `teecup_db` rett
       etter -- fortsatt 12/12 grønt.
    2. `docker compose build teecup_api teecup_frontend && up -d
       teecup_api teecup_frontend` -- begge bygget rent, ren oppstart i
       loggene, ingen feil.
    3. `https://teecup.golf/` bekreftet oppe, ingen konsollfeil.
       Autentisert gjennomgang ("Mål et slag" i ekte nettleser) overlatt
       til brukeren selv (krever brukerens egen pålogging, samme
       begrensning som tidligere utrullinger denne økten).

    Med dette er ADR-048 (slag-for-slag GPS-avstandsmåling) fullt bygget,
    verifisert og live -- begge inngangspunkt, begge hull-eiertyper,
    begge Mapbox-token-veier (kart-valg + delings-satellittbilde).

49. **PRODUKSJONSBUG: slagmåling ga alltid 0 meter, delinger forsvant
    stille — 2026-08-08, brukerrapport rett etter punkt 48s utrulling.**
    Brukeren: "den målte aldri mer enn 0 meter. Jeg så ingen bilder."

    **Rotårsak 1 (selve 0-meter-buggen):** `shot-measurement-sheet.tsx`
    sitt "end"-steg (ballens posisjon) avfyrte GPS-målingen AUTOMATISK i
    et `useEffect` idet steget ble aktivt -- rett etter at startpunktet
    var målt, med null tid for brukeren til faktisk å gå fra utslagsstedet
    til ballen. Start- og sluttpunkt endte dermed på praktisk talt samme
    sted, samme øyeblikk -- avstanden ble alltid ~0m, uavhengig av hvor
    langt slaget faktisk var. Rettet ved å fjerne auto-avfyringen og
    kreve et eksplisitt "Jeg er ved ballen nå"-trykk (samme mønster som
    "Prøv igjen" ved feil, nå gjenbrukt for begge) -- brukeren går fysisk
    til ballen FØR målingen skjer, i stedet for at appen antar de allerede
    er der.

    **Rotårsak 2 (stille tap, "ingen bilder"):** en konsekvens av
    rotårsak 1 -- en 0m-avstand ble avvist av backendens
    `distance_meters > 0`-validering (422), men `submitShot()` i
    `round-detail.tsx` sin `ShotMeasurementEntry` gjorde da bare
    `setOpen(false)` og returnerte -- INGEN feilmelding, arket lukket seg
    stille som om alt var i orden. Brukeren fikk aldri vite at slaget
    (og dermed en eventuell deling) aldri ble lagret. Rettet: `submitShot`
    setter nå en `submitError`-state (parser backendens feilrespons,
    som kan være enten appens vanlige `{detail:{message}}`-form ELLER
    FastAPI/Pydantic sin rå valideringsform `{detail:[{msg,...}]}` --
    bekreftet eksakt hvilken av de to ved å faktisk trigge en 422 mot
    scratch-API-et: det er listeformen), viser feilen i arket
    (`role="alert"`, ny `submitError`/`submitting`-prop på
    `ShotMeasurementSheetProps`), og holder arket ÅPENT ved feil i stedet
    for å anta suksess og lukke.

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (nittende
    scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
    `teecup_db` bekreftet uendret): CDP-automatiserte nettlesersesjoner
    har `geolocation`-tillatelse permanent `denied` i dette miljøet (samme
    begrensning som punkt 45-46), så `navigator.geolocation.
    getCurrentPosition` ble midlertidig overstyrt via `evaluate_script`
    for å drive den EKTE React-komponentens fulle flyt (ikke bare et
    API-nivå-kall) -- bekreftet "Jeg er ved ballen nå"-knappen faktisk
    vises i stedet for å auto-måle, at et ekte gap mellom start-/
    sluttkoordinat ga "MÅLT LENGDE: 130 m" (ikke 0), at delingen faktisk
    postet riktig tekst til `/rounds/{id}/messages`, OG (motsatt test,
    identisk start-/sluttkoordinat) at en avvist 0m-innsending nå viser
    feilmeldingen i arket i stedet for å lukke seg stille. Scratch-miljøet
    ryddet opp fullstendig.

50. **Slag-for-slag GPS-avstandsmåling: liste over egne slag + fant en
    TREDJE stille-feil-bug — 2026-08-08, brukeroppfølging.** Brukeren
    prøvde på nytt etter punkt 49s fiks (bekreftet: målte nå 13 meter,
    ikke 0), men spurte "jeg kan ikke se det delte slaget i ettertid?".

    **Diagnose (skrivebeskyttede spørringer mot ekte `teecup_db` + ekte
    API-logger):** slaget lå riktig i `round_shot` (13m, ikke 0 -- punkt
    49s fiks virket), men `shared_round_message_id` var tom, og
    `round_message`-tabellen hadde null rader for runden. API-loggene
    viste at `POST .../shots` ga 201 Created, men det fantes INGEN
    etterfølgende kall til del-endepunktet i det hele tatt. Konklusjon:
    "Del i feeden"-valget var ikke aktivt idet brukeren trykket "Lagre
    slag" (arket starter forfra, inkl. tilbake til "Behold privat"
    -standardvalget, hver gang det åpnes på nytt -- forklart til
    brukeren).

    Brukerens motspørsmål var det egentlig viktige: **et privat, ikke-delt
    slag burde uansett kunne SES i ettertid** -- merkelappen viste
    tidligere kun et tall ("N slag målt"), aldri kølle/avstand for de
    faktiske slagene. Dette var en reell mangel i inngangspunkt 2 (punkt
    46), ikke bare en brukerforvirring.

    **Bygget:** `ShotMeasurementEntry` lagrer nå hele slag-listen (ikke
    bare `.length`), med en ny utvidbar `ShotList` (kølle + avstand per
    slag, "Delt"-merke eller en "Del"-lenke for et ikke-delt slag, en
    slette-knapp per slag via det allerede eksisterende
    `DELETE /rounds/{id}/shots/{shot_id}`-endepunktet). Splittet den
    tidligere ene knappen i to: en utvidbar "N slag målt"-disclosure
    (kun synlig når `count > 0`) og en separat, alltid synlig "+"-knapp
    for å måle et NYTT slag -- unngår at å åpne listen og å starte en ny
    måling er samme handling.

    **Tredje stille-feil-bug funnet og rettet i samme runde:** akkurat
    som punkt 49s rotårsak 2, sjekket heller ikke re-del-fra-liste-kallet
    (`shareExisting`) svaret sitt. Rettet parallelt med bygningen av
    listen, IKKE etter en ny brukerrapport denne gangen. En mislykket
    deling (enten ved førstegangs innsending eller re-del fra listen)
    vises nå som en egen, kortvarig feiltekst ved siden av merkelappen
    (`shareError`-state) -- BEVISST atskilt fra `submitError` (som
    fortsatt holder selve MÅLE-arket åpent ved en avvist innsending):
    siden selve slaget alerede er lagret på tidspunktet en delingsfeil
    kan oppstå, ville gjenbruk av `submitError` (som holder arket åpent)
    risikert at brukeren trykker "Lagre slag" på nytt og oppretter et
    duplikat-slag.

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tjuende
    scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
    `teecup_db` bekreftet uendret): opprettet to slag direkte mot API-et
    (Driver 215,5m, 7-jern 145m), bekreftet merkelappen viser "2 slag
    målt" som en egen utvidbar knapp ved siden av en separat "+"-knapp,
    utvidet listen og så begge slag med riktig kølle/avstand, klikket
    "Del" på Driver-slaget og bekreftet det ble til "Delt" OG at meldingen
    faktisk postet riktig tekst til `/rounds/{id}/messages`, slettet
    7-jern-slaget og bekreftet det forsvant fra listen ("1 slag målt"
    etterpå). Scratch-miljøet ryddet opp fullstendig.

51. **PRODUKSJONSBUG: kart-steget viste "Kunne ikke laste kartet" for
    alle -- CSS-arv-krasj med mapbox-gl.css — 2026-08-08, brukeroppfølging
    ("jeg ser ikke noe kart eller satellittfoto").** Bruker bekreftet
    "alle tre" da spurt hvor de forventet kart/foto: (1) kart-valg under
    måling, (2) forhåndsvisning i resultatsteget, (3) bilde i den delte
    meldingen.

    **(1) Rotårsak:** `mapbox-gl.css` (selve biblioteket sin stilark,
    importert i `map-point-picker.tsx`) definerer `.mapboxgl-map {
    position: relative }`. Mapbox GL JS legger `mapboxgl-map`-klassen til
    PÅ kart-beholderen sin egen `<div>` ved initialisering -- denne
    klassen kom SENERE i CSS-cascaden enn Tailwind sin `absolute`-klasse
    (samme element), og vant dermed kappløpet, og overstyrte `position`
    fra `absolute` til `relative`. Så snart `position` ikke lenger var
    `absolute`, mistet Tailwind sin `inset-0`-klasse all effekt på
    STØRRELSEN (den styrer kun posisjon for absolutt/fixed-plasserte
    elementer) -- beholderen kollapset til `height: 0`, og selve
    Mapbox-kartet (som TEKNISK sett lastet helt fint -- style/tiles/
    events ga alle 200/204) ble usynlig, klippet vekk av en 0px-høy
    forelder. Bekreftet direkte via `getBoundingClientRect()`-kjeden opp
    DOM-treet: `containerRect.height: 0` til tross for at Mapbox sitt
    eget `<canvas>`-element hadde en normal størrelse. Rettet ved å bytte
    `absolute inset-0` til `h-full w-full` på beholder-diven -- løser
    størrelsen via prosent-arv, upåvirket av hvilken `position`-verdi som
    vinner cascade-kappløpet.

    Denne konkrete feilen kunne IKKE vært fanget opp i noen av de
    tidligere scratch-testene denne økten (femtende, sekstende), siden
    Mapbox sitt eget offentlige token alltid ble avvist av URL-
    restriksjonen mot `localhost`-scratch-opprinnelser der -- kartet
    kom aldri langt nok til å faktisk RENDRE for at CSS-krasjen skulle
    bli synlig. Verifisert denne runden ved å midlertidig overstyre
    nettleserens `Referer`-header til `https://teecup.golf/` (CDP-nivå,
    forbi selve nettleserens fetch()-header-restriksjon) for å simulere
    det ekte, godkjente domenet i scratch -- avdekket samtidig at
    scratch-testen selv hadde en snubletråd: en `grep`-basert token-
    utpakking (`grep NEXT_PUBLIC_MAPBOX_TOKEN .env`, uten `^`-anker)
    matchet FEILAKTIG også `.env` sin forklarende kommentarlinje over
    selve variabelen (som også inneholder teksten "NEXT_PUBLIC_MAPBOX_
    TOKEN"), og satte sammen kommentarteksten med selve tokenet til en
    ugyldig verdi -- bekreftet at DENNE spesifikke feilen kun rammet
    scratch-testverktøyet mitt, ikke selve produksjonsutrullingen
    (`docker-compose.yml` sin `${NEXT_PUBLIC_MAPBOX_TOKEN}`-variabel-
    substitusjon er upåvirket, bekreftet ved å grepe direkte i den ekte,
    kjørende frontend-containerens bygde JS-bunt).

    Underveis avdekket også en ekte SERVICE WORKER-cache-fallgruve verdt
    å ha i bakhoden for senere feilsøking: en omstart av scratch-
    frontend-containeren (SAMME port) beholdt en GAMMEL, cachet JS-bunt
    i nettleseren til service workeren ble eksplisitt avregistrert og
    cachen tømt -- PWA-installasjonen cacher altså aggressivt nok til å
    overleve en full container-utrulling, noe som er relevant å huske
    ved fremtidig feilsøking av "jeg ser fortsatt det gamle" -rapporter.

    **(2) Bygget (ikke opprinnelig planlagt, brukerønske):** en client-
    side forhåndsvisning i resultatsteget, FØR "Lagre slag" trykkes.
    Bruker Mapbox Static Images API direkte som en `<img src>`, med det
    OFFENTLIGE tokenet (trygt i nettleseren) -- ingen server-tur-retur
    nødvendig kun for en forhåndsvisning, adskilt fra selve delingens
    server-side-genererte bilde (som fortsatt inkluderer en linje mellom
    punktene, ikke bare to nåler).

    **(3) Allerede fungerende:** bekreftet tidligere denne økten
    (scratch18, punkt 48) at selve delings-bildegenereringen fungerer
    server-side med `TEECUP_MAPBOX_SECRET_TOKEN` -- brukerens opplevelse
    av "ingen bilde" der skyldtes at selve DELINGEN aldri fullførte
    (punkt 50s diagnose: "Del i feeden"-valget nullstilles hver gang
    arket åpnes på nytt), ikke en feil i bildegenereringen selv.

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tjueførste
    scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
    `teecup_db` bekreftet uendret) MED `Referer`-overstyring for å komme
    forbi URL-restriksjonen: kartet viste nå ekte satellittbilder (Oslo
    sentrum, default-koordinat), et simulert klikk på kartet plasserte en
    markør korrekt og aktiverte "Bekreft punkt", og resultatsteget viste
    en korrekt forhåndsvisning med A/B-markører og riktig avstand (130m).
    Scratch-miljøet ryddet opp fullstendig.

    **Rullet ut umiddelbart** (produksjonsbug som traff ALLE brukere som
    prøvde kart-basert måling) -- kun frontend-image, `teecup_api`
    bekreftet uendret.

52. **PRODUKSJONSBUG: kartet sentrerte alltid på en hardkodet Oslo-
    koordinat, aldri brukerens faktiske posisjon — 2026-08-08, umiddelbar
    brukeroppfølging etter punkt 51.** Bruker: "Kartet viser ikke hvor
    jeg faktisk er, men en eller annen vilkårlig by."

    **Rotårsak:** `MapPointPicker` sin `center: [10.7522, 59.9139]` var
    en HARDKODET Oslo-koordinat, ledsaget av en kommentar som hevdet
    "real app centers on last-known position" -- den logikken fantes
    ALDRI, kun påstanden i kommentaren. Kartet åpnet dermed alltid på
    samme faste punkt i Oslo, uansett hvor i verden (eller landet)
    brukeren faktisk befant seg.

    **Rettet:** kartet henter nå brukerens ekte GPS-posisjon (ett
    `getCurrentPosition`-kall, samme mønster som "Min posisjon nå"-veien
    -- ingen løpende `watchPosition`) FØR selve Mapbox-kartet
    initialiseres, og sentrerer der. Oslo-koordinaten beholdt KUN som
    fallback for det tilfellet posisjon ikke kan hentes (avslått
    tillatelse, tidsavbrudd, ingen støtte i nettleseren).

    `tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tjueandre
    scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
    `teecup_db` bekreftet uendret): overstyrte `navigator.geolocation` til
    Bergen sentrum (60,39°N 5,32°Ø) og bekreftet kartet faktisk åpnet der
    (synlig annen bystruktur enn Oslo-testen fra punkt 51, pluss Mapbox
    sin egen posisjons-markør synlig i visningen) i stedet for på den
    tidligere faste Oslo-koordinaten. Scratch-miljøet ryddet opp
    fullstendig.

    **Rullet ut umiddelbart** (samme produksjonsbug-alvorlighet som
    punkt 51 -- traff alle som brukte kart-valget utenfor Oslo sentrum)
    -- kun frontend-image, `teecup_api` uendret.

53. **Ballens posisjon (sluttpunktet) kan nå OGSÅ velges på kart, ikke
    bare GPS — pluss et vist referansepunkt for utslaget — 2026-08-08,
    brukerønske umiddelbart etter punkt 52.** Bruker: "Jeg må også kunne
    velge på kartet hvor ballen ligger. Dessuten: Jeg vil gjerne se
    kartet mens jeg går frem til ballen. Slagpunktet må være en del av
    det jeg ser." Dette opphever ADR-048s opprinnelige "sluttpunkt:
    alltid GPS, aldri kart"-del av flyten — se "Tillegg 2026-08-08" i
    ARCHITECTURE_DECISIONS.md for full begrunnelse, historikken der er
    beholdt, ikke overskrevet.

    **Endringer:**
    - Migrasjon `061_round_shot_end_method.sql`: ny `round_shot.end_method
      text NOT NULL DEFAULT 'gps' CHECK (IN ('gps','map_tap'))` — speiler
      `start_method` nøyaktig. `DEFAULT 'gps'` er historisk korrekt: ALLE
      slag før dette tillegget ble faktisk målt med GPS for sluttpunktet.
    - Backend (`app/routers/rounds.py`): `ShotIn.end_method` (default
      `"gps"` for bakoverkompatibilitet), `ShotOut.end_method`,
      `_SHOT_SELECT`/`_shot_out`/`_insert_shot` oppdatert til å lese/skrive
      kolonnen.
    - `frontend/components/shot/map-point-picker.tsx`: ny valgfri
      `referencePoint`/`referenceLabel`-prop. Når satt (kun på
      ball-steget): en FAST, ikke-flyttbar oransje markør ("Utslag") vises
      på kartet, og punktet som faktisk plasseres ved tap er grønt i
      stedet for oransje (samme `ff5a1f`/`2f7a3f`-fargekonvensjon som
      delings-bildet allerede brukte, nå ført konsekvent i selve
      UI-et også). Kartet henter brukerens live GPS-posisjon OG kjenner
      referansepunktet, og bruker `map.fitBounds([nåværende posisjon,
      referansepunkt])` slik at BEGGE garantert er synlige med det samme
      (ikke bare sentrert på ett av dem) — faller tilbake til å sentrere
      på referansepunktet alene (zoom 17) hvis GPS feiler.
    - `frontend/components/shot/shot-measurement-sheet.tsx`: nytt
      `end_map`-steg. "end"-steget tilbyr nå samme valg som "start":
      "Jeg er ved ballen nå" (GPS, uendret) vs. "Vis kart mens jeg går"
      (nytt — åpner `MapPointPicker` med `referencePoint={startPoint}`).
      `goBack()` og `onMapStep`-sjekken oppdatert for det nye steget;
      `endMethod`-state lagt til og sendt med i `onSubmit`-payloaden.
    - `frontend/components/round-detail.tsx`: `submitShot()` sender nå
      `end_method` i POST-kroppen.

    `tsc --noEmit` og `python3 -c "import ast; ast.parse(...)"` begge
    rene.

    **Scratch-verifisert** (tjuetredje scratch-miljø denne økten, samme
    migrasjon-002-forsiktighet, `teecup_db`s ACL bekreftet uendret
    før/etter): (1) alle 61 migrasjoner (inkl. ny 061) kjørte rent mot
    scratch-DB, `test_isolation.sql` fortsatt 12/12 grønt; (2)
    `\d round_shot` bekreftet ny kolonne + CHECK-constraint, et direkte
    forsøk på å sette `end_method='not_valid'` ble korrekt avvist; (3)
    API-nivå via ekte innlogging (magic-link, `TEECUP_DEV_LOG_MAGIC_
    LINKS=true`) og direkte HTTP-kall: `POST .../shots` med
    `end_method: "map_tap"` lagret og returnerte korrekt, samme endepunkt
    UTEN `end_method` i kroppen falt korrekt tilbake til `"gps"`
    (bakoverkompatibilitet bekreftet); (4) full nettleser-gjennomgang via
    Chrome DevTools MCP (samme Referer-header-omgåelse for det
    URL-restrikterte Mapbox-tokenet, og samme
    `navigator.geolocation.getCurrentPosition`-monkey-patch for CDP sin
    faste geolocation-`denied`-begrensning, begge kjente fra punkt 51/52):
    valgte "Min posisjon nå" for startpunktet, deretter "Vis kart mens jeg
    går" for ballen — bekreftet visuelt at kartet åpnet med BÅDE
    "Utslag"-referansemarkøren (oransje) og var zoomet/panorert slik at
    den var synlig med det samme (fitBounds), trykket et punkt på kartet
    og fikk en distinkt GRØNN markør for ballen, fullførte flyten til
    lagring, og verifiserte til slutt med direkte SQL mot scratch-DB-en at
    det lagrede slaget faktisk hadde `start_method=gps, end_method=
    map_tap` — den nøyaktige blandede kombinasjonen brukerens ønske
    beskriver. Scratch-miljøet (DB, rolle, MinIO, API- og
    frontend-container/-images) ryddet opp fullstendig etterpå.

    **Rullet ut** samme økt, bruker bekreftet eksplisitt ("Ja") — migrasjon
    061 kjørt mot ekte `teecup_db`, `teecup_api` og `teecup_frontend`
    bygget/restartet rent, `teecup_db`s ACL bekreftet uendret før/etter,
    verifisert live på `teecup.golf` uten konsollfeil.

54. **Ball-steget viser nå alltid kartet med løpende (sanntids) posisjon
    og avstand, og allerede målte slag viser satellittfoto i listen —
    2026-08-08, umiddelbar brukeroppfølging etter punkt 53.** Bruker,
    etter å ha sett skjermbilder av den nye funksjonen: "Jeg får ikke sett
    slagene jeg allerede har målt... jeg ønsker å kunne se på et
    satelittfoto hvor jeg har slått hvert slag. Det andre er at selv om
    jeg velger 'min posisjon nå', så skal jeg se start og slutt på et
    satelittfoto... I alle sammenhenger... ønsker jeg at jeg skal se
    lengden så langt i sanntid mens jeg nærmer meg ballen." Se
    "Tillegg 2026-08-08 (del 2)" i ARCHITECTURE_DECISIONS.md (ADR-048) for
    full arkitektur-begrunnelse, inkl. det bevisste, avgrensede unntaket
    fra "ingen løpende watchPosition"-prinsippet.

    **Endringer, kun frontend (ingen migrasjon, `ShotOut` hadde allerede
    alle koordinatene):**
    - `map-point-picker.tsx`: `onConfirm` tar nå `(lngLat, method)` i
      stedet for bare `(lngLat)`. Når `referencePoint` er satt (ball-
      steget) startes `navigator.geolocation.watchPosition()` ved mount,
      med en egen blå "du er her"-markør som oppdateres løpende og et
      stort sanntids-avstand-tall ("AVSTAND SÅ LANGT") nederst på kartet,
      beregnet med `haversineMeters(referencePoint, livePosition)`.
      Footeren viser nå TO knapper når `referencePoint` er satt: "Bekreft
      ballens posisjon" (trykket punkt, kun aktiv etter tap) og "Jeg er
      ved ballen nå" (siste sporede posisjon, aktiv så snart første
      GPS-fix er mottatt). `watchPosition` ryddes opp (`clearWatch`) ved
      avmontering.
    - `shot-measurement-sheet.tsx`: `end_map`-steget fjernet igjen (varte
      kun én runde) — "end"-steget rendrer nå ALLTID `MapPointPicker`
      direkte med `referencePoint={startPoint}`, ingen egen GPS/kart-valg-
      skjerm lenger (den valget ligger nå inne i selve kartkomponentens
      footer, se over). `measureEndPoint()`/`endStatus` fjernet (ikke
      lenger i bruk). Egen duplikat Haversine-implementasjon
      (`previewDistance`) erstattet med `haversineMeters` fra
      `frontend/lib/geo.ts` (som alt fantes, men var ubrukt inntil nå) —
      ren opprydding, ingen atferdsendring.
    - `round-detail.tsx`: `ShotRecord`-typen utvidet med
      `start_lat`/`start_lng`/`end_lat`/`end_lng` (allerede returnert av
      API-et, kun frontend-typen manglet dem). `ShotList` viser nå et
      160×160 satellitt-thumbnail per slag (samme offentlige token og
      `pin-s-a`/`pin-s-b`-fargekonvensjon som forhåndsvisningen), bygget
      client-side direkte fra de lagrede koordinatene — løser "jeg burde
      jo se slaget selv, selv om det ikke er delt" sitt neste lag: ikke
      bare klubbe/avstand som tekst, men HVOR slaget faktisk ble slått.

    `tsc --noEmit` rent (ingen backend-endring denne runden).

    **Scratch-verifisert** (tjuefjerde scratch-miljø denne økten, ingen
    ny migrasjon å teste denne runden — samme 61 migrasjoner + `teecup_db`
    ACL-forsiktighet som før): full nettleser-gjennomgang via Chrome
    DevTools MCP med BÅDE `getCurrentPosition`- og `watchPosition`
    monkey-patchet (sistnevnte simulerer en spiller som beveger seg fra
    start mot ballen over ~5 sekunder via et `setInterval`). Bekreftet:
    (1) sanntids-avstanden i kartet regner nøyaktig samme tall som en
    uavhengig Python Haversine-kontroll (315,04 m); (2) "Jeg er ved ballen
    nå" bruker siste sporede posisjon og lagret korrekt med
    `end_method=gps` i databasen; (3) tap på kartet plasserer en grønn
    markør og aktiverer "Bekreft ballens posisjon"; (4) en reell
    grensesnitt-verifisering av EKSISTERENDE feilhåndtering (fra punkt 50)
    skjedde underveis helt av seg selv: et tap som (ved et scratch-
    test-uhell) landet ~0 m fra referansepunktet ble korrekt avvist av
    backendens `distance_meters > 0`-sjekk (422), og arket viste riktig
    feilmelding i stedet for å late som suksess — bekrefter at den
    beskyttelsen fortsatt virker uendret gjennom hele denne
    ombyggingen; (5) slag-listens nye satellitt-thumbnails lastet korrekt
    for begge tidligere lagrede slag. Ingen uventede konsollfeil (kun
    kjente scratch-miljø-artefakter: WebGL-fallback-advarsel, en
    irrelevant WebSocket-tidsavbrudd, og den FORVENTEDE 422-en fra punkt
    4). Scratch-miljøet (DB, rolle, MinIO, API- og frontend-container/
    -images) ryddet opp fullstendig etterpå, `teecup_db`s ACL bekreftet
    uendret.

    **Rullet ut** samme økt, bruker bekreftet eksplisitt ("Ja") —
    `teecup_frontend` bygget/restartet rent (ingen migrasjon eller
    backend-endring denne runden, `teecup_api` uendret), verifisert live
    på `teecup.golf` uten konsollfeil.

55. **Ny landingsside `/velkommen` for innlogget-men-ikke-fullført-profil
    — 2026-08-09, eksplisitt brukerønske (ADR-049, se
    ARCHITECTURE_DECISIONS.md for full begrunnelse og drøfting).** Bruker:
    "Skjemaet med personlig informasjon vises for tidlig for ikke
    registrerte spillere etter at de logger seg inn... Jeg tror det beste
    er at man kommer til en side med oppfordring til å installere som app
    (som vanlig) og med deaktiverte knapper for runde, turneringen og bli
    med med kode. Trykker man på noen av disse skal man få beskjed om at
    personlig informasjon må fylles ut først, med lenke til skjemaet."
    Midtveis presisert: "Alle knappene bør egentlig være der, deaktivert.
    bortsett fra til profilen" — dvs. HELE den vanlige bunn-navigasjonen
    skal vises, ikke bare de tre hurtighandlingene.

    **Rotårsak til det opprinnelige problemet:** tre steder rutet en
    innlogget-men-ikke-fullført-profil-bruker RETT til `/account` sitt
    påtvungne skjema uten noen kontekst: `app/page.tsx` (root-redirect),
    `app/logg-inn/page.tsx` (post-innlogging-redirect), og
    `dashboard.tsx` sin egen klient-side `profile_complete`-vakt
    (`loadMe()`, fyres hvis en bruker skulle lande direkte på
    `/dashboard`, f.eks. via `verify-form.tsx` som ALLTID sender dit
    etter innlogging uansett profil-status).

    **Bygget:**
    - Ny `frontend/app/velkommen/page.tsx` (server-komponent, samme
      autentiserings-/redirect-mønster som `/logg-inn/page.tsx` --
      `redirect()`-kall bevisst holdt UTENFOR try/catch, samme fallgruve
      som ble funnet og fikset i `/logg-inn/page.tsx` 2026-08-08 unngås
      her fra start) + `frontend/components/teecup/velkommen.tsx`
      (klient-komponent). Alle tre stedene over pekt om til `/velkommen`
      i stedet for `/account`. `/velkommen` selv redirecter videre til
      `/dashboard` (komplett profil) eller `/logg-inn` (ikke innlogget)
      -- kan ikke nås "feil" via direkte URL.
    - Siden gjenbruker eksisterende, allerede etablerte komponenter
      uendret: `InstallPrompt` (samme PWA-oppfordring som dashbordet),
      `TeeCupWordmark`, og samme "clubhouse"-palett-tokens som
      `/logg-inn`/dashbordet (reskinnet 2026-08-07/08). Personlig
      hilsen bruker `first_name ?? display_name`, samme navneformat-regel
      som dashbordets hilsen (CLAUDE.md).
    - Tre synlige, LÅSTE hurtighandlinger ("Ny runde"/"Ny turnering"/"Bli
      med med kode" -- hengelås-ikon, `aria-disabled`, IKKE HTML
      `disabled` siden de fortsatt skal være klikkbare for å forklare
      hvorfor de er låst).
    - `frontend/components/teecup/bottom-nav.tsx` utvidet med valgfrie
      `disabledHrefs`/`onDisabledClick`-props (bakoverkompatibelt --
      andre 6 sider som allerede bruker `BottomNav` uendret, ingen prop
      sendt). Når en fane er i `disabledHrefs`, rendres den som en
      `<button aria-disabled>` i stedet for `<Link>` -- `/velkommen`
      viser dermed HELE den vanlige bunn-navigasjonen (Hjem/Runder/
      Turneringer/Profil/Mer), med alle unntatt "Profil" låst, per
      brukerens presisering midtveis.
    - Trykk på EN HVILKEN SOM HELST låst kontroll (hurtighandling ELLER
      bunnfane) viser samme delte påminnelse (`role="alert"`, fast
      posisjonert rett over bunn-navigasjonen slik at den er synlig
      uansett hvilken av de to gruppene som trigget den) med lenke videre
      til `/account`.
    - Presentasjons-seksjonen nederst er hentet fra `teecup-beskrivelse.md`,
      skrevet om til kort UI-tekst (fire punkter: turneringsformater, live
      scoreføring, WHS-handicap, sosialt) -- ikke limt inn rått.

    `tsc --noEmit` rent. Ingen migrasjon eller backend-endring.

    **Scratch-verifisert** (tjuefemte scratch-miljø denne økten): full
    nettleser-gjennomgang av HELE kjeden med en HELT NY bruker (aldri sett
    før, ikke forhåndsopprettet i databasen som tidligere scratch-økter)
    -- registrerte e-post, verifiserte magic-link, landet automatisk på
    `/velkommen` (bekrefter at BÅDE `verify-form.tsx` sin `/dashboard`-
    redirect OG dashbordets egen vakt samvirker riktig -- vakten er den
    som faktisk avgjør endestasjonen). Bekreftet: hilsen viser riktig navn,
    `InstallPrompt` vises, hurtighandlinger viser hengelås og er
    ikke-navigerende, bunn-navigasjonen viser alle fem faner med fire
    låst og "Profil" aktiv, trykk på en låst hurtighandling viser
    påminnelsen med korrekt lenke. Fulgte "Fyll ut nå" til `/account`,
    fylte ut skjemaet ende-til-ende, endte korrekt på `/dashboard` med
    riktig personlig hilsen ("God dag, Ny"). Ingen konsollfeil gjennom
    hele kjeden. Scratch-miljøet (DB, rolle, MinIO, API- og
    frontend-container/-images) ryddet opp fullstendig etterpå,
    `teecup_db`s ACL bekreftet uendret.

    **Rullet ut** samme økt, bruker bekreftet eksplisitt ("ja") —
    `teecup_frontend` bygget/restartet rent (`teecup_api` uendret, ingen
    migrasjon), verifisert live på `teecup.golf` uten konsollfeil.

56. **Fjernet "Bekreft ballens posisjon" (trykk-på-kartet for ballens
    posisjon) igjen — 2026-08-09, samme dag som punkt 54/55, etter faktisk
    bruk på ekte bane.** Bruker sendte et ekte skjermbilde fra telefonen
    (utslags- og live-markør nesten overlappende, "AVSTAND SÅ LANGT 1 m")
    og spurte om forskjellen mellom de to bekreftelsesknappene. Etter
    forklaring: "bekreft ballens posisjon er unødvendig." Avklart via
    spørsmål (siden dette reverserer noe brukeren selv ba om bare timer
    tidligere, jf. CLAUDE.md "spør heller enn å gjette" ved usikkerhet om
    omfang): fjern trykk-alternativet HELT, behold kartet (referansepunkt
    + sanntidsposisjon/-avstand), ballens posisjon bekreftes nå
    UTELUKKENDE med "Jeg er ved ballen nå" (sporet GPS).

    **Endringer, kun `map-point-picker.tsx`:**
    - `map.on("click", ...)`-registreringen (trykk-for-å-plassere-markør)
      er nå betinget på `!referencePoint` -- kjører fortsatt uendret på
      start-steget, aldri lenger på ball-steget.
    - "Trykk der ballen ligger"-banneret fjernet for ball-steget (ingen
      trykk-handling å instruere om lenger); start-stegets "Trykk på
      kartet for å plassere punktet" uendret.
    - Footeren på ball-steget viser nå kun ÉN knapp ("Jeg er ved ballen
      nå", primærstil) i stedet for to stablede knapper.
    - Ingen backend-/migrasjonsendring: `end_method`-kolonnen og
      `Literal["gps", "map_tap"]`-typen beholdes uendret (historiske
      `map_tap`-rader fra den korte perioden funksjonen var live skal
      fortsatt leses/vises korrekt -- kun hvordan NYE slag kan opprettes
      er endret).

    `tsc --noEmit` rent.

    **Scratch-verifisert** (tjuesjette scratch-miljø denne økten, samme
    forsiktighetsrutine, `teecup_db`s ACL bekreftet uendret): full
    nettleser-gjennomgang med `getCurrentPosition`/`watchPosition`
    monkey-patchet. Bekreftet at ball-steget nå viser kartet direkte med
    KUN "Jeg er ved ballen nå" i footeren (ingen "Bekreft ballens
    posisjon"), at et trykk midt på kartet ikke lenger gjør noe (ingen ny
    markør, ingen banner om å trykke), og at "Jeg er ved ballen nå"
    fortsatt lagrer korrekt -- `start_method=gps, end_method=gps`
    bekreftet direkte i databasen etter en full måle-runde (start via GPS,
    kølle, lagring). Ingen konsollfeil. Scratch-miljøet ryddet opp
    fullstendig.

    **Rullet ut** samme økt, bruker bekreftet eksplisitt ("ja") —
    `teecup_frontend` bygget/restartet rent (`teecup_api` uendret),
    verifisert live på `teecup.golf` uten konsollfeil.

57. **Sikkerhetsgjennomgang + fiks av alle funn — 2026-08-09/10, bruker:
    "sjekk alle felter... sjekk alle URL-er. Er alt sikret godt nok? Lar
    siden/appen seg hacke?", deretter "tett sikkerhetshullene først."**
    Full gjennomgang (dedikert agent) av autentisering, autorisasjon på
    tvers av alle 16 routere, input-validering, filopplasting, CORS/
    nettverk, hemmeligheter i git. Se ADR-050 i ARCHITECTURE_DECISIONS.md
    for full begrunnelse. Konklusjon: autorisasjonslaget er uvanlig
    solid (ingen bekreftet IDOR), men fire reelle hull ble funnet og
    fikset:

    1. **Rate limiting** (HØY) lagt til på fem auth-endepunkter (ny
       `app/rate_limit.py`) — ingen fantes fra før, passord/2FA-koder
       kunne i praksis brute-forces.
    2. **Sikkerhetshoder** (LAV-MIDDELS) lagt til i Caddy for
       teecup.golf: CSP, X-Frame-Options, X-Content-Type-Options,
       Referrer-Policy, HSTS.
    3. **Input-validering** (LAV) — manglende `max_length`/`ge`/`le` lagt
       til konsekvent i `rounds.py`/`players.py`/`round_messages.py`/
       `messaging.py`, etter mønster som allerede fantes andre steder i
       samme filer.
    4. **BBB-endepunkt informasjonslekkasje** (informativt) — autorisasjon
       flyttet til FØR formatsjekk i `update_bbb_hole`.

    **Reell driftsfallgruve funnet og løst underveis:** `caddy reload`
    (og en direkte admin-API-PUT) rapporterte suksess, men Caddyfile-
    endringen slo aldri igjennom — `teeoff_caddy`s bind-mount av
    Caddyfile-EN ENKELT FIL var bundet til en nå frikoblet inode etter en
    atomisk skriving (skriv+omdøp) på verten. Løst med `docker restart
    teeoff_caddy` (ikke bare reload) — se ADR-050 for full forklaring,
    dette er en STÅENDE fallgruve for enhver fremtidig Caddyfile-endring.

    `python3 -m py_compile` rent på alle endrede filer.

    **Scratch-verifisert** (tjuesjuende scratch-miljø denne økten, samme
    `teecup_db`-ACL-forsiktighet): rate limiting bekreftet å faktisk
    utløse 429 ved riktig terskel på BÅDE `login-password` (10 tillatt,
    11. avvist) og `request-link` (5 tillatt, 6. avvist); input-
    validering bekreftet begge veier (HCP=99 avvist, HCP=18.4 akseptert,
    50-tegns mobilnummer avvist); BBB-endepunktet bekreftet å returnere
    403 NOT_AUTHORIZED (ikke lenger en 400 format-lekkasje) for en bruker
    uten tilgang til en fremmed runde, OG fortsatt 200 OK for den
    faktiske eieren på en ekte BBB-runde (regresjonssjekk). CSP-en
    verifisert direkte mot PRODUKSJON i ekte nettleser (ikke scratch,
    siden Caddy-konfigen er delt infrastruktur som ikke er en del av
    scratch-oppsettet): ingen CSP-brudd i konsollen, et ekte Mapbox-kall
    med gyldig token ga 200 OK, login-siden rendret visuelt korrekt (inline
    styles uendret). `teecup_db`s ACL bekreftet uendret før/etter,
    scratch-ressurser ryddet opp fullstendig.

    **Rullet ut** — Caddy-delen ble rullet ut/verifisert direkte mot
    produksjon underveis (se over). `teecup_api`-delen (rate limiting,
    validering, BBB-fiks) bygget/restartet etter eksplisitt
    brukerbekreftelse ("ja takk"), verifisert LIVE: `POST /auth/
    request-link` mot ekte `teecup.golf` ga 200 på de fem første
    forsøkene og 429 på det sjette (nøyaktig terskelen satt i koden),
    ingen konsollfeil i ekte nettleser etterpå.

58. **13-årsgrense for kontoregistrering + to presentasjonstekst-
    korrigeringer — 2026-08-10, bruker etter å ha lest to PDF-vedlegg.**
    Se ADR-051 i ARCHITECTURE_DECISIONS.md for full begrunnelse og
    drøfting av anbefalingene i "Barns personvern i golfapp"-PDF-en.

    **Endringer:**
    - `app/routers/auth.py`: `update_profile` avviser nå `PATCH /auth/
      profile` med `400 UNDER_MINIMUM_AGE` hvis `birth_date` innebærer
      under 13 år (eksakt dagsberegning). Dette er den ENESTE veien inn i
      appen (profil må være komplett for å komme forbi `/velkommen`,
      ADR-049), så dette ene stedet håndhever grensen for hele appen.
      Bevisst IKKE utvidet til `player`-tabellen (organisasjonens
      roster/CRM, `players.py`/`registration.py`) -- en `player`-rad er
      en arrangørs registrering AV en person, ikke personen som
      registrerer SEG SELV, og er selve PDF-ens egen anbefalte, tryggere
      løsning for yngre spillere.
    - `account-settings.tsx`: ny delt `computeAge()`-hjelpefunksjon,
      brukt i BÅDE `ProfileOnboarding` (førstegangs-utfylling) og
      `ProfileSection` (senere redigering under Konto) for umiddelbar
      rød feilmelding + deaktivert lagre-knapp -- før serveren i det
      hele tatt kontaktes.
    - `install-prompt.tsx`: ny `iosBrowser()` skiller Safari fra Chrome
      på iOS (`CriOS` i user agent) -- installasjonsteksten hevdet
      tidligere feilaktig at man MÅTTE til Safari, og pekte til "nederst
      i Safari" uansett nettleser. Siden iOS 16.4 kan Chrome installere
      PWA-er direkte, med Del-ikonet et ANNET sted (oppe til høyre i
      adressefeltet, ikke nederst). Ukjente iOS-nettlesere får nå en
      nøytral "i nettleseren din"-tekst i stedet for en gjetning.
    - `teecup-beskrivelse.md` + `velkommen.tsx`: "golfklubber" fjernet
      fra målgruppe-beskrivelsen (to steder i beskrivelses-dokumentet,
      ett i `/velkommen`s "Hva er TeeCup?"), nytt punkt lagt til om at
      turneringsadministrasjon/-presentasjon fungerer minst like godt på
      PC som på telefon. "Klubbhus-stemning" (design-retningens navn,
      ren tone-beskrivelse) er bevisst uendret.

    `tsc --noEmit` og `python3 -m py_compile` begge rene.

    **Scratch-verifisert** (tjueåttende scratch-miljø denne økten, samme
    `teecup_db`-ACL-forsiktighet): eksakt dagsgrense bekreftet med tre
    API-kall (10-åring avvist, nøyaktig 13 år i dag akseptert med
    `profile_complete: true`, én dag under 13 avvist). Ekte nettleser:
    klientside rød feilmelding + deaktivert knapp bekreftet ved
    2018-fødselsdato, `/velkommen`s presentasjonstekst bekreftet uten
    "golfklubber" og med det nye PC-punktet. Ingen konsollfeil. Scratch-
    miljøet (DB, rolle, MinIO, API- og frontend-container/-images) ryddet
    opp fullstendig, `teecup_db`s ACL bekreftet uendret.

    **Rullet ut** samme økt, bruker bekreftet eksplisitt ("Ja") — begge
    containere bygget/restartet rent, verifisert live på `teecup.golf`
    uten konsollfeil.

59. **Designrunden: full clubhouse-overtagelse (lys + mørk) — 2026-08-10,
    se ADR-052.** Bruker ba om "designrunden" og valgte, via
    `AskUserQuestion`, det bredeste av tre foreslåtte omfang: "Full
    overtagelse, alt på én gang" — resten av appen (unntatt
    ny-runde-veiviserens `nr-*`-palett) skulle over på "clubhouse"-
    paletten i én samlet runde, ikke gradvis skjerm for skjerm som
    tidligere planlagt.

    **Root cause / tilnærming:** tre paletter levde side om side —
    "Forest Green" (shadcn-tokens, ~90 filer), "clubhouse" (7 filer:
    innlogging/dashbord/bunnnav/velkommen), "nr-*" (ny-runde-veiviseren,
    utenfor omfang). En Explore-agent bekreftet at `components/ui/*` OG
    de ~90 gjenværende filene er RENE token-konsumenter (null hardkodet
    hex funnet noe sted utenfor de 7 clubhouse-filene) — konsekvensen:
    hele overtagelsen kunne gjøres ved å redefinere shadcn-tokenene i
    `frontend/app/globals.css` (`:root`/`.dark`/media-blokk) til
    clubhouse sine faktiske verdier, i stedet for å endre className i 90
    filer. `--destructive`/`--brand-orange`/`--info`/`--gold`/
    `--chart-1..6` bevisst UENDRET (egne semantiske signalfarger, ikke
    del av identiteten paletten styrer).

    **Kontrast-korreksjon funnet FØR utrulling:** `--primary-foreground`
    måtte endres fra nesten-svart til hvit — clubhouse sin
    `--tee-strong` (`#2f6b1e`) er en MØRK grønn (brukes allerede med
    `text-white` i `velkommen.tsx`), ulikt den gamle LYSE primærgrønnen.

    **Mørk variant designet av V0 på bestilling** (ingen fantes fra før —
    clubhouse var "fast lys-modus"). Claude skrev en ren fargeoppgave-
    prompt (ti låste lyse verdier som fasit, eksplisitt WCAG AA-krav),
    bruker limte den inn i V0 og limte svaret tilbake som en prosjekt-zip
    (`globals.css`-diffen inneholdt kun ny `.dark`-blokk). Claude
    verifiserte V0s egne kontrasttall uavhengig (egen WCAG-beregning) —
    alle stemte eksakt: ink/bg 15.7:1, muted/bg 8.2:1, muted/card 7.2:1,
    hvit/tee-strong 4.76:1.

    **Reelt funn under planlegging, sparte en unødvendig kodeendring:**
    opprinnelig plan antok de 7 clubhouse-filenes hardkodede
    `bg-clubhouse-*`/`bg-tee-strong`-klassenavn måtte skrives om til
    generiske tokens for å følge mørk modus. Viste seg unødvendig —
    siden en `.dark`/media-blokk for de RÅ `--tee`/`--cup`/
    `--clubhouse-*`-variablene ble lagt til (symmetri med
    hovedremappen), arver disse 7 filene mørk modus automatisk via
    CSS-variabel-cascade, uansett hvilket klassenavn de bruker. Ingen av
    de 7 filene endret.

    Liten tilleggsrettelse: `app/layout.tsx`s `themeColor`-metadata
    (`#8BC24A`/`#1c261d`) pekte fortsatt på gamle farger — oppdatert til
    de nye bakgrunnsfargene (`#f3f6ec`/`#131a0f`).

    `DESIGN_SYSTEM.md` oppdatert til å beskrive clubhouse som selve
    hovedpaletten (var tidligere "Forest Green").

    **Scratch-verifisert** (full stack: ny scratch-DB med alle 61
    migrasjoner, `teecup_app_dr1`-rolle, hyphenert scratch-MinIO, scratch
    API- og frontend-container — ekte produksjonsbuild av frontend, ikke
    bare `tsc`). Data seedet via ekte API-kall (personlig bane, runde,
    hullscore), ikke rå SQL. `tsc --noEmit` og produksjonsbuild begge
    rene. Browserverifisert i BÅDE lys og mørk modus (Chrome DevTools
    MCP, `emulate colorScheme`) på `/logg-inn`, `/velkommen`,
    `/dashboard` (tom + med data), `/my-rounds/[id]` (appens største
    fil, 6726 linjer, tidligere Forest Green), scorekort-tabellen (tett
    datagrid), `/account` (skjematung side) — alle konsistente, god
    kontrast, ingen konsollfeil utover en harmløs PWA-infomelding.
    `/my-rounds/new` bekreftet visuelt uendret (ingen smitte inn i
    `nr-*`). `BottomNav`s bevisst palett-nøytrale hvite flate forblir
    hvit i mørk modus også — eksisterende, bevisst V0-designvalg, ikke
    en regresjon. `teecup_db`s ACL bekreftet uendret før/etter, alle
    scratch-ressurser ryddet opp fullstendig.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Ja takk") —
    `docker compose build teecup_frontend && docker compose up -d
    teecup_frontend`, ren omstart. Bekreftet live på `teecup.golf` i ekte
    nettleser i BÅDE lys og mørk modus, ingen konsollfeil utover den
    harmløse PWA-infomeldingen.

60. **Ny-runde-veiviseren retemaet til clubhouse — 2026-08-10, se
    ADR-053.** Rett etter forrige punkt ba bruker om at veiviseren
    (`nr-*`-paletten, bevisst holdt utenfor punkt 59 som en tredje,
    uavhengig V0-palett) også skulle få det nye designet.

    Samme remap-prinsipp som punkt 59, samme fil (`globals.css`): de 16
    `--nr-*`-variablene komponentene i `components/ny-runde/*` leser
    (rå `var(--nr-x)`, bekreftet null hardkodet hex noe sted) fikk nye
    verdier — `--nr-bg/-surface/-surface-2/-ink/-muted/-border/-accent/
    -accent-ink` byttet til clubhouse (lys+mørk), `--nr-danger(-soft)`/
    `--nr-ok(-soft)` beholdt sin egen røde/grønne hue i lys modus
    (samme prinsipp som uendret `--destructive`), men fikk NYE mørke
    varianter siden `.nr` aldri hadde mørk-modus-støtte før. Fire
    variabler uten direkte clubhouse-kilde (`--nr-faint`, `--nr-border-
    strong`, `--nr-accent-soft`, `--nr-accent-ring`) ble avledet
    (alfa-blandet) fra clubhouse sine godkjente farger, kontrastsjekket
    til aldri å havne under originalens egne verdier. Ingen av de 11
    filene i `components/ny-runde/*` endret.

    **Scratch-verifisert** (eget scratch-miljø, samme fulle DB/rolle/
    MinIO/API/frontend-oppsett som punkt 59). `tsc --noEmit` rent.
    Browserverifisert i BÅDE lys og mørk modus: bane-valg, "Egen
    bane"-tom-tilstand (øver `--nr-faint`), og det 18-raders tette
    hull-for-hull-skjemaet (par/stroke-indeks-dropdowns). Alle
    konsistente, god kontrast. Tre pre-eksisterende DOM-lint-varsler
    (manglende `autocomplete`/`id`-attributter i selve V0-markupen) —
    uendret fra før denne CSS-only-runden, ikke forårsaket av den.
    Scratch-ressurser ryddet opp fullstendig, `teecup_db`s ACL bekreftet
    uendret.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Deretter;
    slett kontoen, og gjør det resterende live") — ren omstart av
    `teecup_frontend`, bekreftet live på `teecup.golf` uten konsollfeil
    utover den harmløse PWA-infomeldingen.

61. **PRODUKSJONSHENDELSE: konto under 13-årsgrensen slettet — 2026-08-10.**
    Bruker ba om en engangssjekk av ekte `teecup_db` (read-only SELECT,
    eksplisitt bedt om av bruker) etter at 13-årsgrensen (punkt 58) gikk
    live: fantes det allerede kontoer registrert med fødselsdato under
    13 år? Ett treff — "Fredrik Mathisen" (`fredrik.f.mathisen@gmail.com`),
    registrert 2026-08-09 med fødselsdato SATT TIL SAMME DAG (2026-08-09,
    altså 0 år) — mest sannsynlig en dato-velger som ble stående på
    dagens dato i stedet for en reell fødselsdato, ikke nødvendigvis en
    bekreftet mindreårig, men nøyaktig signalet regelen er bygget for å
    reagere på uansett årsak. Sjekket tilknyttet data først (runder,
    medlemskap, meldinger, venner) — null treff overalt, en helt tom,
    ubrukt konto.

    Bruker godkjente eksplisitt både e-postteksten (norsk, forklarer
    sletting og gir mulighet til å opprette ny konto med riktig
    fødselsdato ved feilregistrering) og selve slettingen FØR noe ble
    utført. Rekkefølge: (1) `DELETE FROM app_user WHERE id = ...` mot
    ekte `teecup_db` (teeoff_admin) — bekreftet FK-kaskade ren siden
    kontoen ikke hadde noen tilknyttet data i noen av de NO ACTION-
    begrensede tabellene (message/round_message/round_participant/
    round_shot m.fl.), (2) e-post sendt til den registrerte adressen via
    appens eksisterende `_send_sync`-mekanisme i `app/email.py` (samme
    SMTP-oppsett som magic-link-utsending), kjørt som et engangsskript
    inni den ekte `teecup_api`-containeren, ikke en ny permanent
    funksjon (ett engangstilfelle, ingen gjenbruk planlagt ennå).
    Bekreftet i etterkant: kontoen finnes ikke lenger i `app_user`.

    **Bevisst utenfor omfang:** ingen automatisert, gjentakende sjekk
    for fremtidige under-13-registreringer er bygget — aldersgrensen i
    `PATCH /auth/profile` (punkt 58) hindrer allerede NYE registreringer,
    dette var en engangsopprydding av kontoer fra FØR den regelen gikk
    live. Et automatisert varsel/rutine for dette kan vurderes som egen,
    fremtidig sak om det blir aktuelt.

62. **Fjernet e-postvarsel for "venn startet en runde du kan følge" —
    2026-08-10.** Bruker ba eksplisitt om å fjerne kun e-posten for denne
    hendelsen (`type="round"`-notifikasjonen sendt fra `create_round` i
    `rounds.py` til venner som kan følge runden) — in-app-varselet
    (klokke-ikonet) og push-varselet skal fortsatt fungere som før.

    **Kompleksitet:** samme `type="round"` deles av EN ANNEN, fortsatt
    ønsket e-post ("du ble lagt til som medspiller", sendt fra
    `add_participant`) — å slå av e-post for hele `"round"`-typen ville
    fjernet begge. Løst med et nytt `allow_email: bool = True`-parameter
    på `create_notification()` (`app/routers/notifications.py`), satt
    til `False` KUN på kallstedet i `create_round`. In-app-innsettingen
    og `_push_for_notification` kjører uendret uansett — kun selve
    e-post-blokken hopper over. Ingen migrasjon, ingen endring i
    `user_notification_email_pref`-skjemaet.

    `frontend/components/account-settings.tsx`: "Runder"-varselvalgets
    beskrivelsestekst rettet ("Du blir lagt til som medspiller, eller en
    venn starter en runde du kan følge." → "Du blir lagt til som
    medspiller på en runde.") — teksten lovet tidligere en e-post som nå
    aldri sendes.

    **Scratch-verifisert** (egen scratch-DB/rolle/MinIO/API-container,
    `TEECUP_DEV_LOG_MAGIC_LINKS=true` + tomme SMTP-variabler for å
    observere e-post-forsøk som en tydelig loggmelding i stedet for en
    ekte utsending): to testbrukere, akseptert vennskap, `watcher` opt-et
    inn på `round`-e-post. (1) `owner` opprettet en offentlig synlig
    runde — in-app-varsel opprettet for `watcher`, INGEN "[DEV]
    Varsel-e-post"-linje i loggen. (2) `owner` la `watcher` til som
    medspiller på samme runde — in-app-varsel opprettet OG nøyaktig én
    "[DEV] Varsel-e-post"-linje, bekreftet riktig melding. Begge
    in-app-radene bekreftet i `notification`-tabellen etterpå. `tsc
    --noEmit` og `python3 -m py_compile` begge rene. Scratch-ressurser
    ryddet opp fullstendig, `teecup_db`s ACL bekreftet uendret.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Ja, kjør") —
    begge containere bygget/restartet rent, bekreftet live på
    `teecup.golf` uten konsollfeil utover den harmløse PWA-infomeldingen.

63. **Kartretning ved slagmåling: "opp = fremover på hullet", uten
    green-koordinater — 2026-08-10, se ADR-054.** Bruker spurte om
    kartet kunne roteres slik at man "går oppover" under slagmåling,
    uten å registrere green-koordinater (bekreftet tidligere at ingen
    slike finnes noe sted) — og ba deretter eksplisitt om at det bygges.

    Løsning: bearingen (kompassretningen) kartet roteres til regnes ut
    fra spillerens EGET forrige slag på samme hull (`round_shot` sine
    allerede lagrede start-/sluttkoordinater), ikke fra en lagret
    hull-linje. Ny `bearingDegrees()` i `frontend/lib/geo.ts` (ren
    funksjon, standard forward-azimuth). Satt ÉN gang ved
    kart-initialisering (`bearing` i Mapbox-konstruktøren), aldri
    løpende oppdatert under gange — vurdert og bevisst avvist, se
    ADR-054 Beslutning B (GPS-heading er for støyete ved lav fart til
    kontinuerlig rotasjon). Første slag på et hull (ingen forrige å
    regne fra) faller tilbake til ett engangs-forsøk på enhetens
    `coords.heading`, deretter nord.

    Rent klientside: `lib/geo.ts`, `components/shot/map-point-picker.tsx`,
    `components/shot/shot-measurement-sheet.tsx`,
    `components/round-detail.tsx` (`ShotMeasurementEntry`, som allerede
    har slag-listen for hullet fra sin eksisterende henting). Ingen
    migrasjon, ingen backend-endring.

    **Verifisert i to lag:** `bearingDegrees()` unit-testet frittstående
    (fire kjente himmelretninger — nord/øst/sør/vest ga 0/90/180/270
    eksakt). Ende-til-ende i ekte nettleser (eget scratch-miljø): seedet
    ett slag rett øst på hull 1 via ekte API-kall, midlertidig
    konsollogg (fjernet igjen etter verifisering) bekreftet kartet fikk
    `bearing≈90` på hull 1 og `bearing=0` (nord-fallback) på hull 2 (uten
    tidligere slag). `tsc --noEmit` rent. Scratch-ressurser ryddet opp
    fullstendig, `teecup_db`s ACL bekreftet uendret.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør den.") —
    ren omstart av `teecup_frontend`, bekreftet live på `teecup.golf`
    uten konsollfeil utover den harmløse PWA-infomeldingen.

64. **Nytt app-ikon (ball/pokal/tee), full-bleed, hakk-feil rettet —
    2026-08-10, se ADR-055.** Bruker: dagens ikon var "fremdeles et
    gammelt utkast" — bekreftet ved å åpne `icon-512.png`/
    `icon-maskable-512.png`/`apple-icon.png` direkte: riktig motiv, men
    grov beskjæring med enorm hvit padding rundt en liten grafikk,
    nesten ulesbart ved 32×32.

    V0-prompt (samme motiv, ny komposisjon: full-bleed bakgrunn,
    sentrert i safe-zone, ingen egen avrunding) ga et første svar med to
    synlige hakk i pokal-formen. Claude sammenlignet FØRST mot feil
    kildefil og konkluderte feilaktig at V0 hadde "tegnet på nytt etter
    øyemål" — bruker rettet dette ved å dele den faktiske kilde-SVG-en
    (`TeeCup-logo-kun.svg`) direkte. Rendret stort viste at hakkene var
    en ekte, pre-eksisterende unøyaktighet i selve kildekunsten (to
    overlappende oransje former som ikke dekker hverandre helt i to
    punkter) — usynlig på hvit bakgrunn, synlig mot mørk. V0s "tatt
    verbatim"-påstand var korrekt; Claudes første mistanke var feil.

    Presis rettemelding til V0 (identifiserte de to path-fyllfargene)
    ga en fiks (tettet gapet med en `stroke` i samme farge som fyllet,
    ikke en omtegning) — verifisert visuelt før integrering, hakkene
    bekreftet borte, ingen nye artefakter.

    Integrert: ett 1024×1024 master-SVG er eneste kilde. Alle
    pikselstørrelser (`icons/icon-192.png`, `icons/icon-512.png`,
    `icons/icon-maskable-512.png`, `apple-icon.png` 180×180,
    `icon-light/dark-32x32.png`) rastret av Claude selv via ekte
    nettleser-rendring (`devicePixelRatio=1`, bekreftet eksakte
    pikseldimensjoner etterpå) — ikke V0-genererte PNG-er, for å unngå
    eksport-unøyaktighet. `public/icon.svg` (SVG-favikon) erstattet med
    master-SVG-et. `app/manifest.ts` sin `theme_color`/`background_color`
    (fortsatt gamle Forest Green-farger, `#8BC24A`/`#ffffff`) oppdatert
    til clubhouse (`#2f6b1e`/`#f3f6ec`).

    **Scratch-verifisert**: `tsc --noEmit` rent, egen frontend-only
    scratch-container (ekte produksjonsbuild), `GET
    /manifest.webmanifest` bekreftet nye farger, ikonfilene bekreftet
    200 og visuelt korrekte i ekte nettleser. Scratch-ressurser ryddet
    opp fullstendig.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør") — ren
    omstart av `teecup_frontend`, bekreftet live på `teecup.golf`:
    `icon.svg` og `manifest.webmanifest` (nye farger) begge korrekte,
    ingen konsollfeil utover den harmløse PWA-infomeldingen.

65. **WHS Rule 3.1b-unntak (>54 banehandicap + 4 mottatte slag → par+5)
    implementert og fasit-testet + `round.tee_name_snapshot` synk-bug
    rettet — 2026-08-10, se ADR-056.** Bruker delte en ekstern vurdering
    av appens to "harde kjerner" (WHS-beregning, live-scoring/
    samtidighet) og ba om en ærlig sjekk. To Explore-agenter gransket
    hver sin kjerne parallelt.

    **WHS-funn:** `handicap_engine.py` manglet et reelt, publisert
    unntak (verifisert direkte mot kilde-PDF-en, side 37, ikke bare
    agentens gjenfortelling): banehandicap over 54 OG 4+ mottatte slag
    på ett hull → maks hullscore par+5, ikke den vanlige
    par+2+mottatte-slag. Lagt til som et nytt, bakoverkompatibelt
    `course_handicap`-parameter på `max_hole_score_for_handicap`/
    `adjusted_gross_score`, wired inn i de to produksjonskallstedene i
    `rounds.py`. Fire nye tester i `test_handicap_engine.py` (eksplisitte
    grensetilfeller: eksakt 54, eksakt 3 slag, samt end-til-ende) — 117/117
    grønne. Scratch-verifisert også via ekte API-kall (deltaker med
    banehandicap 63, "plukket opp" på et hull med 4 slag ga korrekt 9,
    ikke det gamle 10).

    **Egen, urelatert bug funnet samtidig** (bruker viste skjermdump av
    en live runde der toppheaderens utslag ikke fulgte et utslagsbytte):
    `PATCH .../participants/{id}` skrev kun til `round_participant`,
    aldri til `round.tee_name_snapshot` (som selve header-visningen
    leser). Fikset: skriver nå begge når det er EIERENS EGEN
    deltaker-rad som endres (ikke en gjests — ulike deltakere kan
    bevisst ha ulikt utslag). Scratch-verifisert: eierens bytte
    oppdaterer runden, en gjests bytte gjør det ikke.

    `tsc`/`py_compile` rene der aktuelt. Scratch-ressurser ryddet opp
    fullstendig, `teecup_db`s ACL bekreftet uendret gjennom hele
    verifiseringen.

    **Ikke rettet ennå:** den konkrete live runden brukeren viste (feil
    historisk data i ekte `teecup_db`) — venter på egen bekreftelse for
    en engangs datakorreksjon. Samtidighets-/konflikt-halvparten av
    vurderingen er bevisst IKKE besluttet i denne runden — krever en
    designbeslutning fra bruker først, se ADR-056.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør alt") —
    `teecup_api` bygget/restartet rent. Den konkrete runden brukeren
    rapporterte (`round.tee_name_snapshot` feilaktig "50") korrigert til
    "55" med en engangs `UPDATE` mot ekte `teecup_db`, bekreftet i
    etterkant.

66. **Optimistisk versjonssjekk for samtidig hull-redigering — 2026-08-10,
    se ADR-057.** Direkte oppfølging av punkt 65: bruker fikk valget
    mellom tre tilnærminger til samtidighets-halvparten av den eksterne
    vurderingen (versjonssjekk / feltvis skrivning / la det ligge), valgte
    "enkel versjonssjekk + tydelig feilmelding".

    Migrasjon 062: ny `version`-kolonne på `round_hole` (dekker begge
    eierskapstyper, deltaker og side, én tabell). `update_hole`/
    `update_side_hole` sjekker nå `expected_version` ATOMISK i selve
    UPDATE-en (`WHERE ... AND (expected_version IS NULL OR version =
    expected_version)`, ingen separat les-så-skriv-race), 409 med tydelig
    norsk feiltekst ved konflikt, skiller korrekt fra 404 (hull finnes
    ikke). `expected_version` valgfri — bakoverkompatibelt.

    Offline-køen (`offline-queue.ts`) kjeder nå versjonen fremover
    innad i én flush-runde — uten dette ville en spillers EGNE
    påfølgende offline-redigeringer av samme hull avvist hverandre som
    falske konflikter (funnet under design, før noe ble bygget).

    **Reelt UX-hull funnet under scratch-verifisering, rettet før
    utrulling:** første versjon gjenbrukte komponentens eksisterende
    `error`-tilstand for konfliktmeldingen — viste seg (kun synlig ved
    ekte to-enhets-test i nettleser) å være en FATAL, hele-siden-
    erstattende tilstand. En forbigående hull-konflikt tok dermed over
    hele skjermen. Rettet med en egen, dismissbar `conflictNotice`-
    banner (gjenbrukt også for den eksisterende kø-synk-feilmeldingen,
    som hadde samme problem fra før).

    **Scratch-verifisert i tre lag, alt i ekte nettleser/API:** (1)
    begge backend-endepunktene hver for seg — riktig 409/404-skille,
    vinnerens data bekreftet bevart, bakoverkompatibilitet uten
    `expected_version` bekreftet. (2) To ISOLERTE nettleser-kontekster
    (ekte to-enheter-simulering) — konflikt ga 409, veiviseren viste
    automatisk riktig (den andres) verdi, banner dismissbart, RESTEN AV
    SIDEN forble fullt brukbar (dette avdekket UX-hullet over). (3)
    Ekte offline-emulering — to redigeringer av samme hull frakoblet,
    begge synkroniserte korrekt ved reconnect, ingen falsk konflikt.
    `tsc`/`py_compile` rene. Scratch-ressurser ryddet opp fullstendig,
    `teecup_db`s ACL bekreftet uendret.

    **Bevisst utenfor omfang:** org-turneringenes match-scoring (helt
    separat system/tabell) ikke undersøkt eller endret denne runden.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør alt") —
    migrasjon 062 kjørt mot ekte `teecup_db` FØR `teecup_api`/
    `teecup_frontend` ble bygget/restartet (riktig rekkefølge, ny kode
    leser `version`-kolonnen). Bekreftet live: begge containere startet
    rent, ingen konsollfeil på `teecup.golf`.

67. **Automatisert backend-testinfrastruktur — RLS, versjonssjekk,
    aldersgrense — 2026-08-10, se ADR-058.** Direkte oppfølging av
    investor-statusrapporten samme dag: "nesten ingen automatisert
    testdekning utenfor HCP-motoren" var det klart største enkeltfunnet.
    Bruker ba om å ta tak i akkurat dette.

    `scripts/run_backend_tests.sh` automatiserer det som til nå har
    vært en manuell prosedyre hver runde: oppretter en scratch-database
    + scratch-rolle i den samme `teeoff_db`-containeren, kjører alle 62
    migrasjonene i rekkefølge (med `sed`-patch av migrasjon 002 sitt
    hardkodede rollenavn OG databasenavn til scratch-spesifikke verdier
    — sistnevnte et nytt funn: uten patch ville testrollen fått en
    reell, om enn ufarlig, `GRANT CONNECT`-rettighet på den EKTE
    `teecup_db`), bygger en dedikert `Dockerfile.test`-container
    (pytest, IKKE i prod-imaget) tilkoblet samme `teeoff_default`-
    nettverk som `teecup_api` selv bruker (nødvendig — Postgres-
    containeren har ingen host-publisert port i dette miljøet), kjører
    testene, dropper scratch-database + -rolle igjen. Rører aldri ekte
    `teecup_db` — bekreftet uendret ACL og radantall etter hver kjøring.

    17 nye tester, tre områder valgt etter risiko: RLS/tenant-isolasjon
    (4 — inkl. regresjonsvern for migrasjon 005s NULL-guard), den
    optimistiske versjonssjekken fra punkt 66 (6 — begge
    `update_hole`/`update_side_hole`), 13-årsgrensen (5 — inkl. eksakt
    dagsgrense begge veier). Testene kaller de FAKTISKE router-/auth-
    funksjonene direkte (ikke en SQL-gjenimplementering) — samme
    kodesti som produksjon.

    **Verifisert:** alle 17 nye + de eksisterende 117 HCP-testene
    grønne. Ingen produksjonskode endret denne runden — ren
    testinfrastruktur, ingen deploy.

    **Bevisst utenfor omfang:** frontend-tester, og dekning av
    org-turneringenes match-scoring (`scoring.py`) — som ADR-057
    allerede har dokumentert mangler samtidighetsvern, men som
    fortsatt ikke har en test som beviser det empirisk.
    CI-workflow-filen ble løst SAMME dag, se punkt 68.

68. **Selvhostet Forgejo Actions-runner — 2026-08-10, se ADR-059.**
    Direkte oppfølging av punkt 67: en `.forgejo/workflows/
    backend-tests.yml` ble lagt til og pushet som en empirisk test —
    Forgejo-instansen svarte `has_actions: true`, men det beviste ikke
    at noe faktisk plukket opp jobber. Jobben la seg som "Waiting" og
    sto uendret i over et minutt. Ingen runner fantes. Bruker ba om
    at det rettes ("start med ci").

    Satt opp en selvhostet `act_runner` PÅ DENNE serveren (må være her
    — `run_backend_tests.sh` er avhengig av `docker exec teeoff_db` og
    `teeoff_default`-nettverket, som kun finnes lokalt). Docker-
    utenfor-Docker (`docker_host: automount`): jobb-containeren får
    host-ens `docker.sock` bind-montert inn, slik at testskriptets
    egne `docker build`/`run`/`exec`-kall lager ekte søsken-containere
    på host-nivå. Jobb-image `catthehacker/ubuntu:act-latest` (docker-
    CLI/git/bash forhåndsinstallert).

    Registrerings-tokenet ble ALDRI limt inn i chatten — bruker hentet
    det selv fra Forgejo sitt UI, la det i en `chmod 600`-fil på
    serveren, Claude leste filen direkte og makulerte den (`shred -u`)
    rett etter registrering. Den ekte, langlevde runner-legitimasjonen
    som oppsto (`.forgejo-runner/.runner`) er gitignored og `chmod 600`
    — samme disiplin som `.env`.

    Startet først manuelt for å bevise at det virket, deretter flyttet
    inn som en egen `teecup-forgejo-runner`-tjeneste i
    `docker-compose.yml` (`restart: unless-stopped`) for samme
    driftsdisiplin som resten av stacken.

    **Sikkerhetsnotat, bevisst akseptert:** `docker.sock`-tilgang er
    root-ekvivalent kontroll over verten — en reell heving av
    angrepsflaten. Akseptert fordi det er samme prinsipp
    `run_backend_tests.sh` allerede krevde (nå automatisert fremfor
    kjørt fra en allerede-priviligert shell), og runneren betjener kun
    denne ene repoens CI på en enkeltpersons egen server.

    **Verifisert:** ekte push → workflowen kjørte → "Success" i
    Forgejo sitt UI (grønn hake, samme kø-oppføring som nettopp sto
    "Waiting"). `teecup_db`s rolleoppsett og fravær av gjenglemte
    scratch-databaser bekreftet uendret etter kjøring.

69. **Optimistisk versjonssjekk for org-turneringers match-scoring —
    2026-08-10, se ADR-060.** Fortsettelse av robusthetslinjen: den
    siste dokumenterte, kjente svakheten (ADR-057/058: org-
    turneringenes match-scoring hadde ingen samtidighetsvern, ren
    siste-skriving-vinner) tettet på brukerens eksplisitte forespørsel.

    Migrasjon 063: `version`-kolonne på BÅDE `hole_score` og
    `match_hole_result` (to separate tabeller/scoring-modi). Ulikt
    round_hole er disse UPSERT (`INSERT ... ON CONFLICT DO UPDATE`),
    ikke ren UPDATE — løst med Postgres sin `DO UPDATE ... WHERE`, som
    kun evalueres på selve konflikt-grenen. Konsekvens: enklere enn
    round_hole — en avvist skrivning er ALLTID en versjonskonflikt,
    aldri en "finnes ikke"-tvetydighet.

    Reelt funn under design: offline-kø-kjedingen fra punkt 66 nøklet
    på URL alene, som IKKE er entydig for `/hole-scores`/`/hole-
    results` (samme URL for alle hull i en match). Generalisert med et
    nytt, valgfritt `resourceKey`-felt på kø-oppføringen (default url,
    100 % bakoverkompatibelt med round_hole sin bruk).

    `submit_hole_score`/`submit_hole_result` i `app/routers/scoring.py`,
    `session-scorecard.tsx`, `offline-queue.ts`. 6 nye pytest-tester
    (`tests/test_scoring_concurrency.py`) — delt-ball og individuell-
    ball-grenen hver for seg, begge endepunktene, vellykket skrivning +
    409-konflikt + bekreftet at avvist skrivning ikke lagres. Alle 23
    backend-tester grønne (kjørt via CI-runneren fra punkt 68). `tsc
    --noEmit` rent.

    **Rullet ut 2026-08-10**, bruker bekreftet eksplisitt — migrasjon
    063 kjørt mot ekte `teecup_db`, begge containere bygget/restartet
    rent, ingen konsollfeil. CI plukket opp pushen og kjørte grønt.

70. **Frontend-testinfrastruktur (Vitest) — 2026-08-10, se ADR-061.**
    Fortsettelse av robusthetslinjen: frontend-tester var eksplisitt
    utenfor omfang i både punkt 67 og 69. Bruker ba om å ta tak i det.

    Vitest satt opp (node-miljø, ingen komponenttester ennå). 30 tester
    i tre filer: `lib/geo.test.ts` (GPS-avstand/retning, fasit-
    forankret mot kjente geodetiske verdier), `lib/offline-queue.
    test.ts` (regresjonsvern for `resourceKey`-fiksen fra punkt 69 --
    beviser eksplisitt at to ulike hull som deler samme URL ikke lenger
    blander sammen versjonsnummer, med `fake-indexeddb` som ekte
    IndexedDB-implementasjon), `lib/ny-runde/formats.test.ts`
    (fullstendighetssjekk på tvers av format-listene -- fanger opp det
    TypeScript sin `as`-cast i `FORMAT_MAP` IKKE garanterer).

    To reelle funn underveis: (1) prosjektet bruker pnpm, ikke npm --
    et første forsøk med `npm install` feilet (intern npm/arborist-
    krasj mot en pnpm-strukturert `node_modules`), ingen skade, løst
    ved å bruke riktig verktøy. (2) `frontend/pnpm-workspace.yaml` var
    ignorert i `.gitignore` (arv fra V0-sandbox-malen) -- fila fikk nå
    reelt CI-relevant innhold (`allowBuilds: sharp: true`), og ville
    latt CI feile stille på `[ERR_PNPM_IGNORED_BUILDS]` hver gang uten
    å bli sporet i git. Rettet.

    Egen, lettvekts CI-workflow (`.forgejo/workflows/frontend-tests.
    yml`) -- INGEN Docker-utenfor-Docker, siden denne suiten ikke
    rører database/containere. `actions/setup-node@v4` (Node 22) +
    `corepack enable` + `pnpm install --frozen-lockfile` + `pnpm test`.

    **Verifisert:** 30/30 grønt lokalt. Ingen produksjonskode endret.

71. **Åtte brukerrapporterte UX-funn — "Ny runde" og score-registrering
    — 2026-08-11, se ADR-062.** Bruker sendte åtte konkrete, skjermbilde-
    dokumenterte problemer. Delt i to spor: fire rettet direkte (reelle
    logikkfeil/tekstvalg), fire sendt via V0 (reelt interaksjonsdesign).

    **Direkte:** `round-card.tsx` sin "Hull"-celle viste alltid planlagt
    antall hull, aldri faktisk spilt, selv for tidlig avsluttede
    "Fullført"-runder — rettet (`played < round.holes` avgjør nå om
    avviket vises, uansett status). Putt-avstand-knappenes etiketter
    endret fra et blandet "&lt;Xm ... 8m+"-mønster til konsekvent
    "X-Ym" (kun visningstekst, ikke backend-kontrakten) — bekreftet via
    git-historikk at dette var Claude-forfattet, ikke V0. "Se feed" på
    dashbordet endret til "Se venneaktivitet". @-tagging av medspillere
    i feeden vurdert og UTSATT (egen fremtidig funksjon) — anbefalt
    personvern-begrensning (kun faktiske relasjoner) og ekte søkbar
    nedtrekksliste (ikke fritekst-tolkning) notert til den runden.

    **Via V0** (`tee-cup (9).zip`, kun `delivery/`-mappen tatt inn, diffet
    mot live-treet først): ny-runde steg 2 sin "Antall hull"/"Avanserte
    handicap-innstillinger" flyttet FØR formatrutenettet (var sist, lett
    oversett). Score-registreringens "detaljer"-steg delt i "Retning"/
    "Detaljer per hull"-seksjoner + en ny `ScrollFade`-hjelpekomponent
    (bunn-fade + nedoverpil, ResizeObserver, auto-skjuler ved bunn) --
    løser at brukere ikke visste de måtte skrolle. Anywayslag endret fra
    tall-rutenett til +/−-stepper (samme mønster som Chip/Bunker/
    Straffeslag nå). Netto par-markør (oransje prikk + kontrastring +
    tekstlig forklaring, tilgjengelig uten fargesyn) på Slag-knappene,
    kan opptre samtidig med "valgt"-tilstand. Putter-velgeren fikk egen
    oransje aksent + flagg-ikon for å skille den fra Slag-velgeren.

    **Verifisert:** `tsc --noEmit` rent. Full scratch-stack (DB, MinIO,
    API, frontend — ekte produksjonsbuild). Ekte testbruker, egen bane
    opprettet via API med HCP 24 for å garantere synlig netto-par-
    markør. Browserverifisert (mobil viewport, lys+mørk modus): ny
    rekkefølge, seksjonsdeling, scroll-hint (bekreftet både synlig og
    auto-skjult), netto-par-prikk alene og kombinert med valgt-tilstand,
    Putter-aksent, nye putt-avstand-etiketter. Ingen konsollfeil.
    Scratch-miljøet ryddet opp fullstendig, `teecup_db` bekreftet uendret.

    **Rullet ut 2026-08-11**, bruker bekreftet eksplisitt ("Kjør på") —
    `docker compose build teecup_frontend && up -d`, ren omstart,
    bekreftet live på `teecup.golf`, ingen konsollfeil utover den
    harmløse PWA-infomeldingen.

    **Oppfølging samme dag — punkt 5 (ChoiceRow) ikke løst.** Bruker
    rapporterte at "Avstand første putt" fortsatt viste 5+1 i stedet for
    3+3. Root cause: `ChoiceRow` (delt komponent) brukte `flex flex-wrap`
    fremfor et fast rutenett, pakket knapper etter tekstbredde i stedet
    for et forutsigbart antall. Rettet til `grid grid-cols-3` (samme
    mønster `NumberPicker` allerede brukte). Visuelt uendret for de fire
    andre `ChoiceRow`-bruken (alle har 3 valg fra før). Browserverifisert
    lys+mørk. Committet (`17e4793`), ikke deployet ennå.

72. **@-tagging av medspillere/venner i runde-feeden — 2026-08-11, se
    ADR-063.** Bakt inn i FEATURE_BACKLOG.md som egen sak under
    ADR-062, bruker ba om å sette den i gang.

    Migrasjon 064: `round_message_tag`/`round_message_comment_tag` (to
    parallelle tabeller, samme mønster som `round_message_reaction` vs.
    `message_reaction`). Kun FAKTISKE relasjoner er taggbare (rundens
    lenkede deltakere UNION avsenderens venner) — aldri fritekstsøk i
    hele brukerbasen, håndhevet både i nytt søkeendepunkt
    (`GET /rounds/{id}/taggable-people`) og server-side ved innsending.
    Tag-posisjon lagres som tegn-offset inn i den (aldri redigerbare)
    body-teksten — trygt, teksten kan aldri gå ut av synk. Ugyldige tags
    forkastes stille, hele innlegget avvises aldri av den grunn. Varsel
    (`type="round"`, gjenbruker eksisterende kategori) til hver gyldig
    tagget person.

    **Verifisert:** 8 nye pytest-tester
    (`tests/test_message_tags.py`) — kandidatliste, søkefilter, gyldig
    tag + varsel bekreftet, tre typer ugyldig-tag-forkastelse. Alle 31
    backend-tester grønne. `teecup_db` uendret.

    **Frontend — samme dag, egen-implementert.** Frontend var
    opprinnelig sendt som eget V0-prompt, men bruker gikk tom for
    V0-credits ("Kan du gi promptet til deg selv, og gjøre det beste
    ut av det?") — eksplisitt, avgrenset unntak fra prosjektets
    stående "frontend via V0"-konvensjon for akkurat denne funksjonen.
    `frontend/lib/mentions.ts` (ren offset-/diff-logikk: @-deteksjon,
    innsetting, diff-justering av eksisterende tags ved redigering,
    tekst-segmentering — 15 enhetstester) +
    `frontend/components/mention-input.tsx` (`MentionTextarea` med
    debouncet autocomplete-dropdown + tastaturnavigasjon, `TaggedText`
    som rendrer tagger som lenker til `/my-friends/{id}`). Koblet inn i
    `round-messages.tsx` (innlegg), `post-engagement.tsx` (kommentarer
    — delt komponent mellom FIRE meldingssystemer, `roundId`/`tags`
    gjort valgfrie for å ikke bryte `feed.tsx`/`public-tournament.tsx`/
    `team-chat.tsx`, hvorav de to siste bevisst IKKE får tagging) og
    `feed.tsx` (aggregert `/my-feed`-visning). Fant og rettet et hull
    underveis: backendens `GET /feed` manglet `tags` i `FeedEntryOut` —
    kun `list_round_messages`/kommentar-endepunktene hadde det fra
    før, selv om `/my-feed` var nettopp det opprinnelige eksempelet
    ("Fredrik i farta") som motiverte hele funksjonen.

    **Verifisert (frontend):** `tsc --noEmit` rent, 45/45 vitest
    grønt. Full scratch-stack (DB, MinIO, API, frontend — ekte
    produksjonsbuild). To ekte testbrukere (venn-relasjon + felles
    runde) browserverifisert: autocomplete-dropdown i både innleggs-
    og kommentar-komposereren, tag rendret som lenke i selve
    rundevisningen OG i aggregert feed, varsel bekreftet opprettet
    (`GET /notifications`), personvern-scoping bekreftet i selve UI-et
    (skriver "@" alene → kun faktisk relasjon tilbys, aldri seg selv
    eller en fremmed), lys+mørk modus. Ingen konsollfeil. Scratch-
    miljøet ryddet fullstendig opp, `teecup_db` bekreftet uendret før
    og etter.

    Org-turneringenes "Banter Board" (`message`/`message_comment`)
    fikk IKKE samme funksjon — separat system, fortsatt bevisst
    utenfor omfang.

    **Rullet ut 2026-08-11**, bruker bekreftet eksplisitt ("ja").
    Migrasjon 064 kjørt mot ekte `teecup_db` (kun nye tabeller, ingen
    endring av eksisterende), deretter `docker compose build teecup_api
    teecup_frontend && up -d` — samme kommando ryddet også inn den
    tidligere upubliserte ChoiceRow-fiksen (`17e4793`). Ren omstart,
    ingen feil i containerloggene, ingen konsollfeil på
    `https://teecup.golf/logg-inn` etter omstart.

73. **Fiks: ScrollFade-scrollhintet (fra ADR-062 punkt 6) hadde to
    reelle feil — 2026-08-11, se oppfølgingsnotatet i ADR-062.**
    Bruker sendte skjermbilde: "Den sprettende ned-pilen fungerer
    ikke. Den ligger OPPÅ en annen nedpil."

    To separate rotårsaker i samme `ScrollFade`-komponent
    (`round-detail.tsx`): (1) `ResizeObserver` observerte scroll-
    beholderen i stedet for innholdet — beholderens boksstørrelse er
    fast, så observeren fyrte aldri ved stegbytte i veiviseren, og
    hintet virket dermed rett og slett ikke på steg med reelt
    overflow-innhold (som Retning-steget). Rettet ved å observere en
    egen `contentRef`-div rundt innholdet i stedet for beholderen selv.
    (2) Fade-/piloverlayet (absolutt posisjonert, siste 64px av
    scroll-området) hadde ingen garanti mot å dekke ekte innhold —
    på Retning-steget landet "Kort"-knappens eget `ArrowDown`-ikon i
    akkurat den sonen, to piler oppå hverandre. Rettet med en usynlig
    64px-buffer etter innholdet (`hasMore`-utregningen trekker fra
    samme høyde for å unngå falske positiver på kort innhold).

    **Verifisert:** Full scratch-stack, testbruker med fullt
    kølle-utvalg for å reprodusere nøyaktig samme rutenett som i
    brukerens skjermbilde. Bekreftet: hintet vises (`opacity: 1`) når
    steget faktisk overflower, forsvinner (`opacity: 0`) ved reell
    bunn, "Kort" fullt synlig og klar av overlay-sonen ved skrolling,
    lys+mørk, ingen konsollfeil. `tsc --noEmit` rent, 45/45 vitest
    grønt. `teecup_db` urørt (ren frontend-endring). Committet
    (`ee91d03`).

    **Rullet ut 2026-08-11**, bruker bekreftet ("ja"). Kun
    `teecup_frontend` bygget og restartet (ren frontend-endring, ingen
    migrasjon). Ingen feil i containerloggene, ingen konsollfeil på
    `https://teecup.golf/logg-inn` etter omstart.

74. **Statistikk: ekspandert trekkspill, spilt-til-HCP, omstrukturert
    /my-rounds/stats — 2026-08-12.** Tre separate tilbakemeldinger fra
    bruker etter å ha sett gjennom statistikksidene med skjermbilder.

    **round-stats.tsx:** trekkspillseksjonene starter nå ekspandert.
    Reverserer en eksplisitt tidligere beslutning (2026-07-25, "trekk-
    spillet skal vises sammenslått") — kommentaren i koden oppdatert til
    å forklare hvorfor, ikke bare strøket.

    **Dashbordets "HCP nå"** viste tidligere det manuelt satte tallet i
    profilen (`handicap_index`) — brukeren ville ha "spilt til"-HCP, dvs.
    `computed_handicap_index` (allerede beregnet og eksponert på
    `/auth/me`, brukt av Konto-sidens "Faktisk HCP (beregnet)"-kort, bare
    ikke koblet inn på dashbordet). Historikk-sparklinen filtreres nå
    også til kun `source="computed"` — den blandet tidligere manuelle og
    beregnede punkter i samme linje.

    **`/my-rounds/stats` fikk tre endringer:**
    - Alle "vs. forrige periode"-deltaer (pil+tall+pp) er nå synlig
      selvforklarende. Teksten fantes tidligere kun som skjermleser-tekst
      — seende brukere så bare et ikon og et tall, ingen kontekst for hva
      det ble sammenlignet mot. Bruker: "Selv ikke jeg... forstår helt
      hva som menes."
    - Greentreff (GIR) og Innspill splittet i to separate kort (GIR er en
      beregnet andel, Innspill er en retningsbeskrivelse — samme prinsipp
      som round-stats.tsx sin eksisterende splitt mellom "Greentreff
      (GIR)" og "Bom-retning på green"). Utslag flyttet til å vises
      først. Redning og "Til par: med vs. uten" flyttet ned under Annet.
    - Ny stolpegraf med glidende snitt-trendlinje (snitt til par per
      runde, eldste runde til venstre) — tidligere bevisst utsatt ("for
      lite rundehistorikk til å vise noe meningsfullt", se punkt 2026-
      07-28 tidligere i denne loggen), bygget nå som brukeren har nok
      fullførte runder. Backend: `RoundStatsSummary` fikk et nytt
      `round_series`-felt (kronologisk snitt-til-par per fullført runde
      i valgt tidsvindu, sortert eldste først).

    **Verifisert:** `tsc --noEmit` rent, 45/45 vitest, 31/31 backend-
    tester (inkl. `round_series`-feltet, kjørt mot en scratch-database).
    Full scratch-stack med seks syntetiske runder (varierende dato og
    resultat, for å faktisk se en trend) browserverifisert lys+mørk:
    HCP-bytte bekreftet (computed 5,3 vist i stedet for manuelt satt
    15,0), ny kortrekkefølge, splittet GIR/Innspill, synlige delta-
    bildetekster på et vindu med reell forrige-periode-sammenligning,
    graf med trendlinje, trekkspill ekspandert på rundesiden. Ingen
    konsollfeil. `teecup_db` urørt gjennom hele verifiseringen.

    **Rullet ut 2026-08-12**, bruker bekreftet ("Jeg bekrefter"). Ingen
    migrasjon (kun et nytt beregnet felt i et eksisterende API-svar) --
    `docker compose build teecup_api teecup_frontend && up -d`. Ren
    omstart, ingen feil i containerloggene, ingen konsollfeil på
    `https://teecup.golf/logg-inn` etter omstart.

75. **Statistikk: lineær regresjon i stedet for glidende snitt, trend på
    alle variabler — 2026-08-12.** Oppfølging til punkt 74, samme dag.
    Bruker: "Trendlinjen din viser jo egentlig bare det samme som
    stolpediagrammet" — korrekt, et 3-runders glidende snitt over så få
    punkter følger bare stolpene med én runde forsinkelse, smoother
    ingenting reelt.

    Presenterte tre alternativer via AskUserQuestion (lineær regresjon /
    eksponentielt glidende snitt / kumulativt løpende snitt) FØR noe ble
    bygget, jf. brukerens eksplisitte "Gi meg forslag før vi
    implementerer". Bruker valgte lineær regresjon (minste kvadraters
    metode) — en rett linje, stigningstall = "endring per runde", vist i
    bildeteksten (f.eks. "+7 pp per runde"). Visuelt en helt annen form
    enn stolpene, løser selve klagen.

    Bruker ba samtidig om trend på ALLE variabler. Lagt til stolpe+trend
    for Fairwaytreff, Greentreff (GIR), Putt/18, Én-putt, Chip/runde,
    Bunkerslag/runde, Straffeslag/runde og Anywayslag/runde, hver i sitt
    eksisterende kort. Scrambling/sand save unntatt (brukervalg via
    samme AskUserQuestion): for få kvalifiserte hull per runde til et
    per-runde-tall, vises i stedet som én kumulativ linje.

    Backend: `round_series` flyttet fra `RoundStatsSummary` til
    `RoundStatsWindowSummary`-nivå, utvidet med per-runde-tall for alle
    variabler pluss kumulativ scrambling/sand save. Fanget og rettet en
    enhets-bug under egen scratch-verifisering (før commit): scrambling/
    sand save ble først bygget på 0–1-skala i stedet for samme 0–100-
    skala som resten av API-et.

    **Verifisert:** `tsc --noEmit` rent, 45/45 vitest, 31/31 backend-
    tester. Full scratch-stack med åtte syntetiske runder (bevisst
    støyende fremgang) browserverifisert lys+mørk: regresjonslinjen
    bekreftet rett og visuelt distinkt fra stolpene på alle variabler,
    riktig fortegn/farge på stigningstall, ingen dobbel enhet i
    bildetekstene, kumulative linjer konvergerer som forventet. Ingen
    konsollfeil. `teecup_db` urørt.

    **Rullet ut 2026-08-12**, bruker bekreftet ("Jeg bekrefter"). Ingen
    migrasjon -- `docker compose build teecup_api teecup_frontend &&
    up -d`. Ren omstart, ingen feil i containerloggene, ingen konsollfeil
    på `https://teecup.golf/logg-inn` etter omstart.

76. **Statistikk: y-akse-benevnelser + verdi i begge ender på
    trendgrafene — 2026-08-12.** Oppfølging til punkt 75, samme dag.
    Bruker: "Y-axen må ha verdi i begge ender" + ønske om benevnelse på
    y-aksen.

    `BarTrendChart`/`CumulativeTrendLine` (`rounds-stats-summary.tsx`)
    fikk en ny `yAxisLabel`-prop -- rendres som EKTE HTML-tekst over hver
    graf (ikke roterte SVG-glyffer), slik at den skalerer med brukerens
    skriftstørrelse i stedet for å bli uleselig ved zoom
    (tilgjengelighetsregelen i CLAUDE.md). Benevnelser: "Slag til par"
    (Utvikling), "%" (alle prosentgrafer: Fairwaytreff, Greentreff,
    Én-putt, Scrambling/Sand save), "Putt" (Putt/18), "Antall" (Chip/
    Bunkerslag/Straffeslag/Anywayslag).

    `BarTrendChart` viste tidligere kun verdi-etikett på SISTE (nyeste)
    stolpe. Lagt til samme etikett på FØRSTE stolpe også (dempet stil,
    samme mønster som `CumulativeTrendLine` allerede brukte for sine
    start-/sluttpunkt-etiketter) -- grafen kan nå leses uten en egen
    tallskala i begge ender, ikke bare ved siste punkt.

    **Verifisert:** `tsc --noEmit` rent, 45/45 vitest.

77. **Avstand til mål (rangefinder) via GolfAPI.io — Tjøme Golfklubb,
    2026-08-12.** Ny, tredje banekilde (`app/golfapi_client.py`,
    `app/golfapi_cache.py`) for baner utenfor TeeOffs dekning. Full
    detalj og alle beslutninger i **ADR-064**, kort oppsummert her:

    Brukeren fikk et GolfAPI.io-token (20 kall) og ba om avstands-
    visning til grønn på baner TeeOff ikke dekker -- konkret testet mot
    Tjøme Golfklubb, som brukeren bekreftet IKKE finnes i TeeOff. Live
    validert mot ekte GolfAPI FØR noe ble bygget (søk, fullt scorekort,
    167 koordinatpunkter for Tjøme -- grønn front/midt/bak, hindringer,
    tee-punkter), 2,3 av 20 kall brukt på research.

    Migrasjon 065: delt, globalt cache-lag (`golfapi_course`/`_hole`/
    `_tee`/`_coordinate`) -- hentes KUN én gang noensinne per fysisk
    bane (GolfAPIs avtale tillater eksplisitt permanent caching), via
    én sentral chokepoint-funksjon som all import-kode må gå via.
    `course_source` utvidet med `'international'` (org-turneringer);
    `personal_course` fikk en ny `external_golfapi_course_id`-kolonne
    (frittstående runder) -- bekreftet med bruker at BEGGE skal
    støttes. Nye endepunkter: `international-search`/`-import` i både
    `courses.py` (org) og `rounds.py` (personal), samt
    `GET /rounds/{id}/holes/{n}/target-points` (returnerer RÅ
    koordinater, aldri en ferdigregnet avstand -- klienten Haversine-
    regner selv mot spillerens ferske GPS-posisjon, samme
    `frontend/lib/geo.ts`-funksjon som ADR-048).

    v1-visning: ren tall-/tekstvisning ("142 m front/151 m midt/163 m
    bak"), intet kart -- bekreftet med bruker (AskUserQuestion), null
    ekstra Mapbox-kostnad. En fremtidig freemium-idé (gratis=tall,
    betalt=interaktivt kart) ble foreslått av bruker og notert i
    ADR-064, ikke bygget (ingen betalingsinfrastruktur finnes).

    Frontend: søk-og-koble-UI lagt til i `tournament-program.tsx`
    (org-siden, to kallsteder) og `round-detail.tsx` (frittstående
    runder, "Fant ikke banen? Søk internasjonalt"-fallback under egen
    bane-søk).

    **Rangefinder-visningen, samme dag:** V0-prompten
    (`v0-prompt-rangefinder.md`) ble kjørt av bruker (zip 10,
    `Temp-uploads/tee-cup (10).zip`) -- `components/target-distance.tsx`
    kom tilbake nøyaktig som spesifisert (props-drevet, ingen egen GPS-/
    fetch-logikk, clubhouse-paletten korrekt brukt). Koblet inn av
    Claude via en ny wrapper, `components/hole-target-distance.tsx`
    (henter `target-points`, kjører `watchPosition` -- samme
    `enableHighAccuracy`/`maximumAge: 1000`/grasiøs-degraderings-mønster
    som `map-point-picker.tsx` allerede bruker for slagmåling, ADR-048
    tillegg 2026-08-08 del 2 -- og Haversine-regner selv), montert i
    `ScoringWizard` sin hull-header i `round-detail.tsx`. Viser INGEN
    rad i det hele tatt for baner uten GolfAPI-koordinatdata (det store
    flertallet av runder) -- ikke en "ingen data"-lapp på hver eneste
    scorekort-åpning.

    **Reell driftsfeil fanget under denne utrullingen:**
    `TEECUP_GOLFAPI_TOKEN` var satt i `.env`, men `docker-compose.yml`
    sin `teecup_api`-tjeneste videreførte den aldri til containeren --
    endepunktene ville svart "ikke konfigurert" i produksjon uansett
    hva `.env` sa. Lagt til i miljø-blokken, samme mønster som de andre
    `TEECUP_*`-hemmelighetene.

    **Verifisert:** migrasjon 065 kjørt mot automatisert scratch-
    database (`scripts/run_backend_tests.sh`), 31/31 eksisterende
    backend-tester uendret grønne. Egen scratch-runde med de FAKTISKE
    Tjøme-svarene som fixtures (monkeypatchet `golfapi_client`, for å
    ikke bruke flere av de knappe API-kallene under iterasjon):
    cache-chokepunktet bekreftet idempotent (henter GolfAPI nøyaktig én
    gang, aldri på nytt ved gjentatte kall), 18 hull + 4 tees + 167
    koordinater cachet korrekt, org-import ga riktig antall
    hull/tee_rating-rader, duplikat-import avvist i BEGGE modeller,
    target-points-oppslaget ga korrekte front/midt/bak-punkter. **Ett
    bevisst, siste ekte kall** mot GolfAPI (2 kall) bekreftet at hele
    kjeden -- inkl. `golfapi_client.py` sitt faktiske HTTP-kall, ikke
    bare cache-logikken -- fungerer mot den virkelige tjenesten.
    `teecup_db` bekreftet uendret gjennom hele verifiseringen
    (scratch-database+rolle droppet etterpå). Frontend: `tsc --noEmit`
    rent, 45/45 vitest.

    **Full-stack scratch-verifisering av rangefinder-koblingen**
    (separat scratch-database/API/frontend/MinIO-sett, GolfAPI-cachen
    forhåndsseedet fra de samme lagrede Tjøme-fixturene -- null nye
    API-kall): ekte innlogging, import av Tjøme via det virkelige
    `POST /personal-courses/international-import`-endepunktet (cache-
    treff, 0 kall), opprettet en runde, åpnet scoreførings-veiviseren
    for hull 1 i nettleseren (Chrome DevTools MCP, geolocation-
    emulering). Bekreftet visuelt i BÅDE lys og mørk modus: "Front
    118 m / Midt 132 m / Bak 144 m" (Midt uthevet), "● Live"-indikator,
    nærmeste hindring korrekt identifisert og vist ("Bunker (grønn)
    (front): 104 m"), ingen konsollfeil. `tsc --noEmit` rent, 45/45
    vitest. Scratch-database/rolle/containere ryddet opp etterpå,
    `teecup_db` bekreftet uendret (`\l`-sjekk).

    **Rullet ut 2026-08-12**, bruker bekreftet ("kjør" / "NÅ kan du
    kjøre på" etter å ha lastet opp V0-eksporten). Migrasjon 065 kjørt
    mot ekte `teecup_db` (additiv, ingen eksisterende data rørt) --
    `docker exec -i teeoff_db psql ... -f 065_golfapi_courses.sql`,
    deretter `docker compose build teecup_api teecup_frontend && up -d`
    (kjørt TO ganger denne runden -- første gang før rangefinder-
    komponenten/docker-compose-fiksen var klare, brukeren stanset
    bevisst midtveis for å laste opp V0-eksporten først, andre gang
    etter). Rene containerlogger begge ganger, ingen konsollfeil på
    `https://teecup.golf/logg-inn` etter siste omstart. ~15,5 av 20
    GolfAPI-kall gjenstår.

    **Tillegg 2026-08-13 -- den PRIMÆRE "Ny runde"-veiviseren manglet
    GolfAPI-søket, og en reell ruting-bug ble funnet og fikset.**
    Brukeren spurte "hvor, nøyaktig, kan jeg se dette implementert?" --
    undersøkelsen avdekket at søk-og-koble-UI-en fra 2026-08-12 KUN var
    koblet inn i "endre bane på en allerede opprettet runde"-skjemaet
    (`ChangeCourseForm`, round-detail.tsx), IKKE i den faktiske
    `/my-rounds/new`-veiviseren (`components/ny-runde/step1-course-
    time.tsx` + `lib/ny-runde/api.ts`) -- en helt separat komponenttre
    brukeren faktisk møter FØRST. Lagt til der også: ny
    `"international-search"`-delstilstand (`Sub1`-typen),
    `InternationalSearch`-komponent (samme klubb->baner-todelte søk som
    de to andre stedene, i `--nr-*`-designtokens), `searchInternationalClubs`/
    `importInternationalCourse` i `lib/ny-runde/api.ts`.

    **Ekte bug fanget under nettleserverifisering av DENNE
    integrasjonen** (ikke funnet i forrige runde fordi kun POST
    `/personal-courses/international-import` ble reelt HTTP-testet der,
    aldri GET `/personal-courses/international-search`): 500-feil,
    `asyncpg.exceptions.DataError: invalid input for query argument $1:
    'international-search' (invalid UUID...)`. Årsak: FastAPI matcher
    ruter i REGISTRERINGSREKKEFØLGE -- `GET /personal-courses/
    {personal_course_id}` (allerede eksisterende, generisk) var
    registrert FØR den nye, mer spesifikke `GET /personal-courses/
    international-search`, så `{personal_course_id}` fanget grådig opp
    "international-search" som en (ugyldig) UUID. Samme fellgruve
    fantes IKKE på org-siden (courses.py) fordi det der ikke finnes noen
    bar `/courses/{course_id}`-rute å kollidere med (kun `/{course_id}/
    tees` og `/{course_id}/holes`, annen path-form). Fikset ved å flytte
    de to nye endepunktene til FØR `get_personal_course` i
    `rounds.py` -- lærdom kommentert direkte i koden for å unngå samme
    feil neste gang en literal-path-rute legges til ved siden av en
    eksisterende parameterisert rute.

    **Verifisert på nytt, fullt ut, etter fiksen:** frisk scratch-stack,
    ekte innlogging, full klikk-gjennom-flyt i den FAKTISKE "Ny
    runde"-veiviseren (Egen bane -> "Fant ikke banen? Søk
    internasjonalt" -> søk «Tjøme» -> Tjøme Golfklubb -> importer ->
    landet korrekt på "Bane & tid"-steget med "Tjøme Golfklubb – Bane",
    4 utslag), ingen konsollfeil, bekreftet via nettverksfane at søket
    nå returnerer 200 med reelt klubbtreff (ikke lenger 401/500).
    `tsc --noEmit` rent, 45/45 vitest, 31/31 backend-tester. Scratch-
    ressurser ryddet opp, `teecup_db` bekreftet uendret.

    **Rullet ut 2026-08-13**, samme økt. `docker compose build
    teecup_api teecup_frontend && up -d`. Rene containerlogger, ingen
    konsollfeil på `https://teecup.golf/logg-inn` etter omstart.

    **Tillegg samme dag (del 3) -- feil skjerm.** Bruker sendte et
    skjermbilde fra ekte produksjon (hadde selv importert Tjøme og
    opprettet en runde) og pekte ut at avstandsindikatoren skulle vært
    synlig i HOVEDVISNINGEN (`PlayerHoleCards`, hull-navigator-kortet
    over spillerlisten -- det brukeren faktisk ser når de går ut på
    hullet), ikke inne i scoreførings-veiviseren (dit den ble montert
    2026-08-12 -- feil skjermøyeblikk, en spiller åpner den veiviseren
    ETTER hullet er spilt, ikke før tilnærmingsslaget). Ba også om mer
    bredde enn kompaktvarianten som satt der.

    `HoleTargetDistance` sin egen rad-wrapper (border/padding, tilpasset
    veiviser-headeren) fjernet -- kalleren styrer nå plassering/bredde
    selv. Flyttet fra `ScoringWizard`-headeren til `PlayerHoleCards`,
    rett under hull-navigatoren (Forrige/Hull N/Neste + hurtig-hopp-
    stripen) og over spillerkortene -- nøyaktig der brukeren pekte,
    `size="full"` i stedet for `"compact"`.

    **Verifisert:** ny scratch-runde (samme fixture-cachede Tjøme-data,
    0 nye API-kall), ekte innlogging, ekte runde, geolocation-emulering
    + `initScript`-injisert `watchPosition`-shim (persisterer over en
    reload, ulikt en engangs `evaluate_script`-injeksjon). Bekreftet
    visuelt lys+mørk: full-bredde "Til grønn"-kort rett under hull-
    navigatoren, front/midt/bak + hindringslinje + live-indikator,
    ingen konsollfeil. `tsc --noEmit` rent, 45/45 vitest. Scratch-
    ressurser ryddet opp, `teecup_db` uendret.

    **Rullet ut 2026-08-13**, samme økt. Ren frontend-endring (ingen
    backend/migrasjon rørt) -- `docker compose build teecup_frontend &&
    up -d teecup_frontend`. Ingen konsollfeil på
    `https://teecup.golf/logg-inn` etter omstart.

    **Tillegg samme dag (del 4) -- strømsparing.** Bruker rapporterte
    høyt batteriforbruk nå som GPS-en er aktivert. To justeringer i
    `hole-target-distance.tsx`, foreslått med tradeoffs FØR bygging (jf.
    "Gi meg forslag før..."-arbeidsmåten): (1) `maximumAge` økt fra
    1000ms til 4000ms -- en golfer beveger seg ~1,5 m/s, oppdatering
    hvert 4. sekund er umerkelig men ber GPS-brikken om et ferskt fix
    langt sjeldnere. (2) `watchPosition` pauses nå helt når fanen/appen
    ikke er synlig (`document.visibilitychange`) -- ingen vits å holde
    GPS aktiv for en skjerm ingen ser på (skjerm låst, annen app i
    forgrunnen) -- og gjenopptas automatisk når brukeren kommer tilbake.
    `enableHighAccuracy` bevisst UENDRET -- lav nøyaktighet ville gitt
    titalls meters feilmargin, for upresist til at front/midt/bak-
    tallene gir mening.

    **Verifisert:** ny scratch-runde (samme fixture-cachede Tjøme-data),
    ekte innlogging/import/runde, geolocation-emulering +
    `initScript`-injisert `watchPosition`/`clearWatch`-spion (telte
    faktiske kall). Bekreftet i nettleseren: `document.hidden = true` +
    `visibilitychange` → `clearWatch` kalt umiddelbart, visningen gikk
    til "Ikke live — sist målte posisjon" (siste kjente tall beholdt,
    ikke nullstilt); `document.hidden = false` + `visibilitychange` →
    `watchPosition` kalt på nytt (kall-telleren gikk fra 1 til 2), "●
    Live" gjenopprettet. `tsc --noEmit` rent, 45/45 vitest, ingen
    konsollfeil. Scratch-ressurser ryddet opp, `teecup_db` uendret.

    **Rullet ut 2026-08-13**, samme økt. Ren frontend-endring --
    `docker compose build teecup_frontend && up -d teecup_frontend`.
    Ingen konsollfeil på `https://teecup.golf/logg-inn` etter omstart.
