Bygget: GET /public/tournaments/{id} og POST /public/tournaments/{id}/register — helt uautentisert, egen /public-prefiks. players.py utvidet med alle sju nye feltene.
Grundig scratch-testet, ikke bare "kjørte uten feil": samtykke-avvisning, duplikat-avvisning, e-post-matching mot en organisator-forhåndsopprettet spiller (bekreftet ingen duplikat, mobil fylt inn, navn ikke overskrevet), kapasitet+venteliste, kapasitet+stengt, godkjenningskrav, utløpt frist — alle seks scenarioene fra ADR-en testet én etter én og ga riktig resultat.
Notatet ditt om synlighet er fanget i FEATURE_BACKLOG.md, koblet til det samme åpne spørsmålet for «Banter Board»-feeden — før dette API-et ble bygget, ikke etter, slik du ba om.
Gjenstår, bevisst utsatt:
E-post-basert kontosammenkobling ved innlogging (ADR-017 Beslutning B sin andre halvdel) — trenger en ny SECURITY DEFINER-funksjon på tvers av org-er, altså migrasjon 008 siden 007 alt er kjørt mot prod. Ikke gjort i denne runden.
Selve påmeldingsflyten er ikke testet med ekte data mot prod (kun ikke-destruktive sjekker: ukjent turnering ga korrekt 404).
Landingssider — egen ADR-runde, som avtalt.
29 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…017). 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). Gjenstår: e-post-basertplayer.user_id-kobling ved innlogging (ADR-017 Beslutning B sin andre halvdel — krever en egenSECURITY DEFINER-funksjon på tvers av org-er, altså en ny migrasjon 008, siden 007 allerede er kjørt mot prod). Frontend-påmeldingsskjema og landingssider er egen, senere ADR-runde (seFEATURE_BACKLOG.md).
Neste steg:
- ADR-017: e-post-basert kontosammenkobling ved innlogging (migrasjon 008
verify_magic_link-utvidelse), så landingssider (turnering/org) med påmeldingsskjema i frontend — se brukerens landingsside-spørsmål, samme økt.
- Flere V0-skjermer (økt/program, 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 (se dashboard-/roster-rundene over for hvorfor begge er nødvendige hver gang).
- PWA-egenskaper (manifest, service worker, offline-cache) — ikke startet.
- Kommunikasjon (chat/feed) — ikke startet.