Nytt:
app/routers/courses.py — enkel bane-CRUD (GET/POST /orgs/{id}/courses), fant og tettet et reelt hull: SessionCreate.course_id var påkrevd, men ingen vei fantes til å skaffe én
components/tournament-program.tsx + rute /tournaments/[id]/program — tidslinje over økter, opprett-skjema med bane-type-ahead og avanserte handicap-brytere
Fanerad lagt til i både roster- og program-skjermen så du kan bevege deg mellom dem
To reelle feil rettet før integrering:
V0-promptet mitt ba om ett generisk "Scramble"-format, men databasen/motoren krever scramble_2/scramble_4 som atskilte verdier — rettet til to segment-knapper
Verifiserte allowance_override-JSON-formen eksakt mot parse_allowance_config (typet combined/per_player + 0–1-brøk, ikke flat prosent) — bekreftet med en ekte rundtur i scratch, ikke bare lest fra koden
Verifisert: courses opprettet+listet, kryss-org-isolasjon, økt med klokkeslett, økt med scramble_4+full handicap-override-rundtur, gammel "scramble"-verdi korrekt avvist, test_isolation.sql 12/12, ekte typesjekket produksjonsbuild (samme Dockerfile som deployes).
47 KiB
CLAUDE.md — arbeidsinstruks for TeeCup
Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber.
Autoritative kilder (les før du gjør noe)
ARCHITECTURE_DECISIONS.md— hva som er bestemt og hvorfor (ADR-001…018). Fasit.FEATURE_BACKLOG.md— hva som gjenstår, hva som er utsatt, hva som mangler.- Endres en beslutning: legg til en ny ADR, ikke slett historikk. Hold begge filene oppdatert når noe avgjøres.
Sikkerhetsregler (ufravikelige)
- Rør ALDRI
teeoff-databasen eller den ekteteecup_dbuten at brukeren eksplisitt har bekreftet det i samme økt. Test alltid migrasjoner mot en egen scratch-database først, og rydd opp etterpå. - Vis planen (hvilke kommandoer, mot hvilken database) FØR du kjører noe som skriver, migrerer eller sletter. Vent på bekreftelse.
- Hemmeligheter (passord, secrets) bor i
.env(filrettigheter 600), dekkes av.gitignore, committes aldri, og skrives aldri i klartekst i chatten eller i SQL-filer. Generer dem på serveren (openssl rand -base64 32). - Kjør appen som databaserollen
teecup_app(NOSUPERUSER, NOBYPASSRLS) — aldri somteeoff_admin/superuser i runtime.
Arkitektur-invarianter (ikke bryt uten en ny ADR)
- Tenant = organisasjon.
organization_idpå alle domenetabeller, håndhevet av RLS. App-koden setterapp.current_orgmedSET LOCALper transaksjon. - Verifiser at brukeren er medlem av organisasjonen FØR org-konteksten settes.
RLS stoler blindt på
app.current_org. - Egen innlogging (uavhengig av teeoff). Banedata hentes fra teeoff via lesende API, ikke delt database.
- v1 = nøyaktig to lag (Ryder Cup-format), håndhevet i app-laget. Match-modellen holdes generell (to sider) så knockout/flere lag kan komme senere.
- Handicap-/matchlogikk skal ligge i
handicap_engine.py(rent, testet, uten db/API-avhengigheter). Allowances er konfig, ikke hardkodet. - Media (bilder/video) skal i objektlagring (MinIO), ikke i Postgres. Postgres holder bare metadata + nøkkel.
Arbeidsmåte
- Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før du går videre.
- Bruk git (remote: brukerens Forgejo). Commit i logiske steg med tydelige meldinger.
- Er du usikker på omfang eller en beslutning: spør heller enn å gjette.
Status (oppdater denne når ting endres)
Ferdig og verifisert:
-
Handicap-motor + tester (24/24, R&A-verifisert).
-
Skjema
001+ roller002+ scoring/blind draw003. Isolasjon bevist medtest_isolation.sql(RLS-oppførsel, ikke bare at skjemaet kjører). -
002 hadde en reell bug (psql interpolerer ikke
:'var'inne iDO $$...$$) — 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 medFOR UPDATE-lås),matches.py(matcher/ deltakere/blind draw-lås, ADR-013-synlighet push-down i SQL). Delt feiloversettelse iapp/errors.py, delte synlighetsspørringer iapp/blind_draw.py.main.pyer 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 viaparse_allowance_config, handicap beregnes icompute_and_store_side_handicapsrett 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 medFOR UPDATE-lås mot race og SAMMENHENGENDE-prefiks-regel for uferdige hull).app/team_authz.pyskilt ut framatches.py(delt medscoring.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-bryterenuse_handicap=false. Fant og fikset underveis:tournaments.pysinSessionCreatemangletscoring_modehelt (økter kunne aldri opprettes ihole_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_resultavviser nå 409 hvismatch.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 iapp/routers/auth.py+app/auth.py(request-link/verify-link/logout/me), ny migrasjon004_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_useropprettes 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 motapp_userpå 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 eksplisittWHERE email IS NOT NULL(samme klasse feil somhole_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— deltSTABLESQL-funksjonapp_current_org()gjørNULLIF(current_setting('app.current_org', true), '')::uuidi stedet for det rå uttrykket, brukt av alle 15 RLS-policyer (ALTER POLICY, 14org_isolation+org_self). Verifisert med 3 nye regresjonstester itest_isolation.sql(Test 10-12) OG ved faktisk å gjenskape original- buggen mot en ekte container (pool-størrelse 1, varm opp medorg_connection(), deretter/auth/mepå samme gjenbrukte tilkobling — gikk fra 500 til 200). Viktig presisering fra denne runden: fiksen gjør IKKE at/auth/mekan joineorganizationdirekte viaplain_connection()— det var en feilaktig antakelse i forrige runde.org_selfkrever fortsatt en MATCHENDEapp.current_orgfor å 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/meslår derfor opp hvert org-navn ett om gangen viaorg_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 enorganization-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, settapp.current_orgtil nøyaktig den via eksisterendeorg_connection(), sett innorganization-raden med samme id —org_selfs implisitteWITH CHECKblir 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 medinsufficient_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,smtplibviaasyncio.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.pysinSMTP_CONFIGUREDer 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): ekteteecup_dbopprettet (migrasjoner 001→005 kjørt permanent,test_isolation.sqlbestår),Dockerfile+docker-compose.yml(tjenesteteecup_api, joiner det eksisterendeteeoff_default-nettverket), Caddy-blokk lagt til i/opt/teeoff/deploy/Caddyfile. Ekte innlogging (magic-link → e-post → JWT-sesjon medSecure-cookie) verifisert ende-til-ende mot den live stacken.teeoff.noupåvirket gjennom hele prosessen. To reelle hendelser underveis, begge løst:- Caddy plukket ikke opp filendringen —
teeoff_caddysinCaddyfile-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 gangercaddy validate/caddy reload/admin-API/loadble kjørt (alle validerte/lastet den uendrede gamle filen, derav ingen feilmelding). Løst med en fulldocker restart teeoff_caddy(brukeren bekreftet — avvek fra planens "kun graceful reload, ingen omstart"-løfte, noen sekunders nedetid forteeoff.no). - Alvorlig nettverksalias-kollisjon (funnet RETT ETTER omstarten, da
ekte teeoff-trafikk som
/api/facilities?...med ekte klubb-slugs dukket opp iteecup_apisin logg):docker-compose.ymlsin service-nøkkel varapi:— SAMME nøkkel som teeoffs egetapi-servicenavn (docker-compose.prod.yml). Docker Compose registrerer nettverksalias basert på service-NAVNET (ikke barecontainer_name) på delte nettverk, så BEGGE containerne fikk aliasapipåteeoff_default— Caddysreverse_proxy api:8000i teeoff sin egen config kunne da tilfeldig treffe enten ekteteeoff_apiellerteecup_api. Rettet umiddelbart (stoppetteecup_apiførst for å hindre videre feilruting av ekte teeoff-trafikk, ga service-nøkkelen navnetteecup_apii stedet, gjenopprettet — bekreftet meddocker network inspectat aliasapinå KUN peker på ekteteeoff_api). Mindre driftslærdom: (a) jeg eksponerte ved et uhellTEECUP_SMTP_PASS/TEECUP_FROM_EMAILi 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.envpå nytt for en allerede kjørende container —docker compose up -d --force-recreatekreves 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.
- Caddy plukket ikke opp filendringen —
-
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_timeer UTLEDET i Python (scheduled_at + (sequence-1)*tee_interval_minutes, override vinner hvis satt) — aldri lagret per match. Full retrofit av ALLE 39 daværendeHTTPException(..., detail="norsk streng")-steder på tvers av 6 filer til en deltapp_error(status_code, code, message)-factory (app/errors.py) — responsformen er nå konsekvent{"detail":{"code":...,"message":...}}i hele API-et, verifisert med et sistegrep -rn 'detail="' app/som ga NULL treff. i18n:locale(nb/en) sendes av klienten vedrequest-link, styrer e-postmalen (ekte engelsk mal lagt inn iapp/email.py, ikke bare rørlegging) OG settes som en HELT NY brukerspreferred_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_datehar ligget i skjemaet siden migrasjon 001, men var ALDRI koblet tilTournamentCreate/Tournament-modellene — funnet som en naturlig bivirkning av å legge tilend_date. Verifisert grundig mot ferskteecup_scratch(001→006,test_isolation.sqlfortsatt 12/12): feilkode-form bekreftet på tvers av 5 filer (NOT_FOUND/LIMIT_REACHEDtournaments.py,DUPLICATEroster,NOT_AUTHENTICATEDuten cookie,NOT_ROSTERED_ON_TEAMmatches.py), tee_time-beregning bekreftet (10 min intervall → 08:00/08:10/08:20, override vinner,scheduled_at=nullgirtee_time:nullikke feil), full i18n-runde bekreftet (ny brukerlocale:"en"→preferred_locale:"en"; påfølgenderequest-linkfor samme bruker medlocale:"nb"skiftet e-postmalen men IKKE den lagrede preferansen, bekreftet via/auth/me). Kjørt mot ekteteecup_db2026-07-17 (bruker bekreftet eksplisitt i samme økt): migrasjonen kjørte rent,test_isolation.sqlfortsatt 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 oppTEECUP_DATABASE_URL, som barteecup_app- passordet innebygd i selve URL-en (i tillegg til at det SAMME passordet allerede lå rent iTEECUP_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 atTEECUP_DATABASE_URLvar é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 fraTEECUP_APP_PASSWORD, kun nøkkelnavnet — verdien aldri lest eller skrevet av meg).app/config.pyogapp/db.pybygger 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.ymlsinenvironment:-liste oppdatert tilsvarende. Krevde et fullt image-rebuild, ikke bare--force-recreate:DockerfilesinCOPY app/ app/bakes inn i imaget ved build-tid (ingen bind-mount i prod, i motsetning til scratch-verifiseringens engangscontainere) — en ren--force-recreategjenbrukte det GAMLE imaget og krasjet umiddelbart på den nå fjernedeTEECUP_DATABASE_URL. Rettet meddocker compose up -d --build --force-recreate. Verifisert ende-til-ende mot den live stacken: containeren boot-et rent (Application startup completei loggen, som krever en vellykketinit_pool()— asyncpg ville kastet og forhindret akkurat den logglinjen ved feil tilkoblingsparametre),/auth/meover ekte https ga et rent401 NOT_AUTHENTICATED(ikke 500/502),teeoff.noupåvirket (200gjennom hele omstarten, kunteecup_apirestartet — 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/#ff5722og rettet alle 6 forekomster iglobals.css. Fjernet@vercel/analytics(unødvendig på egen-hostet infra), fjernettypescript: { ignoreBuildErrors: true }(ekte typesjekk kjører nå), fjernet dødtpnpm.overrides-felt.frontend/.gitignoremanglet.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.mjssinrewrites()proxyer/auth/*//orgs/*//healthserver-side tilteecup_api— same-origin, ingen CORS, cookie uendret (full begrunnelse i ADR-016). Login-skjermet sender ektePOST /auth/request-link; ny/verify-side mottar?token=...fra e-postlenken (auto-verifiserer) eller viser et manuelt "lim inn koden"-felt.app/email.pyfikk en nyPUBLIC_BASE_URL- innstilling og sender nå en EKTE klikkbar lenke (koden beholdes som fallback). Reell fallgruve funnet og fikset ved containerisering: Next.js sinrewrites()løses ved BUILD-tid foroutput: "standalone", ikke ved container-oppstart — en runtime-e TEECUP_API_ORIGIN=...ble stille ignorert (proxy-kall feilet medECONNREFUSEDmotlocalhost:8000). Løst med en Docker build-timeARG TEECUP_API_ORIGINifrontend/Dockerfile, satt viadocker-compose.ymlsinbuild.args. Rullet ut live: nyteecup_frontend-tjeneste idocker-compose.yml. Caddy (teecup.teeoff.no, i det SEPARATEteeoff-repoet,/opt/teeoff/deploy/Caddyfile) endret fra å peke direkte påteecup_apitil å peke påteecup_frontend— samme stale-inode-oppførsel som containeriseringsrunden (gracefulreloadplukket IKKE opp endringen,/fortsatte å giteecup_apisin egen 404 i stedet for innloggingssiden til reload faktisk skjedde). Løst likt: fulldocker restart teeoff_caddy, brukeren bekreftet eksplisitt på forhånd.teeoff.noupåvirket gjennom hele omstarten. Verifisert med FAKTISK e-postlevering: ekte magic-link sendt til brukerens egen adresse overhttps://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 iteecup-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 reversertrewrites()-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.tsxfikk en kirurgisk patch (kun Wordmark flyttet til egen fil, som V0 selv hadde gjort — all egen fetch-/feilhåndteringslogikk urørt).dashboard.tsxsitt datalag skrevet om fra bunnen (V0 leverte kun mockuseState): henter/auth/mefor organisasjonsmedlemskap,/orgs/{id}/tournamentsper valgt org,POST /orgs/POST /orgs/{id}/tournamentsfor opprettelse,POST /auth/logoutfor utlogging — presentasjonskomponentene (kort, bytter, tomme tilstander) beholdt uendret fra V0./verify-siden oppdatert til å sende brukeren videre til/dashboardetter vellykket innlogging (fantes ingen dit å gå før nå). Verifisert: ekte typesjekket build, redeploy av kunteecup_frontend(ingen Caddy-endring nødvendig denne gangen — ADR-016s mønster holder),teecup.teeoff.no/dashboard→ 200,teeoff.noupå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 ekteteecup_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 inntournament-detail.tsxog etLink-baserttournament-card.tsx(navigasjon fra dashbordet). URL-design bevisst avvikende fra V0s forslag: V0s genererte side leste aldriparams.idog 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 laorg+nametil som søkeparametre itournament-card.tsxsin 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 iapp/routers/tournaments.py:PATCH .../roster/{roster_id}(bevisst enkel — setter/fjerneris_captainpå NØYAKTIG denne raden, håndhever IKKE "kun én kaptein per lag", siden kaptein fortsatt bare er et merke, ikke en egen autorisasjonsrolle) ogDELETE .../roster/{roster_id}(204, idempotentNOT_FOUNDved dobbel sletting — ikke krasj).tournament-detail.tsxsitt datalag skrevet om fra V0s mock: henter lag + roster (roster-radene bærer allerededisplay_name/handicap_index_snapshotfra APIet, så V0s separatepoolById-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 ferskteecup_scratch(PATCH setter kaptein + riktigNOT_FOUNDpå ugyldig id, DELETE gir 204 + idempotentNOT_FOUNDved gjentak,test_isolation.sqlfortsatt 12/12), ekte typesjekket frontend-build (5 ruter), redeploy av BEGGE containere (backend-endepunktene er nye),teecup.teeoff.no/dashboard→ 200,teeoff.noupå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 frateam_rostermed konfigurerbar godkjenning/kapasitet/venteliste, utvidet spillerprofil + obligatorisk samtykke, ogpublic_tournament_org()). Migrasjon007_registration_and_player_fields.sqlskrevet og scratch-verifisert (001→007 kjører rent,test_isolation.sqlfortsatt 12/12). Reelt arkitekturproblem løst underveis, ikke bare skjema: et offentlig (uautentisert) påmeldingskall kjenner en turnering-id, men ingen org-kontekst — og utenapp.current_orgslipper RLS ingen rader gjennom, heller ikke oppslaget for å FINNE riktig org. Løst med en sneverSECURITY DEFINER-funksjon (public_tournament_org) som KUN eksponerer uuid→uuid-koblingen. Verifisert presist, ikke bare "kjørte uten feil": kalt funksjonen somteecup_app-rollen med ingenapp.current_orgsatt — ga korrekt org-id for en kjent turnering,NULL(ikke feil) for en ukjent — OG et RÅTTSELECTpåtournamentpå SAMME tilkobling/rolle ga fortsatt 0 rader, som beviser RLS ikke er brutt generelt, bare dette ene smale unntaket eksisterer. Kjørt mot ekteteecup_db2026-07-18 (bruker bekreftet eksplisitt i samme økt): migrasjonen kjørte rent,test_isolation.sqlfortsatt 12/12. -
Registrerings-API LIVE (2026-07-18), samme dag: ny
app/routers/registration.py—GET /public/tournaments/{id}ogPOST /public/tournaments/{id}/register, begge UTENget_current_userellerget_authorized_org(helt uautentisert, egen/public-prefiks, bevisst atskilt fra/orgs/...i koden). Brukerpublic_tournament_org()(migrasjon 007) til å slå opp org-kontekst FØR RLS kan håndheve noe.app/routers/players.pyutvidet med alle sju nye ADR-017-feltene (mobile/email/birth_date/nickname/country/club/club_member_number).frontend/next.config.mjssinrewrites()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 iFEATURE_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 ferskteecup_scratch(001→007,test_isolation.sql12/12): hele registreringsløpet testet reelt — samtykke-avvisning (400), duplikat-avvisning (409DUPLICATE), e-post-matching mot en organisator-forhåndsopprettet spiller (BEKREFTET: ingen duplikatrad, mobil fylt inn viaCOALESCE,display_nameIKKE overskrevet), kapasitet+waitlist-policy (→waitlisted), kapasitet+closed-policy (→ 409LIMIT_REACHED),registration_ requires_approval(→pending), utløpt frist (→ 409REGISTRATION_CLOSED), ogconfirmed_countiGET-responsen talt riktig (kunconfirmed+pending, ikkewaitlisted/avviste). Rullet ut live: begge containere redeployet,teecup.teeoff.no/ dashboardog/healthfortsatt 200,teeoff.noupåvirket, det offentlige endepunktet bekreftet nåbart over ekte https (ukjent turnering-id ga korrekt404/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 migrasjon008_link_player_by_email.sql—link_player_by_email(user_id, email), sammeSECURITY DEFINER-mønster sompublic_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 — funksjonensWHERE user_id IS NULLgjø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_membershiphar NULL rader for henne etterpå — ren identitetskobling, ingen privilegie-eskalering (å haplayer.user_idsatt gir ingen ny tilgang gjennomget_authorized_org, som fortsatt krever ekte org-medlemskap uavhengig av dette). Andre innlogging idempotent, ingen feil.test_isolation.sqlfortsatt 12/12. Kjørt mot ekteteecup_db2026-07-18, bruker bekreftet eksplisitt, backend redeployet, live sjekker OK,teeoff.noupåvirket. ADR-017s backend er dermed komplett (registrering + kontokobling). Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere ADR-runde (seFEATURE_BACKLOG.md). -
ADR-018 + migrasjon 009: landingssider, backend LIVE (2026-07-18), samme dag: trenivås synlighet (
tournament.visibility:public/org/participants, defaultorg— 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 eksisterendeGET /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.visibilityhåndheves derfor eksplisitt iapp/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). Nyget_current_user_optionaliapp/auth.py(somget_current_user, men returnererNonei stedet for 401 — offentlige endepunkter skal fungere for anonyme lesere også). Ny "deltaker"-autorisasjonsvei: en innlogget bruker medplayer.user_idkoblet (ADR-017) OG entournament_registration- ellerteam_roster-rad for NØYAKTIG den turneringen får separticipants-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.slughar ligget i skjemaet siden migrasjon 001 ("f.eks. subdomene/URL-vennlig", allerede med en plainUNIQUE), noe jeg hadde oversett fullstendig og forsøkt å legge til på nytt. Rettet ved å fjerne den dobleADD COLUMN+ den overflødige partielle unik-indeksen (001 sin plainUNIQUEdekker "unik når satt" allerede, siden Postgres behandler NULL som distinkt), beholde kun de nyeCHECK-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 tabelltournament_sponsor(navn+lenke aktivt,logo_keyinert til MinIO-runden — samme medtournament.hero_image_key). TredjeSECURITY DEFINER-bro i prosjektet:public_org_by_slug()(etterpublic_tournament_org007,link_player_by_email008) — returnererNULLfor 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 sinexclude_unset— et utelatt felt nullstilles IKKE),PATCH /orgs/{id}(slug/public_profile), full sponsor-CRUD._fetch_sessions()trukket ut som delt hjelpefunksjon itournaments.py(delt mellom den innloggede og den nye offentligeGET /public/tournaments/{id}/sessions— blind draw-hemmelighold, ADR-013, arves automatisk, ikke reimplementert). Verifisert grundig mot ferskteecup_scratch(001→009,test_isolation.sql12/12): hele synlighetsmatrisen testet med ekte HTTP-kall — anonym avvist påorg-synlig turnering (både lesing OG registrering),PATCHtilpublic+ beskrivelse + sponsor fungerte, anonym lesing fungerte deretter,participants-synlighet bekreftet reell chicken-and-egg-konsekvens av Beslutning D (ingen kan selv- registrere seg til enparticipants-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 KUNpublic-synlige turneringer,CHECK-constraint (public_profilekreverslug) avvist korrekt, ugyldig slug-format avvist av Pydantic, sponsor-sletting fungerte. Kjørt mot ekteteecup_db2026-07-18, bruker bekreftet eksplisitt, backend redeployet, live sjekker OK,teeoff.noupå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 tilapp/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: ektefetchmotGET /public/tournaments/{id}+/sessions, ektePOST .../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 fororg/participants-synlige turneringer). Mappetstatus:"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 somfrontend/Dockerfileallerede bruker (upåvirket av denne — bekreftet ved at selve prod-buildet fortsatt gikk rent). Lærdom:Dockerfilesin 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 ferskteecup_scratch+ en ekte kjørende frontend-dev-server (ikke barenext 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 ektePOST-registrering som økteconfirmed_countfra 0 til 1. Rullet ut live, kunteecup_frontend(ingen backend-endring denne runden),teeoff.noupå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 eksisterendeTournamentCard), egen tom- tilstand. Reell delt-komponent-kollisjon løst, ikke duplisert bort:TournamentCardvar bygget for KUN den innloggede konteksten (krevdeorgId, lenket til/tournaments/{id}?org=...). I stedet for en egen kopi av kortet for den offentlige siden, gjortorgIdvalgfri — 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, riktigapp/clubs/[slug]/page.tsxi stedet for å bruke V0s (feilnavngitte og prop-løse) versjon. Verifisert mot ferskteecup_scratch+ ekte kjørende frontend-dev- server: org med slug+public_profile, én turnering sattpublic, én latt stå på defaultorg— 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, kunteecup_frontend,teeoff.noupå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 gjennomnext.config.mjssinrewrites()(som kun gjelder nettleser-trafikk inn til Next.js- serveren). Måtte derfor leseTEECUP_API_ORIGINdirekte, men den variabelen fantes KUN iDockerfilesitt builder-steg —ENVsatt i ettFROM-steg arves ikke til et senere. Rettet ved å sette sammeARG/ENVpå 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 tilmetadataBaseiapp/layout.tsxnå likevel, som forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en egen fiks da. Rullet ut live, kunteecup_frontend,teeoff.noupå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 tiltournament.hero_image_key/tournament_sponsor. logo_key(som lå inerte siden migrasjon 009), offentlige API-svar bygger nå fulle URL-er,og:imagekoblet på i/t/[id]singenerateMetadata. 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-pluginkrever ingen ekstra systempakker ipython:3.12-slim-- verifisert med et frittstående encode/decode- rundtrip FØR det ble tatt i bruk i selve API-et. (2) MinIO validererHost-headeren STRENGT og avviser understrek som ugyldig vertsnavn -- et tjenestenavn med understrek (teecup_minio, konsistent medteecup_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 EKSISTERENDEteecup.teeoff.no-blokket (IKKE et nytt subdomene -- en tidlig sjekk avdekket atmedia.teecup.teeoff.nofantes 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 -- fulldocker restart teeoff_caddy, brukeren bekreftet eksplisitt.teeoff.noupå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), bekreftethero_image_url/logo_urlbygget riktig i det offentlige API-svaret. Alle tre valideringsveier testet (ugyldig content-type, korrupt bildeinnhold, for stor fil >8MB) -- alle ga korrektVALIDATION_FAILED. Etter Caddy-omstart: et ekte anonymt kall mot/teecup-media/...på produksjonsdomenet ga en ekte MinIO S3-XML-feilrespons (NoSuchKeyfor en scratch-nøkkel som naturligvis ikke finnes i prod) -- beviser ruten treffer MinIO selv, ikke frontend sin egen 404-side.test_isolation.sqlfortsatt 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_ider 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å. Nyapp/routers/courses.py(GET/POST /orgs/{id}/ courses, kunsource='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 skjemaetsCHECK-constraint oghandicap_engine.pysinFormat-enum kreverscramble_2/scramble_4som distinkte verdier -- ren"scramble"avvises med 400. Rettet i frontend-mappingen til to segment-knapper.allowance_override-JSON-formen verifisert eksakt motapp/handicap.pysinparse_allowance_config/_strategy_from_json({type: "combined"|"per_player", percentage: 0..1}, ikke en flat prosent) -- frontend velger riktigtypeut fra om formatet er side-enhet eller spiller-enhet, konverterer 0–100-skjemafelt til 0–1. Scratch-infrastruktur denne runden, bevisst forskjellig fra tidligere: brukte en ISOLERTteecup_app_scratch-rolle (GRANT teecup_app TO teecup_app_scratch) i stedet for den ekteteecup_app-rollen -- den er nå cluster-global og produksjonskritisk (delt Postgres-instans medteecup_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 forensure_bucket()). Verifisert: courses opprettet+listet, kryss-org-isolasjon bekreftet, økt med klokkeslett, økt medscramble_4+fullallowance_override- rundtur, gammel"scramble"-verdi avvist (400),test_isolation.sql12/12, ekte typesjekket PRODUKSJONSBUILD (sammeDockerfilesom 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. IKKE rullet ut live ennå — venter på brukerens bekreftelse (ingen migrasjon denne runden, men ny backend-/frontend-kode kreverdocker compose up -d --buildmot ekte containere).
Neste steg:
- Rulle program-skjermen live (venter på bekreftelse).
- Flere V0-skjermer (blind draw, scorekort, leaderboard) — samme mønster: 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.
- PWA-egenskaper (manifest, service worker, offline-cache) — ikke startet.
- Kommunikasjon (chat/feed) — ikke startet.