teecup/CHANGELOG.md
Erol Haagenrud b704a9c378 Dokumenter utrulling av @-tagging + ChoiceRow-fiks
Migrasjon 064 kjørt mot teecup_db, api+frontend bygget og restartet,
bruker bekreftet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 10:28:28 +02:00

671 KiB
Raw Blame History

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_scores 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_selfs 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 filendringenteeoff_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 apiteeoff_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 SELECTtournament 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.pyGET /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.sqllink_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 0100-skjemafelt til 01. 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_apihttp://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=trueteam_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 windows 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<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 (19 + 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_hole1 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_FAILEDscoring.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 nullhandicap_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 ScoreMarks "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 ScoreMarks "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. NumberPickers 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-10min-h-11, EditRoundPanels lukk-ikon size-8size-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<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-collapseborder-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/60s 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åkets "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_messageRoundOut (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 windows 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_participants 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-installasjonsoppfordringlib/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_handicapformat-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→kategorivelgeralle 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 DecidedViews "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.mds "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): mains 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-6pt-6, sm:py-8sm: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 (AddParticipantForms 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.

  2. 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.

  3. 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.

  4. 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).

  5. Oppfølgingspunkt fra PWA-runden: offline-scoreregistrering er ALDRI browser-testet i praksisferdig 2026-07-28 (se status-punkt samme dag, "Turnering-scorekortets offline-flyt (ADR-028) FAKTISK browserverifisert"). Beholdt her kun for historikk.

  6. 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).

  7. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/ Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå.

  8. 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.

  9. 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.

  10. 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.

  11. 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.

  12. 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.

  13. Å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 åtteRETTET 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.tsxs TeamFlightBoard) og High-low-high (round-leaderboard.tsxs 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.

  14. 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.

  15. 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.
  16. 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.tsxs AddSlotForm (lagturneringer) og individual-tournament-detail.tsxs 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.tsxs 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.

  17. 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.

  18. 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.
  19. 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.

  20. 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.

  21. 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'.

  22. 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.

  23. "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". OwnSearchs 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 OwnCreateSteps 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). CourseLists onUseAsTemplate gjort valgfri på samme måte som OwnSearchs 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å.

  24. 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.

  25. 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.

  26. 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: 0169 av 656 totalt) mens "TeeCup"- ordmerket er seks separate bokstav-grupper (x: 179407) -- 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.

  27. 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.

  28. 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: strPublicRoundOut (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.

  29. 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.

  30. 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_atcreated_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.

  31. "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_atcreated_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-useEffecten (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.

  32. 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.

  33. 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.

  34. 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.

  35. "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.

  36. 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_holeround_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_ORIGINs 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_appteecup_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.

  37. 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.

  38. 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-inns 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.

  39. 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.

  40. /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 /roundsPOST .../sidesPOST .../participantsPATCH .../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.tsxs egne kommentarer) -- la til pb-28<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_dbs 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_apis 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).

  41. /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_dbs 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.

  42. 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_apis 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.

  43. 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_frontends 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.

  44. 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.mds 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.tsxs 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 ScoringWizards 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).

  45. 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.mds "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:

    • ScoringWizards 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.

  46. 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 PlayerCards 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.

  47. 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).

  48. 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.

  49. 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.

  50. 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.

  51. 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.

  52. 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_dbs 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_dbs ACL bekreftet uendret før/etter, verifisert live på teecup.golf uten konsollfeil.

  53. 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_dbs 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.

  54. 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_dbs 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.

  55. 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_dbs 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.

  56. 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_caddys 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_dbs 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å.

  57. 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 /velkommens "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, /velkommens 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_dbs ACL bekreftet uendret.

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

  58. 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.tsxs 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-*). BottomNavs bevisst palett-nøytrale hvite flate forblir hvit i mørk modus også — eksisterende, bevisst V0-designvalg, ikke en regresjon. teecup_dbs 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.

  59. 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_dbs 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.

  60. 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.

  61. 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_dbs 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.

  62. 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_dbs 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.

  63. 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.

  64. 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_dbs 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.

  65. 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_dbs 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.

  66. 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.

  67. 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_dbs rolleoppsett og fravær av gjenglemte scratch-databaser bekreftet uendret etter kjøring.

  68. 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.

  69. 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.

  70. Å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 "<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å.

  71. @-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.