Skriv migrasjon 033 (e-post-preferanser per varseltype) Legg til send_notification_email() i app/email.py Bygg GET/PUT /notifications/email-prefs + koble e-post inn i create_notification() Bygg frontend: velg varseltyper for e-post i /account Scratch-verifiser hele løpet (preferanser + faktisk e-postutsending) Oppdater CLAUDE.md + rull ut mot ekte systemer (med bekreftelse) Live. E-post-fallback for varsler er ferdig, per-type valgbar av mottakeren selv: Ny seksjon "Varsler på e-post" i /account — fire avkrysningsbokser (Venneforespørsler/Runder/Resultater/Turneringer), trygg standard er ingen valgt (ingen e-post inntil du eksplisitt krysser av). E-post-utsendingen ligger ett sted (inni create_notification()), så alle nåværende og fremtidige varseltyper får e-post-støtte automatisk. 19/19 nye sjekker + full regresjon av fire eksisterende testsuiter (219 sjekker totalt) grønt. Migrasjon 033 kjørt mot ekte database (kun ny tabell), test_isolation.sql fortsatt 12/12, begge containere redeployet, teeoff.no upåvirket.
4954 lines
327 KiB
Markdown
4954 lines
327 KiB
Markdown
# CLAUDE.md — arbeidsinstruks for TeeCup
|
||
|
||
Les dette først i hver økt. Det koder hva vi har bestemt og hvordan vi jobber.
|
||
|
||
## Autoritative kilder (les før du gjør noe)
|
||
- `ARCHITECTURE_DECISIONS.md` — hva som er bestemt og hvorfor (ADR-001…029). Fasit.
|
||
- `FEATURE_BACKLOG.md` — hva som gjenstår, hva som er utsatt, hva som mangler.
|
||
- Endres en beslutning: legg til en ny ADR, ikke slett historikk. Hold begge
|
||
filene oppdatert når noe avgjøres.
|
||
- **Regelverk for HCP/slagfordeling — tre PDF-er lastet opp av brukeren til
|
||
prosjektroten 2026-07-19** (ikke innsjekket i git, kun lokale filer på
|
||
serveren): `spilletyper-og-spilleformer-2023.pdf`,
|
||
`Live Tourney _ A Guide to Handicap Scoring in Golf for Tournaments.pdf`,
|
||
`SCGA Club Digest.pdf`. Brukeren: disse tre gir til sammen en tydelig
|
||
beskrivelse av hvordan HCP og mottatte/tildelte slag skal beregnes/
|
||
fordeles. Les disse FØR videre arbeid med `handicap_engine.py`,
|
||
`app/handicap.py`, allowance-strategier (ADR-005/014) eller
|
||
slagfordeling (ADR-008) — spesielt relevant for de fire
|
||
turneringsformatene som ennå ikke er designet (Københavner/High-low-high/
|
||
Robbins/Try all, se FEATURE_BACKLOG.md), siden disse dokumentene trolig
|
||
dekker akkurat de reglene som trengs der.
|
||
- **Design-dokumentasjon, to filer med bevisst motsatt retning (2026-07-27):**
|
||
`DESIGN_SYSTEM.md` (innsjekket i git) er DESKRIPTIV — hva som faktisk ER i
|
||
koden i dag (farger/typografi/komponentmønstre/golfscore-språket), avledet
|
||
direkte fra `globals.css`/`components/ui/*`. `teecup-scorekort-og-entry-
|
||
spec.md` (lastet opp av brukeren til prosjektroten, IKKE innsjekket i git)
|
||
er PRESKRIPTIV — hva scorekort-/score-entry-skjermene BØR bli, basert på en
|
||
sammenligning mot fem etablerte golf-apper. Der de to avviker vinner
|
||
kildekoden (DESIGN_SYSTEM.md beskriver den), men spec-dokumentets §3
|
||
"Compliance-pass" er en konkret sjekkliste over kjente avvik — les den FØR
|
||
videre visuelt arbeid på scorekort-/score-entry-skjermene.
|
||
|
||
## Sikkerhetsregler (ufravikelige)
|
||
- Rør ALDRI `teeoff`-databasen eller den ekte `teecup_db` uten at brukeren
|
||
eksplisitt har bekreftet det i samme økt. Test alltid migrasjoner mot en egen
|
||
scratch-database først, og rydd opp etterpå.
|
||
- Vis planen (hvilke kommandoer, mot hvilken database) FØR du kjører noe som
|
||
skriver, migrerer eller sletter. Vent på bekreftelse.
|
||
- Hemmeligheter (passord, secrets) bor i `.env` (filrettigheter 600), dekkes av
|
||
`.gitignore`, committes aldri, og skrives aldri i klartekst i chatten eller i
|
||
SQL-filer. Generer dem på serveren (`openssl rand -base64 32`).
|
||
- Kjør appen som databaserollen `teecup_app` (NOSUPERUSER, NOBYPASSRLS) — aldri
|
||
som `teeoff_admin`/superuser i runtime.
|
||
|
||
## Arkitektur-invarianter (ikke bryt uten en ny ADR)
|
||
- Tenant = organisasjon. `organization_id` på alle domenetabeller, håndhevet av
|
||
RLS. App-koden setter `app.current_org` med `SET LOCAL` per transaksjon.
|
||
- Verifiser at brukeren er medlem av organisasjonen FØR org-konteksten settes.
|
||
RLS stoler blindt på `app.current_org`.
|
||
- Egen innlogging (uavhengig av teeoff). Banedata hentes fra teeoff via lesende
|
||
API, ikke delt database.
|
||
- v1 = nøyaktig to lag (Ryder Cup-format), håndhevet i app-laget. Match-modellen
|
||
holdes generell (to sider) så knockout/flere lag kan komme senere.
|
||
- Handicap-/matchlogikk skal ligge i `handicap_engine.py` (rent, testet, uten
|
||
db/API-avhengigheter). Allowances er konfig, ikke hardkodet.
|
||
- Media (bilder/video) skal i objektlagring (MinIO), ikke i Postgres. Postgres
|
||
holder bare metadata + nøkkel.
|
||
|
||
## Tilgjengelighet — frontend (ufravikelig, gjelder ALT, eksisterende og fremtidig)
|
||
- Brukeren instruerte eksplisitt 2026-07-22: ALL frontend — det som
|
||
allerede finnes OG alt som designes med V0 fremover — skal være
|
||
lesbart, forståelig og betjenbart for noen med noe redusert syn UTEN
|
||
briller, så langt det praktisk lar seg gjøre. Dette er en STÅENDE
|
||
forventning til alt fremtidig UI-arbeid, ikke en engangsting for én
|
||
skjerm.
|
||
- Praktisk konsekvens: god kontrast, stor nok skrift, store nok
|
||
trykkflater, ikke ikon-only uten tekst-label for viktige handlinger,
|
||
ikke avhengig av finmotorikk/skarpt syn for å bruke appen.
|
||
- Gjelder begge retninger: (a) ta dette eksplisitt med som krav når en ny
|
||
V0-prompt skrives eller en V0-eksport gjennomgås, og (b) rett
|
||
opportunistisk opp eksisterende skjermer når de likevel røres i en
|
||
annen runde — ingen egen stor retrofit-runde er igangsatt eller bedt
|
||
om ennå.
|
||
|
||
## Navneformat (ufravikelig, gjelder ALT, eksisterende og fremtidig)
|
||
- Brukeren instruerte eksplisitt 2026-07-26: når TeeCup kommuniserer
|
||
DIREKTE TIL brukeren (adresserer dem, "snakker til" dem) — f.eks. en
|
||
dashbord-hilsen ("God morgen Erol") eller en e-post/varsel rettet til
|
||
mottakeren selv — skal KUN FORNAVN brukes, aldri fullt navn.
|
||
- Til vanlig (lister, roster, chat-forfatter, "fjern X"-bekreftelser,
|
||
administrasjonsvisninger, alt som IKKE er direkte adressering) brukes
|
||
fortsatt fullt navn, eller initial(er)+etternavn, eller annet som er
|
||
nødvendig for å skille personer fra hverandre — ikke fornavn alene der.
|
||
- Samme STÅENDE forventning som tilgjengelighetsregelen over: gjelder
|
||
fremtidig arbeid direkte, og rettes opportunistisk når en skjerm
|
||
likevel røres — ingen egen retrofit-runde igangsatt.
|
||
- **✅ FIKSET 2026-07-26** (samme dag, i "fiks alle kjente små bugs"-
|
||
runden): dashbordets hilsen brukte fullt navn — rettet til
|
||
`me.first_name ?? me.display_name` (`/auth/me` eksponerte allerede
|
||
`first_name` separat, ingen backend-endring nødvendig). Se
|
||
CLAUDE.md-status lenger ned for full detalj.
|
||
|
||
## Arbeidsmåte
|
||
- Inkrementelt. Ingenting tas for gitt før det er testet. Bekreft hvert steg før
|
||
du går videre.
|
||
- Bruk git (remote: brukerens Forgejo). Commit i logiske steg med tydelige
|
||
meldinger.
|
||
- Er du usikker på omfang eller en beslutning: spør heller enn å gjette.
|
||
|
||
## Status (oppdater denne når ting endres)
|
||
Ferdig og verifisert:
|
||
- Handicap-motor + tester (24/24, R&A-verifisert).
|
||
- Skjema `001` + roller `002` + scoring/blind draw `003`. Isolasjon bevist med
|
||
`test_isolation.sql` (RLS-oppførsel, ikke bare at skjemaet kjører).
|
||
- 002 hadde en reell bug (psql interpolerer ikke `:'var'` inne i `DO $$...$$`)
|
||
— permanent fikset, verifisert mot scratch to ganger.
|
||
- API-et kjørt for ekte (ikke bare syntaks-sjekket) i en engangs Docker-
|
||
container mot en scratch-database, RLS bevist gjennom hele
|
||
asyncpg-pool-stacken (ikke bare i rå SQL).
|
||
- Oppsett-endepunktene er bygget og verifisert: `app/routers/players.py`
|
||
(spillerpool), `tournaments.py` (turnering/lag/roster/økter, ADR-011
|
||
to-lags-grense håndhevet med `FOR UPDATE`-lås), `matches.py` (matcher/
|
||
deltakere/blind draw-lås, ADR-013-synlighet push-down i SQL). Delt
|
||
feiloversettelse i `app/errors.py`, delte synlighetsspørringer i
|
||
`app/blind_draw.py`. `main.py` er nå bare app-factory + `include_router`.
|
||
- Scoring-runden er bygget og verifisert for ekte mot scratch-db (18-hulls
|
||
bane med `tee_rating`, 4 spillere for fourball-testing): `app/handicap.py`
|
||
(ADR-014 fire brytere via `parse_allowance_config`, handicap beregnes i
|
||
`compute_and_store_side_handicaps` rett etter deltaker-innsetting —
|
||
singles/fourball per spiller umiddelbart, foursome/greensome/scramble kun
|
||
når siden er komplett), `app/routers/scoring.py` (`hole-scores`/
|
||
`hole-results`-upsert, `scorecard`-GET, matchstatus-recompute med
|
||
`FOR UPDATE`-lås mot race og SAMMENHENGENDE-prefiks-regel for uferdige
|
||
hull). `app/team_authz.py` skilt ut fra `matches.py` (delt med
|
||
`scoring.py`). Alle 10 planlagte tester bestått, inkl. fourball
|
||
better-ball-aggregering (MIN av to nettoer, venter til begge partnere har
|
||
registrert), poeng-caching ved tidlig avgjort match, og ADR-014-bryteren
|
||
`use_handicap=false`.
|
||
**Fant og fikset underveis:** `tournaments.py` sin `SessionCreate` manglet
|
||
`scoring_mode` helt (økter kunne aldri opprettes i `hole_result`-modus via
|
||
API-et) — lagt til.
|
||
**Bevisst utelatt/kjente begrensninger:** en side som aldri når forventet
|
||
deltakerantall (no-show) får aldri beregnet handicap og matchen kan da
|
||
aldri avgjøres — ingen manuell overstyring bygget. Score-skriving er
|
||
upsert (ingen avvisning ved duplikat) — ingen audit-trail på rettelser.
|
||
Kapteins-only autorisasjon fortsatt ikke bygget (FEATURE_BACKLOG ❓); bar
|
||
er «rostret på laget».
|
||
- **Match-lås ved avgjørelse (2026-07-16):** `submit_hole_score`/
|
||
`submit_hole_result` avviser nå 409 hvis `match.points_side_a IS NOT NULL`
|
||
(matchen er avgjort) — FØR upserten kjøres, både for nye hull og
|
||
korrigering av allerede talte hull. Tetter en reell bug: uten dette kunne
|
||
«spøkelses-hull» lagt inn etter avgjørelse endre en allerede cachet margin
|
||
ved neste omregning. Automatisk, ingen ny autorisasjon involvert.
|
||
- **Ekte autentisering bygget og verifisert (2026-07-16):** `X-Debug-User-Id`-
|
||
stubben er HELT fjernet (ingen fallback). Magic-link + JWT-sesjon i
|
||
`app/routers/auth.py` + `app/auth.py` (`request-link`/`verify-link`/
|
||
`logout`/`me`), ny migrasjon `004_auth.sql` (`magic_link_token`-tabell +
|
||
unik e-post-indeks på `app_user`). Token = `secrets.token_urlsafe(32)`, kun
|
||
SHA-256-hash lagres, atomisk forbruk (`UPDATE ... RETURNING`, ikke
|
||
les-sjekk-skriv), generisk respons uansett om e-posten finnes (unngår
|
||
enumerering), gamle uforbrukte lenker ugyldiggjøres når en ny utstedes,
|
||
`app_user` opprettes FØRST ved vellykket verifisering (ikke ved
|
||
forespørsel). Sesjons-JWT (PyJWT, `algorithms=["HS256"]` eksplisitt) i
|
||
HttpOnly/SameSite=Lax/dynamisk-Secure-cookie, 30 dager, med et ekte
|
||
eksistens-oppslag mot `app_user` på hver forespørsel (faktisk
|
||
tilbakekalling — en slettet bruker kan ikke ri ut sesjonen). Alle 12
|
||
planlagte tester bestått.
|
||
**Fant og fikset underveis:** `ON CONFLICT (email)` matchet ikke den nye
|
||
PARTIELLE unike indeksen uten eksplisitt `WHERE email IS NOT NULL` (samme
|
||
klasse feil som `hole_score`s partielle indekser i scoring-runden).
|
||
**Fant, IKKE fikset i denne runden (egen runde rett etterpå — se under):**
|
||
`organization`-tabellens RLS-policy kastet en 500 i stedet for skjemaets
|
||
lovede "trygg standard: se ingenting" ved tomstreng-GUC.
|
||
- **RLS-tomstreng-bug FIKSET (2026-07-16):** ny migrasjon
|
||
`005_rls_null_guard.sql` — delt `STABLE` SQL-funksjon `app_current_org()`
|
||
gjør `NULLIF(current_setting('app.current_org', true), '')::uuid` i stedet
|
||
for det rå uttrykket, brukt av alle 15 RLS-policyer (`ALTER POLICY`,
|
||
14 `org_isolation` + `org_self`). Verifisert med 3 nye regresjonstester i
|
||
`test_isolation.sql` (Test 10-12) OG ved faktisk å gjenskape original-
|
||
buggen mot en ekte container (pool-størrelse 1, varm opp med
|
||
`org_connection()`, deretter `/auth/me` på samme gjenbrukte tilkobling —
|
||
gikk fra 500 til 200).
|
||
**Viktig presisering fra denne runden:** fiksen gjør IKKE at `/auth/me` kan
|
||
joine `organization` direkte via `plain_connection()` — det var en feilaktig
|
||
antakelse i forrige runde. `org_self` krever fortsatt en MATCHENDE
|
||
`app.current_org` for å vise en rad (riktig RLS-design, ikke noe fiksen
|
||
skulle endre), og en bruker kan tilhøre flere organisasjoner samtidig, så
|
||
det finnes ingen ÉN kontekst å sette for en tverr-org-spørring. `/auth/me`
|
||
slår derfor opp hvert org-navn ett om gangen via `org_connection()` (N+1,
|
||
N = antall org-er brukeren tilhører) — dette er riktig løsning, ikke en
|
||
omvei.
|
||
- **Organisasjon-bootstrap bygget og verifisert (2026-07-16):** nytt
|
||
`POST /orgs` (`app/routers/organizations.py`) — det ENESTE stedet i API-et
|
||
som setter inn en `organization`-rad. Fant under statusgjennomgang at dette
|
||
manglet helt (alle tidligere org-er var seedet med superbruker-SQL). Ingen
|
||
ny migrasjon. Selvrefererende RLS-bootstrap bekreftet å fungere: generer
|
||
org-ens uuid i Python, sett `app.current_org` til nøyaktig den via
|
||
eksisterende `org_connection()`, sett inn `organization`-raden med samme
|
||
id — `org_self`s implisitte `WITH CHECK` blir da trivielt sann, ingen
|
||
privilegert tilkobling nødvendig (i motsetning til hva 002s kommentar
|
||
antydet). Verifisert med 5 tester inkl. en negativ kontroll (mismatchende
|
||
id avvist med `insufficient_privilege`) og full kryss-org-isolasjon mellom
|
||
to uavhengig opprettede organisasjoner.
|
||
- **Ekte SMTP-utsending bygget og verifisert (2026-07-16):** ny `app/email.py`
|
||
(`send_magic_link_email`, `smtplib` via `asyncio.to_thread`, håndterer
|
||
både implisitt TLS/port 465 og STARTTLS dynamisk). Brukeren la egne
|
||
SMTP-credentials i `.env` (`TEECUP_SMTP_*`, `TEECUP_FROM_EMAIL` — ADR-009,
|
||
ikke delt med teeoff); jeg leste kun nøkkelnavnene for å bekrefte de
|
||
fantes, aldri verdiene. `app/config.py` sin `SMTP_CONFIGURED` er valgfri
|
||
(ikke `_required`) — dev-only logging (`TEECUP_DEV_LOG_MAGIC_LINKS`)
|
||
fortsatt fungerer uendret når SMTP ikke er satt opp. Driftsfeil i
|
||
utsendingen lekker aldri til klientresponsen (bevarer anti-enumerering).
|
||
**Verifisert med faktisk levering:** sendte én ekte test-e-post til en
|
||
adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i
|
||
prosjektet er bevist ved ekte levering, ikke bare curl/scratch.
|
||
- **Containerisert og LIVE på `teecup.teeoff.no` (2026-07-16):** ekte
|
||
`teecup_db` opprettet (migrasjoner 001→005 kjørt permanent, `test_isolation.sql`
|
||
består), `Dockerfile` + `docker-compose.yml` (tjeneste `teecup_api`, joiner
|
||
det eksisterende `teeoff_default`-nettverket), Caddy-blokk lagt til i
|
||
`/opt/teeoff/deploy/Caddyfile`. Ekte innlogging (magic-link → e-post →
|
||
JWT-sesjon med `Secure`-cookie) verifisert ende-til-ende mot den live
|
||
stacken. `teeoff.no` upåvirket gjennom hele prosessen.
|
||
**To reelle hendelser underveis, begge løst:**
|
||
1. **Caddy plukket ikke opp filendringen** — `teeoff_caddy` sin
|
||
`Caddyfile`-mount er en ENKELTFIL-bind-mount, låst til inoden som fantes
|
||
da containeren sist startet. Min fil-redigering (atomisk rename) laget
|
||
en ny inode på samme sti, så containeren fortsatte å lese den GAMLE
|
||
filen uansett hvor mange ganger `caddy validate`/`caddy reload`/admin-API
|
||
`/load` ble kjørt (alle validerte/lastet den uendrede gamle filen, derav
|
||
ingen feilmelding). Løst med en full `docker restart teeoff_caddy`
|
||
(brukeren bekreftet — avvek fra planens "kun graceful reload, ingen
|
||
omstart"-løfte, noen sekunders nedetid for `teeoff.no`).
|
||
2. **Alvorlig nettverksalias-kollisjon** (funnet RETT ETTER omstarten, da
|
||
ekte teeoff-trafikk som `/api/facilities?...` med ekte klubb-slugs dukket
|
||
opp i `teecup_api` sin logg): `docker-compose.yml` sin service-nøkkel var
|
||
`api:` — SAMME nøkkel som teeoffs eget `api`-servicenavn
|
||
(`docker-compose.prod.yml`). Docker Compose registrerer nettverksalias
|
||
basert på service-NAVNET (ikke bare `container_name`) på delte nettverk,
|
||
så BEGGE containerne fikk alias `api` på `teeoff_default` — Caddys
|
||
`reverse_proxy api:8000` i teeoff sin egen config kunne da tilfeldig
|
||
treffe enten ekte `teeoff_api` eller `teecup_api`. **Rettet umiddelbart**
|
||
(stoppet `teecup_api` først for å hindre videre feilruting av ekte
|
||
teeoff-trafikk, ga service-nøkkelen navnet `teecup_api` i stedet,
|
||
gjenopprettet — bekreftet med `docker network inspect` at alias `api` nå
|
||
KUN peker på ekte `teeoff_api`).
|
||
**Mindre driftslærdom:** (a) jeg eksponerte ved et uhell
|
||
`TEECUP_SMTP_PASS`/`TEECUP_FROM_EMAIL` i eget debug-output mens jeg
|
||
feilsøkte en `.env`-korrupsjon (manglende linjeskift fra min egen
|
||
`>>`-tilføyelse) — brukeren roterte passordet som forsiktighetsregel; (b)
|
||
Docker leser IKKE `.env` på nytt for en allerede kjørende container —
|
||
`docker compose up -d --force-recreate` kreves etter enhver `.env`-endring
|
||
som skal tas i bruk; (c) et `#`-tegn i et upassordet `.env`-passord kuttes
|
||
som en kommentar av Compose sin parser — anførselstegn (fortrinnsvis enkle)
|
||
løser dette.
|
||
- **Dato/klokkeslett + feilkode-kontrakt + i18n-forberedelse (2026-07-17,
|
||
ADR-015):** reist av brukeren rett før frontend-arbeidet. Ny migrasjon
|
||
`006_scheduling_and_locale.sql` (alt additivt): `tournament.end_date`,
|
||
`session.scheduled_at`/`tee_interval_minutes`/`start_hole`,
|
||
`match.tee_time_override`, `app_user.preferred_locale`,
|
||
`magic_link_token.locale`. `match.tee_time` er UTLEDET i Python
|
||
(`scheduled_at + (sequence-1)*tee_interval_minutes`, override vinner hvis
|
||
satt) — aldri lagret per match. Full retrofit av ALLE 39 daværende
|
||
`HTTPException(..., detail="norsk streng")`-steder på tvers av 6 filer til
|
||
en delt `app_error(status_code, code, message)`-factory
|
||
(`app/errors.py`) — responsformen er nå konsekvent
|
||
`{"detail":{"code":...,"message":...}}` i hele API-et, verifisert med et
|
||
siste `grep -rn 'detail="' app/` som ga NULL treff. i18n: `locale`
|
||
(`nb`/`en`) sendes av klienten ved `request-link`, styrer e-postmalen
|
||
(ekte engelsk mal lagt inn i `app/email.py`, ikke bare rørlegging) OG
|
||
settes som en HELT NY brukers `preferred_locale` — en eksisterende bruker
|
||
som logger inn på et annet språk får IKKE sin lagrede preferanse
|
||
overskrevet.
|
||
**Fant og fikset underveis:** `tournament.start_date` har ligget i
|
||
skjemaet siden migrasjon 001, men var ALDRI koblet til
|
||
`TournamentCreate`/`Tournament`-modellene — funnet som en naturlig
|
||
bivirkning av å legge til `end_date`.
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (001→006,
|
||
`test_isolation.sql` fortsatt 12/12): feilkode-form bekreftet på tvers av
|
||
5 filer (`NOT_FOUND`/`LIMIT_REACHED` tournaments.py, `DUPLICATE` roster,
|
||
`NOT_AUTHENTICATED` uten cookie, `NOT_ROSTERED_ON_TEAM` matches.py),
|
||
tee_time-beregning bekreftet (10 min intervall → 08:00/08:10/08:20,
|
||
override vinner, `scheduled_at=null` gir `tee_time:null` ikke feil), full
|
||
i18n-runde bekreftet (ny bruker `locale:"en"` → `preferred_locale:"en"`;
|
||
påfølgende `request-link` for samme bruker med `locale:"nb"` skiftet
|
||
e-postmalen men IKKE den lagrede preferansen, bekreftet via `/auth/me`).
|
||
**Kjørt mot ekte `teecup_db` 2026-07-17** (bruker bekreftet eksplisitt i
|
||
samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12
|
||
(ruller alltid tilbake, ingen domenerader berørt).
|
||
**Mindre driftslærdom, funnet OG rettet samme runde:** et `.env`-filter
|
||
(`grep -v -i 'pass|secret|key'`) jeg brukte for å lese ikke-sensitive
|
||
nøkler fanget ikke opp `TEECUP_DATABASE_URL`, som bar `teecup_app`-
|
||
passordet innebygd i selve URL-en (i tillegg til at det SAMME passordet
|
||
allerede lå rent i `TEECUP_APP_PASSWORD` — duplisert, ikke bare skjult) —
|
||
passordet ble dermed synlig i et verktøyresultat. Flagget til brukeren
|
||
umiddelbart (samme mønster som SMTP-passord-hendelsen over).
|
||
- **`.env`/tilkobling ryddet opp (2026-07-17), samme runde:** roten til
|
||
hendelsen over var at `TEECUP_DATABASE_URL` var én sammensatt DSN-streng
|
||
med passordet URL-kodet inni — usynlig for navnebaserte secret-filtre.
|
||
Erstattet med fem separate, rent navngitte felt:
|
||
`TEECUP_DB_HOST`/`TEECUP_DB_PORT`/`TEECUP_DB_NAME`/`TEECUP_DB_USER`/
|
||
`TEECUP_DB_PASS` (sistnevnte omdøpt fra `TEECUP_APP_PASSWORD`, kun
|
||
nøkkelnavnet — verdien aldri lest eller skrevet av meg). `app/config.py`
|
||
og `app/db.py` bygger nå `asyncpg`-poolen fra disse fem separate feltene
|
||
(`host=`/`port=`/`user=`/`password=`/`database=`) i stedet for én DSN —
|
||
fjerner også URL-prosentkoding-problemet fra SMTP-passord-hendelsen sin
|
||
klasse av feil. `docker-compose.yml` sin `environment:`-liste oppdatert
|
||
tilsvarende. **Krevde et fullt image-rebuild, ikke bare
|
||
`--force-recreate`:** `Dockerfile` sin `COPY app/ app/` bakes inn i
|
||
imaget ved build-tid (ingen bind-mount i prod, i motsetning til
|
||
scratch-verifiseringens engangscontainere) — en ren `--force-recreate`
|
||
gjenbrukte det GAMLE imaget og krasjet umiddelbart på den nå fjernede
|
||
`TEECUP_DATABASE_URL`. Rettet med `docker compose up -d --build
|
||
--force-recreate`. **Verifisert ende-til-ende mot den live stacken:**
|
||
containeren boot-et rent (`Application startup complete` i loggen, som
|
||
krever en vellykket `init_pool()` — asyncpg ville kastet og forhindret
|
||
akkurat den logglinjen ved feil tilkoblingsparametre), `/auth/me` over
|
||
ekte https ga et rent `401 NOT_AUTHENTICATED` (ikke 500/502), `teeoff.no`
|
||
upåvirket (`200` gjennom hele omstarten, kun `teecup_api` restartet — ikke
|
||
delt Caddy-instans, ikke samme risikoklasse som Caddy-hendelsen fra
|
||
containeriseringsrunden).
|
||
|
||
- **Frontend startet, innlogging LIVE (2026-07-17, ADR-016):** første
|
||
frontend-skjerm i prosjektet. Designet i V0 (Next.js + Tailwind +
|
||
shadcn/ui), hentet inn som `frontend/` — merkevare-form/farge fra
|
||
Teeoff-logoen, IKKE navn/logo (egne, separate produkter, se ADR-009).
|
||
**Kvalitetsrunde før bruk:** V0s fargetokens var OKLCH-TILNÆRMINGER, ikke
|
||
eksakte — regnet ut presise verdier fra `#8bc24a`/`#ff5722` og rettet alle
|
||
6 forekomster i `globals.css`. Fjernet `@vercel/analytics` (unødvendig på
|
||
egen-hostet infra), fjernet `typescript: { ignoreBuildErrors: true }`
|
||
(ekte typesjekk kjører nå), fjernet dødt `pnpm.overrides`-felt.
|
||
`frontend/.gitignore` manglet `.pnpm-store/` — årsaken til at brukerens
|
||
VSCode viste 10 000 "ukjente" filer ved første commit-forsøk; rettet.
|
||
**Kablet mot ekte API:** `next.config.mjs` sin `rewrites()` proxyer
|
||
`/auth/*`/`/orgs/*`/`/health` server-side til `teecup_api` — same-origin,
|
||
ingen CORS, cookie uendret (full begrunnelse i ADR-016). Login-skjermet
|
||
sender ekte `POST /auth/request-link`; ny `/verify`-side mottar
|
||
`?token=...` fra e-postlenken (auto-verifiserer) eller viser et manuelt
|
||
"lim inn koden"-felt. `app/email.py` fikk en ny `PUBLIC_BASE_URL`-
|
||
innstilling og sender nå en EKTE klikkbar lenke (koden beholdes som
|
||
fallback).
|
||
**Reell fallgruve funnet og fikset ved containerisering:** Next.js sin
|
||
`rewrites()` løses ved BUILD-tid for `output: "standalone"`, ikke ved
|
||
container-oppstart — en runtime `-e TEECUP_API_ORIGIN=...` ble stille
|
||
ignorert (proxy-kall feilet med `ECONNREFUSED` mot `localhost:8000`).
|
||
Løst med en Docker build-time `ARG TEECUP_API_ORIGIN` i
|
||
`frontend/Dockerfile`, satt via `docker-compose.yml` sin `build.args`.
|
||
**Rullet ut live:** ny `teecup_frontend`-tjeneste i `docker-compose.yml`.
|
||
Caddy (`teecup.teeoff.no`, i det SEPARATE `teeoff`-repoet,
|
||
`/opt/teeoff/deploy/Caddyfile`) endret fra å peke direkte på `teecup_api`
|
||
til å peke på `teecup_frontend` — samme stale-inode-oppførsel som
|
||
containeriseringsrunden (graceful `reload` plukket IKKE opp endringen,
|
||
`/` fortsatte å gi `teecup_api` sin egen 404 i stedet for innloggingssiden
|
||
til reload faktisk skjedde). Løst likt: full `docker restart
|
||
teeoff_caddy`, brukeren bekreftet eksplisitt på forhånd. `teeoff.no`
|
||
upåvirket gjennom hele omstarten.
|
||
**Verifisert med FAKTISK e-postlevering:** ekte magic-link sendt til
|
||
brukerens egen adresse over `https://teecup.teeoff.no`, ekte e-post
|
||
mottatt med en ekte klikkbar lenke, åpnet i nettleser, landet på en
|
||
fungerende `/verify`-side, sesjon opprettet — brukeren bekreftet innlogget
|
||
status. Første gang en hel bruker-vendt flyt er bevist ende-til-ende i
|
||
produksjon, ikke bare API-et isolert.
|
||
**Merk for neste økt:** `deploy/Caddyfile`-endringen ligger uncommitted i
|
||
det SEPARATE `/opt/teeoff`-repoet, ikke i `teecup`-repoet — lett å glemme
|
||
siden denne økten ellers kun har jobbet i `/opt/teecup`.
|
||
- **Dashboard-skjerm LIVE (2026-07-18):** andre V0-skjerm — organisasjon-
|
||
bytter/-opprettelse + turneringsliste (`/dashboard`), samme mønster som
|
||
login-runden. **Reell integrasjonsfelle unngått:** V0s eksport denne gangen
|
||
var en FULL re-eksport av hele prosjektet (inkl. `login-form.tsx`,
|
||
`next.config.mjs`, `package.json`), ikke bare de nye filene — en naiv
|
||
utpakking ville stille reversert `rewrites()`-proxyen, `output:
|
||
"standalone"`, den ekte fetch-kablingen i login-skjemaet, og alle
|
||
V0-uavhengige opprydninger fra forrige runde. Løst ved å pakke ut til et
|
||
scratch-område FØRST, diffe mot live-treet fil for fil, og kun ta inn det
|
||
som faktisk var nytt (`dashboard.tsx`, `tournament-card.tsx`,
|
||
`tournament-status-badge.tsx`, `wordmark.tsx`, `badge.tsx`,
|
||
`dropdown-menu.tsx`, `app/dashboard/page.tsx`) — `next.config.mjs`,
|
||
`package.json`, `globals.css`, Docker-filene ble bevisst IKKE overskrevet.
|
||
`login-form.tsx` fikk en kirurgisk patch (kun Wordmark flyttet til egen
|
||
fil, som V0 selv hadde gjort — all egen fetch-/feilhåndteringslogikk
|
||
urørt).
|
||
**`dashboard.tsx` sitt datalag skrevet om fra bunnen** (V0 leverte kun
|
||
mock `useState`): henter `/auth/me` for organisasjonsmedlemskap,
|
||
`/orgs/{id}/tournaments` per valgt org, `POST /orgs`/`POST
|
||
/orgs/{id}/tournaments` for opprettelse, `POST /auth/logout` for utlogging
|
||
— presentasjonskomponentene (kort, bytter, tomme tilstander) beholdt
|
||
uendret fra V0. `/verify`-siden oppdatert til å sende brukeren videre til
|
||
`/dashboard` etter vellykket innlogging (fantes ingen dit å gå før nå).
|
||
**Verifisert:** ekte typesjekket build, redeploy av kun `teecup_frontend`
|
||
(ingen Caddy-endring nødvendig denne gangen — ADR-016s mønster holder),
|
||
`teecup.teeoff.no/dashboard` → 200, `teeoff.no` upåvirket.
|
||
**Skrive-flyten bekreftet med EKTE data samme dag (brukeren testet selv,
|
||
ikke meg):** organisasjon "Tjøme Gents" og turnering "De Gamle er Eldst"
|
||
opprettet via UI-et mot den ekte `teecup_db` — statusmerket viste riktig
|
||
"Utkast", "Ingen datoer satt" håndtert korrekt (ingen krasj på manglende
|
||
dato), ett-org-visningen viste riktig uten unødvendig bytter-UI. Første
|
||
gang en HEL skrive-flyt (ikke bare lesing) er bevist ende-til-ende fra
|
||
frontend mot ekte produksjonsdata.
|
||
- **Lag/roster-skjerm LIVE (2026-07-18):** tredje V0-skjerm
|
||
(`/tournaments/[id]`), samme re-eksport-mønster som dashboard-runden —
|
||
diffet mot live-treet, tok kun inn `tournament-detail.tsx` og et
|
||
`Link`-basert `tournament-card.tsx` (navigasjon fra dashbordet).
|
||
**URL-design bevisst avvikende fra V0s forslag:** V0s genererte side leste
|
||
aldri `params.id` og hadde ingen organization_id i det hele tatt — holdt
|
||
derfor V0s flate `/tournaments/[id]`-struktur (i stedet for en nøstet
|
||
`/orgs/[orgId]/tournaments/[id]`, som ville krevd manuell ombygging ved
|
||
HVER fremtidig V0-reeksport) og la `org`+`name` til som søkeparametre i
|
||
`tournament-card.tsx` sin lenke — API-et krever organization_id på alle
|
||
team-/roster-kall (RLS).
|
||
**Reelt hull funnet FØR integrering, ikke etter:** V0-skjermen bygger inn
|
||
"fjern spiller"/"gjør til kaptein"-handlinger, men backend hadde KUN
|
||
GET/POST på `team_roster` — ingen DELETE eller PATCH. Spurte bruker
|
||
eksplisitt (samme mønster som andre scope-avklaringer denne økten) — svar:
|
||
bygg de to endepunktene nå. Lagt til i `app/routers/tournaments.py`:
|
||
`PATCH .../roster/{roster_id}` (bevisst enkel — setter/fjerner
|
||
`is_captain` på NØYAKTIG denne raden, håndhever IKKE "kun én kaptein per
|
||
lag", siden kaptein fortsatt bare er et merke, ikke en egen
|
||
autorisasjonsrolle) og `DELETE .../roster/{roster_id}` (204, idempotent
|
||
`NOT_FOUND` ved dobbel sletting — ikke krasj).
|
||
**`tournament-detail.tsx` sitt datalag skrevet om fra V0s mock:** henter
|
||
lag + roster (roster-radene bærer allerede `display_name`/
|
||
`handicap_index_snapshot` fra APIet, så V0s separate `poolById`-oppslag
|
||
ble fjernet som overflødig) og organisasjonens spillerpool
|
||
(`GET /orgs/{id}/players`, brukt til type-ahead ved "legg til spiller").
|
||
Alle fem mutasjonene (opprett lag, legg til eksisterende spiller, opprett
|
||
ny spiller inline + rostre, endre kaptein, fjern fra roster) kablet mot
|
||
ekte endepunkter — presentasjonskomponentene (kort, type-ahead,
|
||
fargevelger, bekreft-fjerning) beholdt uendret fra V0.
|
||
**Verifisert:** de to nye endepunktene testet mot en fersk
|
||
`teecup_scratch` (PATCH setter kaptein + riktig `NOT_FOUND` på ugyldig id,
|
||
DELETE gir 204 + idempotent `NOT_FOUND` ved gjentak, `test_isolation.sql`
|
||
fortsatt 12/12), ekte typesjekket frontend-build (5 ruter), redeploy av
|
||
BEGGE containere (backend-endepunktene er nye), `teecup.teeoff.no/dashboard`
|
||
→ 200, `teeoff.no` upåvirket. Selve skrive-flyten på `/tournaments/[id]`
|
||
(opprett lag/roster) ikke testet med ekte data i denne runden — venter på
|
||
brukeren, samme mønster som dashboard-rundens skrive-test.
|
||
- **ADR-017 + migrasjon 007 (2026-07-18):** brukeren reiste selvregistrering
|
||
rett etter at roster-skrive-flyten var bekreftet. Full ADR skrevet
|
||
(5 beslutninger: offentlig påmelding uten innlogging, e-post som
|
||
sammenkoblingsnøkkel mot forhåndsopprettede spillere, egen
|
||
`tournament_registration`-tabell atskilt fra `team_roster` med
|
||
konfigurerbar godkjenning/kapasitet/venteliste, utvidet spillerprofil +
|
||
obligatorisk samtykke, og `public_tournament_org()`). Migrasjon
|
||
`007_registration_and_player_fields.sql` skrevet og scratch-verifisert
|
||
(001→007 kjører rent, `test_isolation.sql` fortsatt 12/12).
|
||
**Reelt arkitekturproblem løst underveis, ikke bare skjema:** et
|
||
offentlig (uautentisert) påmeldingskall kjenner en turnering-id, men
|
||
ingen org-kontekst — og uten `app.current_org` slipper RLS ingen rader
|
||
gjennom, heller ikke oppslaget for å FINNE riktig org. Løst med en snever
|
||
`SECURITY DEFINER`-funksjon (`public_tournament_org`) som KUN eksponerer
|
||
uuid→uuid-koblingen. **Verifisert presist, ikke bare "kjørte uten feil":**
|
||
kalt funksjonen som `teecup_app`-rollen med ingen `app.current_org` satt
|
||
— ga korrekt org-id for en kjent turnering, `NULL` (ikke feil) for en
|
||
ukjent — OG et RÅTT `SELECT` på `tournament` på SAMME tilkobling/rolle ga
|
||
fortsatt 0 rader, som beviser RLS ikke er brutt generelt, bare dette ene
|
||
smale unntaket eksisterer.
|
||
**Kjørt mot ekte `teecup_db` 2026-07-18** (bruker bekreftet eksplisitt i
|
||
samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12.
|
||
- **Registrerings-API LIVE (2026-07-18), samme dag:** ny
|
||
`app/routers/registration.py` — `GET /public/tournaments/{id}` og
|
||
`POST /public/tournaments/{id}/register`, begge UTEN `get_current_user`
|
||
eller `get_authorized_org` (helt uautentisert, egen `/public`-prefiks,
|
||
bevisst atskilt fra `/orgs/...` i koden). Bruker `public_tournament_org()`
|
||
(migrasjon 007) til å slå opp org-kontekst FØR RLS kan håndheve noe.
|
||
`app/routers/players.py` utvidet med alle sju nye ADR-017-feltene
|
||
(`mobile`/`email`/`birth_date`/`nickname`/`country`/`club`/
|
||
`club_member_number`). `frontend/next.config.mjs` sin `rewrites()`
|
||
utvidet med `/public/*` (ADR-016s konsekvens: enhver ny API-prefiks MÅ
|
||
inn her).
|
||
**Ny brukers-oppdaget notat fanget FØR bygging, ikke etter:** brukeren
|
||
krevde eksplisitt at synlighet (offentlig/kun org/kun turnering-
|
||
deltakere) må være et VALG for fremtidige landingssider — notert grundig
|
||
i `FEATURE_BACKLOG.md` (koblet til samme åpne spørsmål for "Banter
|
||
Board"-feeden) FØR dette registrerings-API-et ble bygget, akkurat for at
|
||
det ikke skal gå i glemmeboken til landingsside-runden.
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (001→007,
|
||
`test_isolation.sql` 12/12): hele registreringsløpet testet reelt —
|
||
samtykke-avvisning (400), duplikat-avvisning (409 `DUPLICATE`),
|
||
e-post-matching mot en organisator-forhåndsopprettet spiller (BEKREFTET:
|
||
ingen duplikatrad, mobil fylt inn via `COALESCE`, `display_name` IKKE
|
||
overskrevet), kapasitet+`waitlist`-policy (→ `waitlisted`),
|
||
kapasitet+`closed`-policy (→ 409 `LIMIT_REACHED`), `registration_
|
||
requires_approval` (→ `pending`), utløpt frist (→ 409
|
||
`REGISTRATION_CLOSED`), og `confirmed_count` i `GET`-responsen talt
|
||
riktig (kun `confirmed`+`pending`, ikke `waitlisted`/avviste).
|
||
**Rullet ut live:** begge containere redeployet, `teecup.teeoff.no/
|
||
dashboard` og `/health` fortsatt 200, `teeoff.no` upåvirket, det
|
||
offentlige endepunktet bekreftet nåbart over ekte https (ukjent
|
||
turnering-id ga korrekt `404`/`NOT_FOUND`, ikke-destruktiv sjekk — selve
|
||
påmeldingsflyten med ekte data ikke testet mot prod i denne runden).
|
||
**E-post-basert kontosammenkobling LIVE, samme dag:** ny migrasjon
|
||
`008_link_player_by_email.sql` — `link_player_by_email(user_id, email)`,
|
||
samme `SECURITY DEFINER`-mønster som `public_tournament_org()` (007),
|
||
denne gangen for en tverr-org UPDATE i stedet for et lese-oppslag.
|
||
`verify_magic_link` (`app/routers/auth.py`) kaller den på HVER
|
||
innlogging (idempotent — funksjonens `WHERE user_id IS NULL` gjør
|
||
gjentatte kall til en no-op), ikke bare ved førstegangsopprettelse.
|
||
**Verifisert presist:** to separate org-er, hver med sin egen
|
||
organisator-opprettede "Kari"-rad (samme e-post, ulik store/små
|
||
bokstaver for å teste case-insensitivitet også) — ved Karis FØRSTE
|
||
innlogging ble BEGGE radene koblet til kontoen hennes, i to org-er hun
|
||
aldri har vært medlem av. Eksplisitt bekreftet: `organization_membership`
|
||
har NULL rader for henne etterpå — ren identitetskobling, ingen
|
||
privilegie-eskalering (å ha `player.user_id` satt gir ingen ny tilgang
|
||
gjennom `get_authorized_org`, som fortsatt krever ekte org-medlemskap
|
||
uavhengig av dette). Andre innlogging idempotent, ingen feil.
|
||
`test_isolation.sql` fortsatt 12/12. **Kjørt mot ekte `teecup_db`
|
||
2026-07-18**, bruker bekreftet eksplisitt, backend redeployet, live
|
||
sjekker OK, `teeoff.no` upåvirket.
|
||
**ADR-017s backend er dermed komplett** (registrering + kontokobling).
|
||
Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere
|
||
ADR-runde (se `FEATURE_BACKLOG.md`).
|
||
- **ADR-018 + migrasjon 009: landingssider, backend LIVE (2026-07-18),
|
||
samme dag:** trenivås synlighet (`tournament.visibility`:
|
||
`public`/`org`/`participants`, default `org` — trygg standard) +
|
||
`organization.public_profile`. **Reell presisering funnet underveis, ikke
|
||
antatt på forhånd:** RLS (`org_isolation`) beskytter kun TENANT-grenser
|
||
(org A ser aldri org B), IKKE innholds-synlighet innenfor riktig
|
||
org-kontekst — det eksisterende `GET /public/tournaments/{id}` (ADR-017)
|
||
leste allerede fullt innhold uten synlighetssjekk, fordi RLS er fornøyd
|
||
så snart org-konteksten er satt, uansett hvem som spør. `visibility`
|
||
håndheves derfor eksplisitt i `app/routers/registration.py`, på BÅDE
|
||
lesing og registrering (ADR-018 Beslutning D: kan du ikke se turneringen,
|
||
kan du heller ikke melde deg på den — bekreftet med bruker FØR bygging).
|
||
Ny `get_current_user_optional` i `app/auth.py` (som `get_current_user`,
|
||
men returnerer `None` i stedet for 401 — offentlige endepunkter skal
|
||
fungere for anonyme lesere også). Ny "deltaker"-autorisasjonsvei: en
|
||
innlogget bruker med `player.user_id` koblet (ADR-017) OG en
|
||
`tournament_registration`- eller `team_roster`-rad for NØYAKTIG den
|
||
turneringen får se `participants`-synlige turneringer, uansett
|
||
org-medlemskap.
|
||
**Ekte migrasjonsfeil funnet OG rettet UNDER scratch-verifisering, ikke
|
||
antatt riktig:** migrasjonen feilet først ("column slug already exists")
|
||
— `organization.slug` har ligget i skjemaet siden migrasjon 001
|
||
("f.eks. subdomene/URL-vennlig", allerede med en plain `UNIQUE`), noe jeg
|
||
hadde oversett fullstendig og forsøkt å legge til på nytt. Rettet ved å
|
||
fjerne den doble `ADD COLUMN` + den overflødige partielle unik-indeksen
|
||
(001 sin plain `UNIQUE` dekker "unik når satt" allerede, siden Postgres
|
||
behandler NULL som distinkt), beholde kun de nye `CHECK`-constraintene.
|
||
Kjørte rent på ny etter fiksen. **Lærdom:** grep alltid eksisterende
|
||
skjema for feltnavn FØR en ny migrasjon skrives, ikke bare stol på
|
||
hukommelsen om hva som "sikkert" ikke finnes fra før.
|
||
Ny tabell `tournament_sponsor` (navn+lenke aktivt, `logo_key` inert til
|
||
MinIO-runden — samme med `tournament.hero_image_key`). Tredje
|
||
`SECURITY DEFINER`-bro i prosjektet: `public_org_by_slug()` (etter
|
||
`public_tournament_org` 007, `link_player_by_email` 008) — returnerer
|
||
`NULL` for BÅDE "finnes ikke" og "finnes, men er privat", samme
|
||
anti-enumerering som magic-link.
|
||
**Fylte også et implisitt hull oppdaget underveis:** ADR-en beskrev
|
||
hvordan synlighet skulle håndheves, men ingen tidligere runde hadde bygget
|
||
noen vei for organisator til faktisk å SETTE disse feltene. Lagt til:
|
||
`PATCH /orgs/{id}/tournaments/{id}` (visibility/description/
|
||
registrerings-innstillinger, ekte PATCH-semantikk via Pydantic sin
|
||
`exclude_unset` — et utelatt felt nullstilles IKKE), `PATCH /orgs/{id}`
|
||
(slug/public_profile), full sponsor-CRUD. `_fetch_sessions()` trukket ut
|
||
som delt hjelpefunksjon i `tournaments.py` (delt mellom den innloggede
|
||
og den nye offentlige `GET /public/tournaments/{id}/sessions` — blind
|
||
draw-hemmelighold, ADR-013, arves automatisk, ikke reimplementert).
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (001→009,
|
||
`test_isolation.sql` 12/12): hele synlighetsmatrisen testet med ekte
|
||
HTTP-kall — anonym avvist på `org`-synlig turnering (både lesing OG
|
||
registrering), `PATCH` til `public` + beskrivelse + sponsor fungerte,
|
||
anonym lesing fungerte deretter, `participants`-synlighet bekreftet
|
||
reell chicken-and-egg-konsekvens av Beslutning D (ingen kan selv-
|
||
registrere seg til en `participants`-synlig turnering, kun organisator
|
||
kan legge til direkte — korrekt, ikke en bug), en organisator-rostret
|
||
spiller som logget inn fikk tilgang, en tilfeldig innlogget FREMMED
|
||
(ikke deltaker) ble fortsatt avvist, org-landingsside viste KUN
|
||
`public`-synlige turneringer, `CHECK`-constraint (`public_profile`
|
||
krever `slug`) avvist korrekt, ugyldig slug-format avvist av Pydantic,
|
||
sponsor-sletting fungerte.
|
||
**Kjørt mot ekte `teecup_db` 2026-07-18**, bruker bekreftet eksplisitt,
|
||
backend redeployet, live sjekker OK, `teeoff.no` upåvirket. Ingen
|
||
frontend-endring nødvendig for selve API-tilgangen (det brede
|
||
`/public/:path*`-mønsteret fra ADR-016 dekker allerede `/public/orgs/*`).
|
||
**Gjenstår:** selve landingsside-SKJERMENE i frontend (V0), og
|
||
MinIO/bildeopplasting — bevisst utsatt, egen runde.
|
||
- **Offentlig turnering-landingsside LIVE (2026-07-18), samme dag:** fjerde
|
||
V0-skjerm (`components/public-tournament.tsx`, ny rute `/t/[id]`) —
|
||
banner (bevisst permanent farge-/gradient-utseende, ikke en "bilde
|
||
kommer"-plassholder), presenterende tekst, status (datoer + "X av Y
|
||
plasser"), program, sponsorer (navn+lenke), og et påmeldingsskjema med
|
||
navn+e-post synlig først og resten bak en "flere detaljer"-utvidelse —
|
||
tre distinkte bekreftelsestilstander (bekreftet/venteliste/godkjenning
|
||
venter), ikke én generisk "takk".
|
||
**Ingen ny reeksport-kollisjon denne gangen** — kun étt genuint nytt
|
||
filnavn (`public-tournament.tsx`), resten var kjent V0-revert av
|
||
allerede-tilpassede filer, samme mønster som før.
|
||
**V0 opprettet komponenten, men ingen rute** — la selv til `app/t/[id]/
|
||
page.tsx` (bevisst en FLAT `/t/[id]`-sti, ikke nøstet under `/orgs/...`
|
||
som den innloggede turnering-detalj-siden, siden det offentlige API-et
|
||
kun trenger turnering-id, ikke org-id).
|
||
**Datalaget skrevet om fra V0s mock:** ekte `fetch` mot
|
||
`GET /public/tournaments/{id}` + `/sessions`, ekte `POST .../register`.
|
||
Håndterer 403 (`NOT_VISIBLE`, ADR-018) og 404 med en egen tilgang-avvist-
|
||
tilstand V0 ikke hadde bedt om (fantes ikke i prompten, men trengs for at
|
||
siden faktisk skal fungere for `org`/`participants`-synlige turneringer).
|
||
Mappet `status:"waitlisted"` fra API-et til komponentens `"waitlist"`, og
|
||
skjemaets norske kjønnsvalg ("kvinne"/"mann"/"annet") til API-ets
|
||
`^[mfx]$`-mønster — to reelle navnekollisjoner mellom V0s UI-språk og
|
||
API-kontrakten, ikke bare et rett-frem felt-for-felt-uttrekk.
|
||
**Reell driftsfeil funnet OG rettet under scratch-test, ikke i
|
||
produksjon:** `pnpm install` (uten `--ignore-scripts`) i dev-server-
|
||
testoppsettet feilet stille med tom logg og exit 1 — corepack hadde
|
||
hentet en NY pnpm-versjon (11.13.1 → 11.14.0) siden sist, som gjør
|
||
"ignored builds"-varselet til en hard feil i stedet for bare en
|
||
advarsel. Rettet ved å bruke samme `--ignore-scripts`-flagg som
|
||
`frontend/Dockerfile` allerede bruker (upåvirket av denne — bekreftet
|
||
ved at selve prod-buildet fortsatt gikk rent). **Lærdom:** `Dockerfile`
|
||
sin pinning av *kode* er ikke det samme som å pinne *verktøyene rundt*
|
||
(corepack henter alltid siste pnpm) — verdt å huske neste gang et
|
||
scratch-dev-server-oppsett plutselig feiler uten åpenbar grunn.
|
||
**Verifisert grundig mot fersk `teecup_scratch` + en ekte kjørende
|
||
frontend-dev-server** (ikke bare `next build`): ekte turnering med
|
||
beskrivelse, kapasitet, sponsor og økt opprettet via API-et, hentet
|
||
gjennom frontend-proxyen (ikke direkte mot backend) og bekreftet
|
||
byte-for-byte riktig — inkludert en ekte `POST`-registrering som økte
|
||
`confirmed_count` fra 0 til 1.
|
||
**Rullet ut live**, kun `teecup_frontend` (ingen backend-endring denne
|
||
runden), `teeoff.no` upåvirket.
|
||
**Gjenstår:** org-landingssiden (egen V0-prompt), Open Graph-metadata for
|
||
deling, MinIO/bilder.
|
||
- **Offentlig klubb-landingsside LIVE (2026-07-18), samme dag:** femte og
|
||
siste V0-skjerm i ADR-018 (`components/public-club.tsx`, ny rute
|
||
`/clubs/[slug]`). Samme banner-språk som turnering-siden, liste over
|
||
klubbens turneringer (gjenbrukte eksisterende `TournamentCard`), egen tom-
|
||
tilstand.
|
||
**Reell delt-komponent-kollisjon løst, ikke duplisert bort:**
|
||
`TournamentCard` var bygget for KUN den innloggede konteksten (krevde
|
||
`orgId`, lenket til `/tournaments/{id}?org=...`). I stedet for en egen
|
||
kopi av kortet for den offentlige siden, gjort `orgId` valgfri —
|
||
satt (dashbordet) gir innlogget lenke, utelatt (klubbsiden) gir
|
||
`/t/{id}` i stedet. Samme kort, to kontekster, ingen duplisering.
|
||
Bekreftet dashbordets egen bruk uendret/upåvirket etterpå.
|
||
**V0 opprettet denne gangen selv en rute** (`app/clubs/[id]/page.tsx`,
|
||
uten noen props sendt inn i det hele tatt) — men navnga parameteren
|
||
`[id]` selv om den faktisk er en SLUG (`public_org_by_slug()`, ADR-018).
|
||
Skrev selv en ny, riktig `app/clubs/[slug]/page.tsx` i stedet for å bruke
|
||
V0s (feilnavngitte og prop-løse) versjon.
|
||
**Verifisert mot fersk `teecup_scratch` + ekte kjørende frontend-dev-
|
||
server:** org med slug+`public_profile`, én turnering satt `public`, én
|
||
latt stå på default `org` — klubbsiden viste GJENNOM frontend-proxyen
|
||
kun den ene offentlige turneringen, den org-private var korrekt
|
||
usynlig (samme filtermønster som API-et selv, bekreftet fra
|
||
klientsiden også). Ukjent slug ga korrekt 404.
|
||
**Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket.
|
||
**ADR-018s planlagte skjermer er dermed komplette.** Gjenstår: Open
|
||
Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere
|
||
runder.
|
||
- **Open Graph-metadata LIVE (2026-07-18), samme dag — ADR-018 dermed
|
||
helt ferdig:** `generateMetadata()` lagt til på `/t/[id]` og
|
||
`/clubs/[slug]` (ekte tittel/beskrivelse fra API-et, `og:site_name`,
|
||
trygg fallback-tittel ved ukjent id/slug — feil her skal ALDRI hindre
|
||
selve siden i å laste).
|
||
**Reell driftsfeil funnet FØR den nådde produksjon, ikke etter:**
|
||
`generateMetadata()` kjører server-side ved REQUEST-tid, ikke i
|
||
nettleseren — går derfor IKKE gjennom `next.config.mjs` sin
|
||
`rewrites()` (som kun gjelder nettleser-trafikk inn til Next.js-
|
||
serveren). Måtte derfor lese `TEECUP_API_ORIGIN` direkte, men den
|
||
variabelen fantes KUN i `Dockerfile` sitt builder-steg — `ENV` satt i
|
||
ett `FROM`-steg arves ikke til et senere. Rettet ved å sette samme
|
||
`ARG`/`ENV` på nytt i runner-steget også.
|
||
**Verifisert presist at fiksen faktisk virker, ikke bare at bygget gikk
|
||
gjennom:** bygget det EKTE produksjonsimaget (ikke dev-server) pekt mot
|
||
en scratch-backend med en ekte offentlig turnering+org, hentet den
|
||
faktiske server-rendrede HTML-en og bekreftet ekte `<title>`/`og:title`/
|
||
`og:description` — ikke bare at TypeScript kompilerte. Ukjent
|
||
turnering-id ga korrekt trygg fallback-tittel.
|
||
**Bevisst utenfor omfang:** `og:image` — ingen ekte bilde finnes ennå
|
||
(MinIO-runden). La til `metadataBase` i `app/layout.tsx` nå likevel, som
|
||
forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en
|
||
egen fiks da.
|
||
**Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket.
|
||
- **MinIO-runden LIVE (2026-07-18), samme dag — ADR-018 dermed HELT
|
||
ferdig, ingenting utsatt igjen:** ny `teecup-minio`-tjeneste (persistent
|
||
volum, genererte credentials i `.env`), backend-endepunkter for ekte
|
||
bildeopplasting til `tournament.hero_image_key`/`tournament_sponsor.
|
||
logo_key` (som lå inerte siden migrasjon 009), offentlige API-svar bygger
|
||
nå fulle URL-er, `og:image` koblet på i `/t/[id]` sin `generateMetadata`.
|
||
**Sent, men viktig presisert krav underveis:** brukeren avbrøt en
|
||
verktøyskall midtveis for å presisere at ALLE bilder skal konverteres
|
||
til AVIF for plassbesparelse. Snudde HELE opplastingsarkitekturen på
|
||
dette: droppet den opprinnelige planen om presignerte URL-er (nettleser
|
||
laster opp DIREKTE til MinIO) til fordel for ekte multipart-opplasting
|
||
GJENNOM API-et, som konverterer til AVIF (Pillow + pillow-avif-plugin)
|
||
FØR lagring. Dette forenklet arkitekturen betydelig: kun ÉN MinIO-klient
|
||
trengs nå (før: to separate klient-oppsett for henholdsvis internt
|
||
admin-arbeid og offentlig presignering), og Caddy sin nye rute trenger
|
||
ikke lenger bevare Host-headeren presist (relevant kun for SigV4-
|
||
signaturverifisering av presignerte URL-er, ikke for anonym public-read).
|
||
**To reelle feil funnet UNDER scratch-verifisering, aldri i produksjon:**
|
||
(1) `pillow-avif-plugin` krever ingen ekstra systempakker i
|
||
`python:3.12-slim` -- verifisert med et frittstående encode/decode-
|
||
rundtrip FØR det ble tatt i bruk i selve API-et. (2) MinIO validerer
|
||
`Host`-headeren STRENGT og avviser understrek som ugyldig vertsnavn --
|
||
et tjenestenavn med understrek (`teecup_minio`, konsistent med
|
||
`teecup_api`/`teecup_frontend`) feilet umiddelbart ved oppstart
|
||
("Invalid Request (invalid hostname)"). Bekreftet presist ved å teste
|
||
samme oppsett med bindestrek i stedet (`teecup-minio`) -- fungerte
|
||
umiddelbart. Alle referanser rettet til bindestrek FØR noe ble forsøkt
|
||
mot ekte infrastruktur.
|
||
**Caddy:** ny `/teecup-media/*`-rute på det EKSISTERENDE
|
||
`teecup.teeoff.no`-blokket (IKKE et nytt subdomene -- en tidlig sjekk
|
||
avdekket at `media.teecup.teeoff.no` fantes som en wildcard DNS-post,
|
||
men pekte til en IPv6-adresse denne serveren ikke har i det hele tatt;
|
||
path-prefiks på et allerede fungerende domene unngikk hele den DNS-
|
||
avhengigheten). Samme stale-inode-oppførsel som alle tidligere Caddy-
|
||
runder -- full `docker restart teeoff_caddy`, brukeren bekreftet
|
||
eksplisitt. `teeoff.no` upåvirket.
|
||
**Verifisert grundig, i flere lag:** frittstående AVIF-encode/decode-
|
||
test, full scratch-kjede (fersk Postgres + en ISOLERT scratch-MinIO-
|
||
container) med et ekte opplastet bilde -- bekreftet konvertert til
|
||
gyldig AVIF (800×400, 406 bytes for et helfarget testbilde), bekreftet
|
||
lagret med riktig nøkkel, bekreftet lesbart ANONYMT direkte mot MinIO
|
||
(uten Caddy, isolerer bucket-policyen), bekreftet `hero_image_url`/
|
||
`logo_url` bygget riktig i det offentlige API-svaret. Alle tre
|
||
valideringsveier testet (ugyldig content-type, korrupt bildeinnhold,
|
||
for stor fil >8MB) -- alle ga korrekt `VALIDATION_FAILED`. Etter
|
||
Caddy-omstart: et ekte anonymt kall mot `/teecup-media/...` på
|
||
produksjonsdomenet ga en ekte MinIO S3-XML-feilrespons (`NoSuchKey` for
|
||
en scratch-nøkkel som naturligvis ikke finnes i prod) -- beviser ruten
|
||
treffer MinIO selv, ikke frontend sin egen 404-side.
|
||
`test_isolation.sql` fortsatt 12/12 gjennom hele runden.
|
||
**Bevisst utenfor omfang:** ingen faktisk opplasting av et EKTE bilde
|
||
til en EKTE, live turnering i denne runden (ville krevd å skrive
|
||
test-data i brukerens ekte konto uten å bli spurt) -- tilbudt, ikke
|
||
utført. Selve dra-og-slipp-opplastingsskjermen i frontend (V0) er
|
||
fortsatt ikke bygget, som avtalt fra starten av runden.
|
||
|
||
- **Program-skjerm (økter/tidsplan), bygget og SCRATCH-verifisert, IKKE
|
||
live ennå (2026-07-18):** sjette V0-skjerm, første i "bygg i rekkefølgen
|
||
ting brukes"-serien (etter lag/roster: program → blind draw → scorekort →
|
||
leaderboard). `components/tournament-program.tsx`, ny rute
|
||
`/tournaments/[id]/program`. Tidslinje over økter + opprett-skjema
|
||
(format, hullomfang, scoringsmodus, poeng, klokkeslett+intervall,
|
||
starthull, kollapsbar "avansert"-seksjon med ADR-014s fire brytere).
|
||
**Reelt blokkerende hull funnet FØR integrering:** `SessionCreate.
|
||
course_id` er påkrevd, men INGEN endepunkt kunne noensinne produsere en —
|
||
ADR-004s teeoff-integrasjon er fortsatt bare vedtatt (ingen utgående
|
||
HTTP-klient finnes). Spurte bruker eksplisitt — svar: bygg enkel
|
||
course-CRUD nå. Ny `app/routers/courses.py` (`GET`/`POST /orgs/{id}/
|
||
courses`, kun `source='custom'`). Program-skjemaet fikk et
|
||
type-ahead-felt for bane (samme mønster som spiller-type-ahead i
|
||
roster-skjermen).
|
||
**Reell korrekthetsfeil rettet FØR integrering:** V0-promptet mitt ba om
|
||
ett generisk "Scramble"-valg, men skjemaets `CHECK`-constraint og
|
||
`handicap_engine.py` sin `Format`-enum krever `scramble_2`/`scramble_4`
|
||
som distinkte verdier -- ren `"scramble"` avvises med 400. Rettet i
|
||
frontend-mappingen til to segment-knapper.
|
||
**`allowance_override`-JSON-formen verifisert eksakt** mot
|
||
`app/handicap.py` sin `parse_allowance_config`/`_strategy_from_json`
|
||
(`{type: "combined"|"per_player", percentage: 0..1}`, ikke en flat
|
||
prosent) -- frontend velger riktig `type` ut fra om formatet er
|
||
side-enhet eller spiller-enhet, konverterer 0–100-skjemafelt til 0–1.
|
||
**Scratch-infrastruktur denne runden, bevisst forskjellig fra tidligere:**
|
||
brukte en ISOLERT `teecup_app_scratch`-rolle (`GRANT teecup_app TO
|
||
teecup_app_scratch`) i stedet for den ekte `teecup_app`-rollen -- den er
|
||
nå cluster-global og produksjonskritisk (delt Postgres-instans med
|
||
`teecup_db`), så et eldre plandokuments "drop teecup_app-rolle"-
|
||
opprydning (skrevet FØR go-live) er utdatert og ble bevisst IKKE fulgt.
|
||
Egen isolert scratch-MinIO-container også (app-oppstart krever en
|
||
nåbar MinIO for `ensure_bucket()`).
|
||
**Verifisert:** courses opprettet+listet, kryss-org-isolasjon bekreftet,
|
||
økt med klokkeslett, økt med `scramble_4`+full `allowance_override`-
|
||
rundtur, gammel `"scramble"`-verdi avvist (400), `test_isolation.sql`
|
||
12/12, ekte typesjekket PRODUKSJONSBUILD (samme `Dockerfile` som faktisk
|
||
deployes) kjørt og bekreftet.
|
||
**Diffet V0-eksporten mot live-treet FØR noe ble tatt inn** (samme mønster
|
||
som alle tidligere runder): kun tre reelt nye filer, resten forventede
|
||
full-reverts, ikke rørt. Fjernet V0s dev-only forhåndsvisnings-toggle;
|
||
lagt til fanerad ("Lag og spillere" / "Program") i BEGGE skjermene siden
|
||
V0 ikke visste om den andre når den ble generert i egen prompt.
|
||
**Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: `docker
|
||
compose up -d --build teecup_api teecup_frontend` (kun disse to, `teecup-
|
||
minio` urørt). Verifisert: begge containere boot-et rent (`Application
|
||
startup complete`, Next.js `Ready`), `teecup.teeoff.no/health` og
|
||
`/dashboard` → 200, `teeoff.no` upåvirket (200).
|
||
- **ADR-004 (teeoff-banedata) kartlagt, IKKE bygget (2026-07-18):** brukeren
|
||
påpekte rett etter program-skjerm-rullingen at ADR-004s teeoff-
|
||
integrasjon fortsatt bare er vedtatt, ikke bygget (kun `source='custom'`
|
||
finnes). Kartlagt `/opt/teeoff/backend/main.py` (annet repo, kun lest):
|
||
`GET /api/facilities/{slug}` (offentlig, INGEN auth/API-nøkkel) returnerer
|
||
allerede alt teecup trenger — `courses[].holes[]` (`par`, `hcp_index`
|
||
= stroke index), `courses[].tees[]` (`name`, `cr_men`/`slope_men`,
|
||
`cr_women`/`slope_women` — kjønnsdelt WHS-rating). CORS-lista på teeoff-
|
||
siden ekskluderer teecups origin, men er IRRELEVANT for et server-til-
|
||
server-kall (kun nettleser-fetch rammes av CORS). Ingen dokumentasjon
|
||
(README/OpenAPI) finnes for dette API-et i teeoff-repoet — kartleggingen
|
||
over er basert på å lese koden direkte. Stabil identifikator: `facilities.
|
||
slug` (f.eks. `borregaard-golfklubb`) — selve banen har kun en intern
|
||
serial-id, ingen egen slug, så `course.external_course_ref` må bære
|
||
facility-slug + bane-id sammen.
|
||
**Oppdatering samme dag: brukeren ba meg ta fatt på dette NÅ** (bevisst
|
||
sidesprang fra "bygg i rekkefølgen ting brukes"-planen — blind draw-
|
||
skjermen er fortsatt neste steg i den planen ETTERPÅ, ikke droppet).
|
||
Design besluttet og skrevet som **ADR-019** (se ARCHITECTURE_DECISIONS.md):
|
||
import (kopi) ved organisators eksplisitte valg, ikke live oppslag ved
|
||
hver bruk — fryser data på importtidspunktet, samme reproduserbarhets-
|
||
prinsipp som handicap-snapshotten (ADR-007). Server-til-server-kall
|
||
(`teecup_api` → `http://teeoff_api:8000`, internt Docker-nettverk, ingen
|
||
auth trengs). Ny migrasjon `010` (unik `external_course_ref` per org,
|
||
hindrer dupliserte importer). Se ADR-019 for alle fem delbeslutningene.
|
||
**Bygget, scratch-verifisert MOT EKTE `teeoff_api`** (ikke en simulert
|
||
respons — ekte HTTP-kall til den kjørende produksjonscontaineren, kun
|
||
lesing): søkte opp "Borregaard" i teeoff sine 174 publiserte anlegg,
|
||
hentet Borregaard Golfklubb sin hovedbane (18 hull, 4 tee-farger ×
|
||
kjønn), importerte den til en scratch-org — alle 18 hull med riktig
|
||
par/stroke-index (1-18, unik), alle 8 tee/tee_rating-rader med riktig
|
||
full_18 course/slope-rating verifisert direkte i databasen, opprettet
|
||
deretter en ekte økt med den importerte banen som `course_id` (beviser
|
||
hele veien til handicap-motoren fungerer, ikke bare selve importen).
|
||
Reimport av samme bane korrekt avvist (409 DUPLICATE, migrasjon 010 sin
|
||
indeks). Kryss-org-isolasjon bekreftet, ukjent teeoff-slug ga korrekt 404,
|
||
`test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild av
|
||
frontend-utvidelsen (bane-søk i program-skjemaet: søk anlegg → velg bane →
|
||
importer, samme UI-mønster som spiller-/bane-type-ahead ellers i appen).
|
||
**Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: migrasjon 010
|
||
kjørt mot ekte `teecup_db` (kun én ny partiell unik-indeks, ingen
|
||
eksisterende rader rørt), begge containere bygget+redeployet, live
|
||
sjekker OK, `teeoff.no` upåvirket. **"Bygg i rekkefølgen ting brukes"-
|
||
planen gjenopptas nå** — blind draw-skjermen er neste steg.
|
||
**Reell produksjonsbug funnet OG fikset samme dag, rapportert av bruker
|
||
som faktisk brukte funksjonen:** brukeren klikket seg korrekt via
|
||
dashbord → turnering → Program-fane (bekreftet med skjermbilde + full
|
||
klikk-sti, ikke gjettet), åpnet "Hent bane fra teeoff", søkte "Tjøme", og
|
||
fikk feilmeldingen "Mangler organisasjon i lenken" — URL-en hadde da
|
||
MISTET `?org=...&name=...`. Root-cause: `OfficialCourseSearch` sitt eget
|
||
søke-`<form onSubmit={runSearch}>` var rendret INNI `CreateSessionCard`
|
||
sitt eksisterende `<form onSubmit={handleSubmit}>` -- nestede
|
||
`<form>`-elementer er ugyldig HTML. Nettleseren slår sammen de to
|
||
skjemaene i den faktiske DOM-en, så "Søk"-knappen submittet i praksis det
|
||
YTRE økt-skjemaet som en ekte native side-navigasjon (GET til gjeldende
|
||
sti, ingen navngitte felt => tom spørrestreng) -- dette vasket bort
|
||
org-parameteren og landet brukeren på siden sin egen org-guard. **Fikset**
|
||
ved å fjerne det indre `<form>`-elementet helt (vanlig `<div>` +
|
||
Enter-tast-håndtering på inputet + `type="button"` i stedet for
|
||
`type="submit"` på søkeknappen) -- gjør nestede skjemaer strukturelt
|
||
umulig fremover for denne komponenten. Ekte typesjekket
|
||
produksjonsbuild kjørt på nytt, kun `teecup_frontend` redeployet (ingen
|
||
backend-endring). **Lærdom for fremtidige skjermer:** en ny
|
||
søk-/underskjema-widget som skal plasseres INNI et eksisterende skjema
|
||
(slik som denne bane-søk-widgeten ligger inni økt-opprett-skjemaet) må
|
||
ALDRI være et eget `<form>` -- bruk `<div>` + eksplisitt
|
||
klikk-/Enter-håndtering.
|
||
|
||
- **Organisator-overstyring i `team_authz.py`, LIVE (2026-07-18):** reist av
|
||
brukeren rett før blind draw-skjermen skulle designes: `app/team_authz.py`
|
||
sin `user_may_act_for_team` krevde tidligere en `team_roster`-rad med
|
||
lenket bruker-konto — ingen vei for organisatoren til å låse/legge til
|
||
deltakere/føre score hvis ingen spiller hadde logget inn ennå (vanlig
|
||
tidlig i en turnering). Utvidet til også å godta org-eier/admin
|
||
(`organization_membership.role IN ('owner','admin')`), på ETHVERT lag,
|
||
ingen unntak for at organisatoren selv er rostret på motstanderlaget.
|
||
**Det unntaket ble bevisst vurdert og avvist** (brukeren spurte selv om
|
||
det, jeg anbefalte det opprinnelig, men vi kom sammen frem til at det ville
|
||
skapt en verre låsning: er organisatoren spillende på Lag A og
|
||
Lag B heller ikke har noen innlogget spiller, ville Lag B blitt helt
|
||
låst ute) — TeeCup er et tillitsbasert klubb-/vennegjeng-verktøy, ikke en
|
||
sikkerhetsgrense mot en fiendtlig organisator som uansett allerede ser
|
||
begge lags fulle troppe-liste (blind draw skjuler kun selve
|
||
kamp-paringen). Alle 5 kallsteder oppdatert (matches.py sin
|
||
add_participant/lock_lineup, scoring.py sin submit_hole_score/
|
||
submit_hole_result). **Verifisert grundig i scratch:** org-eier uten
|
||
roster kan nå låse BEGGE lag + føre score, en vanlig 'member'-rolle
|
||
fortsatt blokkert (uendret), en rostret spiller fungerer uendret
|
||
uavhengig av org-rolle. `test_isolation.sql` 12/12. Ingen migrasjon (ren
|
||
Python-endring) — kun `teecup_api` redeployet, `teeoff.no` upåvirket.
|
||
|
||
- **Tee-endepunkter bygget og LIVE (2026-07-18), rett før blind draw-skjermen:**
|
||
fant et hull som blokkerte selve blind draw-flyten: `match_participant.
|
||
tee_id` er påkrevd, men det fantes INGEN `GET`-vei for å liste en banes
|
||
tee-er (selv offisielt importerte baner har tee-rader, men ingenting
|
||
eksponerte dem) OG egendefinerte («custom») baner har ALDRI hatt noen
|
||
vei til å FÅ tee-er i det hele tatt — dette er ikke noe ADR-019 innførte,
|
||
det var et hull som fantes fra før custom-baner ble lagt til i første
|
||
omgang, bare usynlig til nå. Konsekvens før fiksen: en turnering satt opp
|
||
på en manuelt navngitt bane kunne ALDRI få en ekte deltaker lagt til på
|
||
noen match. **Presisering (brukeren spurte eksplisitt):** dette er IKKE
|
||
noe som må løses i teeoff.no sin kode — egendefinerte baner er per
|
||
definisjon baner teeoff ikke kjenner til, så dette er en ren
|
||
teecup-intern funksjon, uavhengig av ADR-019-integrasjonen.
|
||
Ny `GET/POST /orgs/{id}/courses/{id}/tees` i `app/routers/courses.py`.
|
||
`POST` avviser eksplisitt forsøk på offisielle baner (400
|
||
`VALIDATION_FAILED` — de får tee-ene sine fra teeoff-importen, ikke
|
||
manuelt). Kun full_18-rating dekket (samme begrunnelse som ADR-019
|
||
Beslutning D). **Bevisst UTENFOR omfang denne runden, egen senere sak:**
|
||
hull-/stroke-index-data for egendefinerte baner (`hole`-tabellen forblir
|
||
tom for custom-baner) — trengs for korrekt slagfordeling i SCORING-fasen
|
||
(ADR-008), ikke i blind draw, så det løses naturlig når scorekort-
|
||
skjermen bygges (samme "bygg i rekkefølgen ting brukes"-logikk).
|
||
**Verifisert grundig i scratch:** tom tee-liste på fersk custom-bane,
|
||
opprett+list tee på custom-bane, offisiell bane viser alle 8 importerte
|
||
tee-er, manuell tee-opprettelse avvist på offisiell bane, og — den
|
||
faktiske payoff-en — en deltaker lagt til en match på en custom-bane-økt
|
||
for FØRSTE gang noensinne (`POST .../matches/{id}/participants` lykkes
|
||
nå med en tee_id fra en nyopprettet custom-tee). `test_isolation.sql`
|
||
12/12. Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no`
|
||
upåvirket.
|
||
|
||
- **Blind draw-skjermen LIVE (2026-07-18):** syvende V0-skjerm,
|
||
`components/session-blind-draw.tsx`, ny rute
|
||
`/tournaments/[id]/sessions/[sessionId]`. To lag-kolonner, hver med sine
|
||
matcher ("flights"), legg til/fjern spiller+tee per plass (antall plasser
|
||
avhenger av format), lås-knapp med bekreftelse, avslørings-visning når
|
||
begge lag har låst. Program-skjermens økt-kort er nå klikkbare inn hit.
|
||
**Datamodell tilpasset fra V0s mock til API-ets faktiske sett-modell:**
|
||
V0 designet faste, indekserte "Slot"-arrays (kontrollert skjema-state);
|
||
API-et har verken PATCH på `match_participant` eller noen slot-indeks —
|
||
kun opprett/slett av en løs deltaker-mengde per side. Løst med en
|
||
"legg til spiller"-inline-form (samme mønster som spiller-/bane-type-ahead
|
||
ellers i appen) i stedet for faste dropdown-rader; funksjonelt likeverdig,
|
||
strukturelt riktigere for API-ets faktiske form.
|
||
**To reelle hull funnet og fikset FØR/UNDER integrering:**
|
||
1. Ingen `DELETE` fantes for `match_participant` — en kaptein kunne aldri
|
||
angre et valg før låsing uten å etterlate en foreldreløs rad. Ny
|
||
`DELETE /orgs/{id}/matches/{match_id}/participants/{participant_id}`
|
||
(samme ALREADY_LOCKED/NOT_ROSTERED_ON_TEAM-sjekker som opprett).
|
||
2. **"Skriv blindt"-hull, funnet under selve scratch-testingen (ikke bare
|
||
tenkt ut på forhånd):** forrige rundes org-admin-overstyring i
|
||
`team_authz.py` lot en organisator LEGGE TIL deltakere på et lag de
|
||
ikke selv er rostret på, men `app/blind_draw.py` sin `own_team_ids()`
|
||
sjekket KUN rostret spiller for SYNLIGHET — så organisatoren så aldri
|
||
sine egne tilføyelser igjen før begge lag hadde låst. Fikset ved å gi
|
||
`own_team_ids()` samme owner/admin-utvidelse som `user_may_act_for_
|
||
team()` — en org-admin ser nå BEGGE lag umiddelbart (konsistent med at
|
||
de uansett allerede har full tilgang, se forrige rundes resonnement),
|
||
mens en faktisk rostret kaptein fortsatt kun ser sitt eget lag før
|
||
reveal. Kun ett reelt kallsted (`matches.py` sin `list_matches`).
|
||
**Tredje, urelatert bug fanget under samme scratch-økt og fikset på
|
||
brukerens eksplisitte forespørsel:** `POST .../matches/{id}/participants`
|
||
krasjet med en rå 500 (`TypeError` i `handicap_engine.py` sin
|
||
`course_handicap_raw`) hvis spilleren manglet `handicap_index` OG
|
||
`use_handicap` var på (standard) — preeksisterende, ikke noe denne
|
||
runden introduserte. Fikset i `app/routers/matches.py` sin
|
||
`add_participant`: sjekker nå `handicap_index_snapshot IS NULL` EKSPLISITT
|
||
FØR innsetting når `use_handicap` er sann, avviser med en klar
|
||
`VALIDATION_FAILED` (400) i stedet for å krasje. Bekreftet at
|
||
`use_handicap=false` fortsatt tillater en spiller uten handicap
|
||
(scratch-spill), og at en spiller MED handicap fortsatt fungerer uendret.
|
||
**Verifisert grundig i scratch, flere runder:** DELETE-endepunktet
|
||
(fjern+idempotent 404 ved gjentak+409 etter lås), synlighetsfikset (org-
|
||
admin ser begge sider, rostret kaptein ser fortsatt kun eget lag),
|
||
null-handicap-fikset (avvist rent, scratch-modus upåvirket, normal
|
||
spiller upåvirket), full opprett-match→legg-til-deltaker→lås→avslør-
|
||
syklus ende-til-ende. `test_isolation.sql` 12/12 etter hver runde. Ekte
|
||
typesjekket produksjonsbuild.
|
||
**Rullet ut live**, bruker bekreftet eksplisitt: begge containere
|
||
bygget+redeployet (ingen migrasjon), `teeoff.no` upåvirket.
|
||
|
||
- **Backend-forarbeid for scorekort-skjermen, LIVE (2026-07-18):** to hull
|
||
funnet ved gjennomlesing av `scoring.py` FØR V0-prompten ble skrevet,
|
||
samme føre-var-mønster som resten av økten.
|
||
1. **Hull-/stroke-index-data for egendefinerte baner** (bevisst utsatt fra
|
||
tee-rundens status-notat) — `hole`-tabellen har ALDRI hatt noen vei inn
|
||
for custom-baner, som blokkerte `stroke`-scoringsmodus helt (`hole_
|
||
result`-modus trenger den ikke). Ny `GET/POST /orgs/{id}/courses/{id}/
|
||
holes` i `app/routers/courses.py` — engangs alle-18-på-en-gang
|
||
(avviser feil antall, avviser hvis banen allerede har hull, avviser på
|
||
offisielle baner som får hullene sine fra teeoff-import).
|
||
2. **`GET scorecard` viste kun UTLEDET vinn/tap/delt per hull, aldri de
|
||
faktiske tallene som var registrert** — umulig å bygge en skjerm som
|
||
viser gjeldende tilstand ved gjenlasting. Utvidet `Scorecard`-modellen
|
||
i `app/routers/scoring.py` med `stroke_entries`/`hole_result_entries`
|
||
(rå `hole_score`/`match_hole_result`-rader, nøyaktig ett av de to fylt
|
||
ut avhengig av øktens scoring_mode).
|
||
**Verifisert grundig i scratch:** hull-validering (feil antall avvist,
|
||
gjentatt oppsett avvist), og en FULL `stroke`-modus scoringsrunde med
|
||
ekte handicap-justert nettoberegning på en egendefinert bane for FØRSTE
|
||
gang noensinne (ulikt slagtall — 4 mot 5 — ga korrekt "halved" etter
|
||
handicap-utjevning), scorecard sin nye `stroke_entries` bekreftet å
|
||
returnere nøyaktig det som ble registrert. `test_isolation.sql` 12/12.
|
||
**Rullet ut live**, ingen migrasjon, kun `teecup_api` redeployet,
|
||
`teeoff.no` upåvirket.
|
||
|
||
- **Scorekort-skjermen LIVE (2026-07-18):** åttende V0-skjerm,
|
||
`components/session-scorecard.tsx`, ny rute `/tournaments/[id]/sessions/
|
||
[sessionId]/matches/[matchId]`. Ett hull i fokus om gangen (banebruk,
|
||
ikke et regneark) — store slag-steppere for `stroke`-modus, tre-valgs
|
||
vinner-knapper for `hole_result`-modus, hull-chip-navigasjon, kollapsbar
|
||
full oversikt, feiret "avgjort"-banner som låser alt til read-only.
|
||
Blind draw sin avslørte visning lenker nå til hver match sitt scorekort.
|
||
**Ingen nye backend-hull denne runden** — forrige rundes forarbeid
|
||
(hull-endepunkter + `stroke_entries`/`hole_result_entries` i scorecard)
|
||
dekket akkurat det skjermen trengte.
|
||
**Verifisert grundig i scratch, inkludert et fullt oppsett som speiler
|
||
NØYAKTIG frontend-ens egen last-sekvens** (sessions→teams→matches→
|
||
holes→scorecard, deretter en ekte `POST hole-scores` for begge spillere
|
||
på hull 1 og en refetch): status_text/derivert resultat oppdaterte seg
|
||
korrekt ("1 UP (A)", hull 1 → "a"), alle feltnavn stemte eksakt med
|
||
TypeScript-typene uten justering. `test_isolation.sql` 12/12, ekte
|
||
typesjekket build.
|
||
**Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket.
|
||
|
||
- **Leaderboard-backend LIVE (2026-07-18):** ny `GET /orgs/{id}/
|
||
tournaments/{id}/leaderboard` i `app/routers/tournaments.py` — summerer
|
||
poeng på tvers av ALLE økter/matcher i turneringen, både total og
|
||
per-økt-delsum.
|
||
**Reelt korrekthetshull funnet OG designet rundt FØR koden ble skrevet,
|
||
ikke oppdaget i ettertid:** `match.team_a_id`/`team_b_id` settes PER
|
||
MATCH ved opprettelse, ikke garantert konsistent på tvers av matcher —
|
||
en organisator kunne i prinsippet opprettet match 1 med team_a=Rød og
|
||
match 2 med team_a=Blå. En naiv summering av `points_side_a`/`points_
|
||
side_b` ville da blandet sammen poeng fra to ULIKE fysiske lag. Løst ved
|
||
å ALDRI summere på "a"/"b"-labelen — kun på det ekte lag-id-et
|
||
(`points_by_team: dict[team_id, float]`, både i totalen og per økt).
|
||
**Verifisert presist, ikke bare "kjørte uten feil":** bygget et scratch-
|
||
scenario med 3 matcher over 2 økter der match 2 sin team_a/team_b
|
||
BEVISST var byttet om i forhold til de to andre — satte poeng direkte
|
||
(Rød vinner alle tre, ett delt) og bekreftet at leaderboardet likevel ga
|
||
riktig total (Rød 2.5, Blå 0.5) og riktig per-økt-delsum, til tross for
|
||
swap-en. `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api`
|
||
redeployet, `teeoff.no` upåvirket. Frontend-skjermen gjenstår.
|
||
|
||
- **Leaderboard-skjermen LIVE (2026-07-18), samme dag:** niende og siste
|
||
V0-skjerm i "bygg i rekkefølgen ting brukes"-serien for kamp-play-flyten.
|
||
`components/tournament-leaderboard.tsx`, ny rute `/tournaments/[id]/
|
||
leaderboard`. Stort scoreboard-kort med de to lagenes totalpoeng
|
||
(lederen fremhevet), pluss en per-økt poeng-fordelingsliste med
|
||
fullført/ikke-startet-status. V0 la selv til et tredje faneelement
|
||
("Leaderboard") i BÅDE program- og roster-skjermens fanerad denne
|
||
runden — portert inn i de LIVE versjonene av begge (med riktig `org`-
|
||
parameter, som V0s eksport som vanlig manglet).
|
||
**Ingen nye backend-hull** — forrige rundes leaderboard-endepunkt dekket
|
||
akkurat det skjermen trengte.
|
||
**Verifisert i scratch:** full datamodell-runde (samme felt-for-felt
|
||
som backend-verifiseringen dagen før) OG et eget tomtilstand-scenario
|
||
(fersk turnering med 2 lag, 0 økter — `sessions: []`, `matches_total: 0`)
|
||
for å bekrefte at "ingen økter opprettet ennå"-meldingen vises riktig
|
||
i stedet for å krasje på et tomt array. `test_isolation.sql` 12/12, ekte
|
||
typesjekket build.
|
||
**Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket.
|
||
**Hele "bygg i rekkefølgen ting brukes"-serien for match-play-flyten er
|
||
dermed komplett:** oppsett (lag/roster) → program → blind draw →
|
||
scorekort → leaderboard.
|
||
- **Offisiell bane-import: idempotent + tydeligere navn, LIVE (2026-07-18),
|
||
rapportert av brukeren som faktisk brukte funksjonen på ekte
|
||
produksjonsdata:** to reelle problemer, begge funnet ved å lese (kun
|
||
lesing) de faktiske dataene for "De Gamle er Eldst" FØR noe ble antatt.
|
||
1. **`POST .../courses/official-import` var IKKE idempotent** — å
|
||
importere samme bane på nytt (helt vanlig: flere økter spilles ofte
|
||
på samme bane) ga en 409 `DUPLICATE`-feil (migrasjon 010 sin sperre)
|
||
i stedet for å bare gi tilbake den allerede importerte banen. Fikset:
|
||
sjekker nå `external_course_ref` FØR noe teeoff-kall gjøres — finnes
|
||
banen fra før, returneres den eksisterende raden direkte (også
|
||
raskere, og robust mot at teeoff er nede akkurat da).
|
||
2. **Lagret navn var kun selve banens navn ("Hovedbanen"), ikke hvilken
|
||
klubb** — ubrukelig til å skille baner fra hverandre, siden mange
|
||
klubber navngir hovedbanen sin identisk. Fikset: navnet kombineres nå
|
||
til "{anlegg} – {bane}" (f.eks. "Tjøme Golfklubb – Hovedbanen") ved
|
||
import.
|
||
**Reell konsekvens av hull #1 funnet i produksjonsdata:** brukeren hadde,
|
||
mens hen forsøkte å søke opp Tjøme via det ØVERSTE banefeltet (som søker
|
||
organisasjonens EGNE baner, ikke teeoff), ved et uhell trigget «Opprett
|
||
ny bane: «Tj»»-snarveien og fått en tom, søppel `custom`-bane hengende på
|
||
Foursome-økten -- en ekte forvekslingsfelle mellom de to adskilte
|
||
bane-søkeflatene (eget vs. teeoff), ikke en kodefeil i seg selv.
|
||
**Data ryddet opp i EKTE `teecup_db`, bruker bekreftet eksplisitt:**
|
||
Foursome-økten pekt om til den allerede importerte, ekte Tjøme-banen
|
||
(samme bane som Fourball-økten allerede brukte -- nøyaktig det
|
||
organisatoren egentlig ønsket), søppel-«Tj»-banen slettet (bekreftet
|
||
ingen matcher/tee-er/hull hang på den FØR sletting), og den ekte banens
|
||
navn oppdatert til "Tjøme Golfklubb – Hovedbanen". Verifisert i etterkant
|
||
at begge økter nå peker til samme, korrekt navngitte bane.
|
||
**Verifisert mot ekte teeoff_api i scratch FØR utrulling:** importerte
|
||
Borregaard på nytt to ganger — andre kallet ga nøyaktig samme course-id
|
||
(ikke en duplikat-rad), navnet kom ut som "Borregaard Golfklubb –
|
||
Hovedbanen". `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api`
|
||
redeployet, `teeoff.no` upåvirket.
|
||
- **`PATCH`/`DELETE` for økter, LIVE (2026-07-18), samme dag:** brukeren
|
||
spurte rett etter opprydningen om det i det hele tatt var mulig å
|
||
rette/slette en feiloppsatt økt — det var det ikke (kun `POST`/`GET`
|
||
fantes). Ny `PATCH /orgs/{id}/sessions/{id}` (`app/routers/
|
||
tournaments.py`) for enkle felt (navn, klokkeslett/intervall, starthull,
|
||
poeng, handicap-brytere) via vanlig `exclude_unset`-mønster, PLUSS en egen
|
||
gren for bane-bytte. Ny `DELETE /orgs/{id}/sessions/{id}` — kun tomme
|
||
økter (ingen matcher), avviser med 409 ellers (bruk PATCH til å korrigere
|
||
i stedet).
|
||
**Banebytte-scenarioet brukeren selv reiste** ("5 hull spilt, oppdager
|
||
feil bane — slagene er ekte, utregningen er trolig feil") krevde egen
|
||
design: `match_participant.tee_id` peker til en tee som HØRER til den
|
||
gamle banen. Løst med `_remap_course()` — finner en tee med samme
|
||
navn+kjønn på den nye banen for hver allerede tillagte deltaker, flytter
|
||
dem dit, og avviser HELE bane-byttet tydelig (400, ingenting skrevet,
|
||
bekreftet transaksjonell rollback) hvis den nye banen mangler en
|
||
tilsvarende tee. Etter et vellykket bytte: handicap regnes om for alle
|
||
berørte deltakere, og matchstatus/poeng regnes om for HVER match i
|
||
økten — **bevisst uavhengig av om matchen allerede er avgjort** (brukeren
|
||
bekreftet eksplisitt at en bane-korrigering skal kunne endre et allerede
|
||
cachet resultat). `recompute_and_cache_match_state` i `app/routers/
|
||
scoring.py` gjort delt (fjernet ledende understrek) for gjenbruk fra
|
||
tournaments.py.
|
||
**Reelt, urelatert funn underveis i scratch-testingen, IKKE fikset:**
|
||
`tee_rating` lages i dag ALLTID kun med `full_18`-omfang (både ved
|
||
teeoff-import og manuell tee-opprettelse) — en økt satt til `front_9`/
|
||
`back_9` i `stroke`-modus kan derfor ALDRI få handicap beregnet
|
||
(`compute_and_store_side_handicaps` sin `tee_rating`-join finner aldri
|
||
noen rad), og dermed aldri avgjøre noen hull. `hole_result`-modus
|
||
upåvirket. Flagget til bruker, bevisst latt urørt denne runden.
|
||
**Verifisert grundig i scratch:** enkelt feltbytte (kun navn), fullt
|
||
banebytte-scenario bygget nøyaktig som brukerens eksempel (10 hull spilt
|
||
under feil bane, matchen allerede avgjort 10&8, PATCH til riktig bane →
|
||
tee-er ombyttet korrekt, handicap endret fra 7/9 til 10/13 under den nye
|
||
banens rating, matchstatus regnet på nytt med UENDREDE rå slagtall),
|
||
avvist bane-bytte ved manglende tee-match (bekreftet full rollback,
|
||
også av det urelaterte navnefeltet i samme kall), DELETE avvist på økt
|
||
med match (409) og godtatt på tom økt (204). `test_isolation.sql` 12/12.
|
||
Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no` upåvirket.
|
||
Frontend (rediger-/slett-knapper i program-skjermen) ikke bygget ennå —
|
||
kun backend-kapasiteten denne runden.
|
||
- **Rediger/slett-UI for økter LIVE (2026-07-19):** V0-utvidelse av den
|
||
eksisterende Program-skjermen (ingen ny rute) — "..."-meny (samme
|
||
DropdownMenu-mønster som roster-skjermens per-spiller-handlinger) med
|
||
"Rediger"/"Slett" per øktkort. Rediger bytter kortet til et inline-skjema
|
||
(samme felt som PATCH støtter: navn, poeng, starthull, klokkeslett/
|
||
intervall, bane). Slett viser en bekreftelse; treffer den ekte 409-en
|
||
(økt har matcher), vises backend sin egen feiltekst i stedet for en
|
||
forhåndsberegnet klient-tilstand.
|
||
**Reell regresjon funnet OG UNNGÅTT i selve V0-eksporten, ikke i
|
||
etterkant:** denne rundens V0-prompt handlet kun om rediger/slett, men
|
||
eksporten hadde samtidig (utilsiktet) FJERNET bane-feltet fra "Legg til
|
||
økt"-skjemaet helt — nye økter ville stille blitt satt til en hardkodet
|
||
mock-bane. Fanget under diff-mot-live-treet FØR noe ble tatt inn (samme
|
||
rutine som alltid) — `CreateSessionCard` (med sitt ekte bane-søk/
|
||
teeoff-import) beholdt fullstendig urørt; kun de nye rediger/slett-
|
||
delene ble hentet inn.
|
||
**Bane-bytte i redigeringsskjemaet er bevisst ENKLERE enn opprett-
|
||
skjemaets bane-felt** — kun velg blant organisasjonens eksisterende
|
||
baner eller hent fra teeoff, INGEN "opprett ny bane: X"-snarvei her
|
||
(PATCH sin `course_id` må være en ekte, allerede eksisterende bane, ikke
|
||
en tekststreng) — unngår at samme "Tj"-forvekslingsfelle fra forrige
|
||
banebytte-hendelse kan gjenta seg via redigeringsveien.
|
||
**Verifisert:** ekte PATCH med nøyaktig skjemaets feltform, ekte DELETE
|
||
(204 tom økt, 409 med matcher — bekreftet at `{"detail":{"code",
|
||
"message"}}`-formen leses riktig av UI-et), `test_isolation.sql` 12/12,
|
||
ekte typesjekket build. Rullet ut live, ren frontend-endring, `teeoff.no`
|
||
upåvirket.
|
||
|
||
- **Invitasjonskode + ledende side + projisert stilling, BACKEND LIVE
|
||
(2026-07-19, ADR-020):** brukeren reiste tre relaterte hull rett etter at
|
||
"bygg i rekkefølgen ting brukes"-serien var ferdig: (1) ingen vei inn til
|
||
en turnering for en spiller som bare har fått muntlig beskjed, (2)
|
||
leaderboardet viser kun faktisk opptjente poeng, ikke hva stillingen ville
|
||
blitt om pågående matcher holder seg, (3) ingen fargekoding i matchlister
|
||
for hvem som leder. Full ADR-020 skrevet (4 delbeslutninger, se
|
||
ARCHITECTURE_DECISIONS.md) — nøkkelbeslutning bekreftet eksplisitt av
|
||
bruker FØR bygging: en invitasjonskode OVERSTYRER `tournament.visibility`
|
||
helt (koden ER selve invitasjonen, ikke en snarvei som fortsatt krever
|
||
eksisterende tilgang).
|
||
Ny migrasjon `011_join_code_and_leading_side.sql`: `tournament.join_code`
|
||
(6 tegn, alfabet uten 0/O/1/I, globalt unikt, backfylt for eksisterende
|
||
rader), `match.leading_side` (cachet fortegn av `MatchState.lead`, samme
|
||
mønster som `status_text`/`points_side_a/b`), fjerde
|
||
`SECURITY DEFINER`-bro `public_tournament_by_code()` (etter
|
||
`public_tournament_org` 007, `link_player_by_email` 008,
|
||
`public_org_by_slug` 009).
|
||
**Backend bygget:** `create_tournament` genererer koden (retry-løkke ved
|
||
kollisjon, astronomisk usannsynlig med 33^6 kombinasjoner). Ny
|
||
`GET /public/tournaments/by-code/{code}` (MÅ registreres FØR
|
||
`/{tournament_id}` i routeren, ellers tolkes "by-code" som en ugyldig
|
||
UUID). `GET /public/tournaments/{id}`, `GET .../sessions` og
|
||
`POST .../register` godtar alle en valgfri `code`-parameter som — når den
|
||
matcher — hopper `_check_visibility()` helt over.
|
||
`recompute_and_cache_match_state` (scoring.py) cacher nå `leading_side`
|
||
ved HVER hull-innsending, ikke bare ved avgjørelse. Leaderboard-
|
||
endepunktet fikk `projected_points`/`projected_points_by_team`:
|
||
avgjorte matcher bidrar likt til faktisk og projisert, ikke-avgjorte gir
|
||
hele `points_per_match` til `leading_side` (delt 0,5/0,5 ved "AS"/ikke
|
||
startet) — speiler hvordan Ryder Cup-TV-dekning viser "hvis det sluttet
|
||
nå".
|
||
**Reell hendelse underveis, håndtert transparent (ikke skjult):** en
|
||
feilformulert `docker exec teeoff_db env | grep -i POSTGRES`-kommando
|
||
(ment å liste variabelNAVN) fanget opp `POSTGRES_PASSWORD` sin VERDI også,
|
||
siden selve nøkkelnavnet matchet søkemønsteret — eksponerte `teeoff_db`
|
||
sitt superbruker-passord (`teeoff_admin`) i verktøyresultatet. Alvorligere
|
||
enn de to tidligere passord-hendelsene i prosjektet siden dette er
|
||
superbrukeren for HELE den delte Postgres-klyngen (teeoff OG teecup), ikke
|
||
en enkelt tjeneste-credential. Flagget til bruker umiddelbart, som valgte
|
||
å rotere. Rotert trygt UTEN å noensinne re-eksponere gammel ELLER ny verdi
|
||
i noe synlig kommandoresultat: `ALTER ROLE` kjørt via lokal
|
||
Unix-socket-`trust`-auth (bekreftet ved å lese `pg_hba.conf`, ingen
|
||
hemmelighet involvert i den sjekken) — krevde altså IKKE det gamle
|
||
passordet i det hele tatt. Nytt passord generert med `openssl rand -hex
|
||
32` (hex, ikke base64 — unngår SAMME klasse URL-enkodings-felle som
|
||
`TEECUP_DATABASE_URL`-hendelsen tidligere, siden verdien også ligger i en
|
||
`postgres://`-DSN). `/opt/teeoff/.env` sine TO forekomster
|
||
(`POSTGRES_PASSWORD` og `DATABASE_URL`) oppdatert med `sed`-mønstre som
|
||
ALDRI leser/skriver ut den gamle verdien. `teeoff_api`/`teeoff_worker`
|
||
service-nøklene i `docker-compose.prod.yml` viste seg å hete `api`/
|
||
`worker` (ikke `teeoff_api`/`teeoff_worker` — det er kun
|
||
`container_name`), samme "service-nøkkel ≠ container-navn"-fallgruve som
|
||
nettverksalias-hendelsen fra containeriseringsrunden. `docker compose up
|
||
-d --force-recreate api worker` gjenskapte OGSÅ `teeoff_db` selv (ikke
|
||
eksplisitt navngitt) — Compose oppdager konfigurasjonsendring
|
||
(`${POSTGRES_PASSWORD}` i `db`-tjenestens egen `environment:`) og
|
||
gjenskaper uansett hvilke tjenester som ble navngitt. Verifisert grundig
|
||
ETTERPÅ: `teeoff_db`-loggen viste "Skipping initialization" (datavolum
|
||
urørt, ikke reinitialisert) + ren oppstart, `teeoff_api`/`teeoff_worker`
|
||
ren oppstart uten en eneste feil-/auth-/passord-linje i hele loggen,
|
||
`teeoff.no` OG `teecup.teeoff.no/health` begge `200` etterpå.
|
||
**Scratch-verifisert grundig** (fersk `teecup_scratch` 001→011,
|
||
isolert `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs
|
||
API-container): `test_isolation.sql` 12/12 (måtte først rettes — tre
|
||
RÅ `INSERT INTO tournament`-steder i selve testfilen predaterte
|
||
`join_code` og traff den nye NOT NULL-constrainten, rettet med
|
||
dummy-koder). Full ende-til-ende-runde: to turneringer opprettet, ulike
|
||
koder bekreftet; `by-code`-oppslag bekreftet for kjent OG ukjent kode
|
||
(404); anonym lesing av en `org`-synlig turnering BLOKKERT uten kode
|
||
(403 NOT_VISIBLE), TILLATT med riktig kode (inkl. case-insensitivt),
|
||
FORTSATT blokkert med feil kode; samme mønster bekreftet for
|
||
`POST .../register`. Full hull-for-hull-simulering av en singel-match
|
||
(hole_result-modus): `leading_side`/`status_text` fulgte hverandre
|
||
eksakt gjennom "1 UP (A)" → "AS" (leading_side=null) → avgjort "9&7
|
||
(A)", leaderboardets `projected_points` traff nøyaktig 1.0/0.0 mens A
|
||
ledet, 0.5/0.5 ved "AS", og ble likt `points`/`projected_points` (begge
|
||
1.0/0.0) etter avgjørelse.
|
||
**Rullet ut mot ekte `teecup_db` 2026-07-19**, bruker bekreftet
|
||
eksplisitt: migrasjon 011 kjørt (eneste eksisterende turnering fikk
|
||
automatisk generert kode), `test_isolation.sql` fortsatt 12/12, kun
|
||
`teecup_api` redeployet (ingen frontend-endring i denne del-runden),
|
||
`teeoff.no` upåvirket.
|
||
**Frontend fullført samme dag, egen del-runde:** `login-form.tsx` fikk et
|
||
eget kode-modus (`JoinByCode`) — «Har du en invitasjonskode?»-lenke bytter
|
||
ut e-post-skjemaet, slår opp `/public/tournaments/by-code/{code}` og
|
||
navigerer til `/t/{id}?code=...` med Next sin `useRouter`. `code`
|
||
query-param tres gjennom hele veien: `app/t/[id]/page.tsx` leser
|
||
`searchParams`, `public-tournament.tsx` sender den med på BÅDE
|
||
info-/sessions-lesingen og selve `POST .../register` (ikke bare det
|
||
første oppslaget som tok deg dit).
|
||
`tournament-detail.tsx` viser koden i en egen kopier-chip i headeren —
|
||
ingen enkelt-turnering-`GET` fantes, så komponenten henter i stedet hele
|
||
org-ens turneringsliste (som allerede bærer `join_code`) og finner egen
|
||
rad, i stedet for å legge til et nytt endepunkt kun for dette.
|
||
`tournament-leaderboard.tsx` fikk en ny `SegmentedBar`-komponent — ETT
|
||
fargesegmentert rektangel per bar (ikke tall side om side), proporsjonalt
|
||
med hvert lags poeng, 50/50 nøytralt ved 0-0. To slike bares rett under
|
||
headeren: "Stilling nå" (faktisk) og "Projisert (hvis pågående matcher
|
||
holder seg)" — den EKSISTERENDE store tall-scoreboarden beholdt uendret
|
||
lenger ned som detaljvisning.
|
||
`session-blind-draw.tsx` sin `RevealedView`: matchkortet får nå en farget
|
||
toppkant (leaderens `team.color`) og en `status_text`-chip (fylt farge
|
||
ved avgjort match m/ konfetti-ikon, lys tone ved pågående) i stedet for
|
||
kun klokkeslettet; `RevealSide` for ledende side får en svak fargetonet
|
||
bakgrunn. `leading_side`/`status_text`/`points_side_a/b` lagt til
|
||
`ApiMatch`-typen (var der allerede i API-et, bare ikke konsumert
|
||
frontend-siden før nå).
|
||
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
|
||
som deployes, ikke dev-server) kompilerte rent, alle 10 ruter listet.
|
||
Rullet ut (kun `teecup_frontend`, ingen backend-endring i denne delen),
|
||
`teecup.teeoff.no/dashboard` og `/` → 200, `teeoff.no` upåvirket. Ekte
|
||
smoke-test i produksjon: `by-code`-oppslag for "De Gamle er Eldst" sin
|
||
faktiske kode ga riktig turnering-id, `/t/{id}?code=...` ga 200.
|
||
**ADR-020 er dermed helt ferdig** (backend + frontend, alle fire
|
||
del-ønsker: kode-basert oppdagelse, kode-felt på login, projisert
|
||
stilling, fargekoding av matcher) — bortsett fra kode-regenerering, som
|
||
er bevisst utsatt (se FEATURE_BACKLOG.md).
|
||
**Reell driftshendelse underveis** (mellom backend- og frontend-delen,
|
||
under scratch-oppsett): en feilformulert `grep -i POSTGRES`-kommando
|
||
eksponerte `teeoff_db` sitt superbruker-passord ved et uhell. Flagget
|
||
umiddelbart, brukeren valgte å rotere — se detaljene under
|
||
backend-avsnittet over for hele hendelsen og hvordan roteringen ble
|
||
gjennomført uten å noensinne re-eksponere gammel eller ny verdi.
|
||
- **Sesjons-bug diagnostisert og FIKSET, LIVE (2026-07-19):** brukeren
|
||
rapporterte at hen måtte be om ny magic-link-kode ved HVERT besøk til
|
||
`teecup.teeoff.no`, til tross for ADR-009s 30-dagers sesjonscookie. Bad
|
||
brukeren sjekke den EKTE cookien i nettleseren fremfor å gjette — kom
|
||
tilbake korrekt satt i alle henseender (`Expires` 30 dager frem,
|
||
`Secure`/`HttpOnly`/`SameSite=Lax`). Rot-årsaken var derfor IKKE cookien
|
||
eller backend-en: `frontend/app/page.tsx` (rot-siden) viste ALLTID
|
||
innloggingsskjemaet uten noensinne å sjekke om en gyldig sesjon allerede
|
||
fantes. `Dashboard`-komponenten sjekker `/auth/me` og sender til `/` ved
|
||
MANGLENDE sesjon, men ingen kode gjorde det motsatte — en bruker som
|
||
besøkte roten direkte (i stedet for å navigere til `/dashboard`) så
|
||
derfor alltid innloggingsskjemaet uansett sesjonsstatus.
|
||
**Fikset:** `page.tsx` gjort om til en async server-komponent som leser
|
||
sesjonscookien via `next/headers`, kaller `/auth/me` server-til-server
|
||
direkte mot `TEECUP_API_ORIGIN` (samme mønster som `generateMetadata` i
|
||
`app/t/[id]/page.tsx` — IKKE gjennom `next.config.mjs` sin `rewrites()`,
|
||
som kun gjelder nettleser-trafikk), og sender en allerede innlogget
|
||
bruker videre til `/dashboard` med `redirect()` FØR innloggingsskjemaet
|
||
når rendres.
|
||
**Verifisert presist mot den ekte, live stacken, med brukerens EGEN
|
||
ekte sesjonscookie** (ikke en syntetisk test): `curl` uten cookie mot
|
||
`https://teecup.teeoff.no/` ga `200` (skjemaet vises, riktig for en
|
||
anonym besøkende); samme kall MED den ekte cookien ga `307` til
|
||
`/dashboard` (riktig — sender en allerede innlogget bruker rett videre).
|
||
Ekte typesjekket produksjonsbuild kjørt FØR utrulling (build-outputet
|
||
viste selv at `/` nå er `ƒ` dynamisk i stedet for `○` statisk — bekrefter
|
||
at server-sjekken faktisk ble tatt i bruk). Rullet ut live, kun
|
||
`teecup_frontend`, ingen migrasjon, `teeoff.no` upåvirket.
|
||
**Samme runde:** brukeren stilte to oppfølgingsspørsmål om
|
||
autentisering/autorisasjon — «er flere-organisasjoner-eierskap tenkt
|
||
gjennom» (bekreftet: ja, ADR-002 fra dag én, ingen kodeendring nødvendig)
|
||
og et ønske om passord (valgfritt tillegg)+2FA. Fullt design skrevet som
|
||
**ADR-021** (passord/2FA) og **ADR-022** (dele/invitere/frasi seg
|
||
eierskap + superadmin) — se ARCHITECTURE_DECISIONS.md.
|
||
- **ADR-021 (passord/2FA) + ADR-022 (org-eierskap) BYGGET OG LIVE
|
||
(2026-07-19), samme dag:** brukeren ba om begge sammen («Bygg det»,
|
||
bekreftet eksplisitt at det gjaldt begge ADR-ene i samme runde). Ny
|
||
migrasjon `012_password_2fa_and_org_invitations.sql`: `app_user.
|
||
password_hash`/`two_factor_method`/`totp_secret`/`is_super_admin`, ny
|
||
tabell `two_factor_code` (samme hash-og-utløp-mønster som
|
||
`magic_link_token`), ny tabell `organization_invitation` (RLS
|
||
org-isolert, INGEN egen klikkbar aksept-lenke — godtas automatisk ved
|
||
neste innlogging med matchende e-post), femte
|
||
`SECURITY DEFINER`-bro `accept_pending_invitations_by_email()` (etter
|
||
`public_tournament_org` 007, `link_player_by_email` 008,
|
||
`public_org_by_slug` 009, `public_tournament_by_code` 011).
|
||
**Sesjons-STADIER innført i `app/auth.py`:** en sesjonscookie er ikke
|
||
nødvendigvis en full sesjon lenger — `create_session_token()` tar nå en
|
||
`stage`-parameter (`full`/`pending_2fa`/`must_enroll_2fa`), lagt inn som
|
||
et JWT-claim. `get_current_user` avviser eksplisitt alt annet enn `full`
|
||
ELLER en ELDRE token uten stage-claim i det hele tatt (utstedt før denne
|
||
runden — behandlet som `full` for bakoverkompatibilitet, ingen
|
||
eksisterende bruker logget brått ut). To nye avhengigheter:
|
||
`get_pending_user` (kun for 2FA-verifiseringsendepunktene) og
|
||
`get_current_or_enrolling_user` (godtar BÅDE en full sesjon — frivillig
|
||
2FA-oppsett fra kontoinnstillinger — OG `must_enroll_2fa` — tvunget
|
||
oppsett rett etter innlogging — samme oppsett-logikk dekker begge
|
||
veiene). `get_current_user_optional` fikk samme stage-sjekk for
|
||
konsistens.
|
||
**Passord (Argon2id, ikke bcrypt):** bevisst valg for å unngå bcrypt sin
|
||
stille 72-byte-trunkering, siden brukeren eksplisitt ba om korrekt
|
||
håndtering av spesialtegn/mellomrom. `POST /auth/set-password`/
|
||
`/remove-password`/`/login-password` — sistnevnte svarer med IDENTISK
|
||
401 uansett om e-posten finnes, mangler passord, eller passordet er
|
||
feil (samme anti-enumerering som magic-link).
|
||
**2FA (TOTP via `pyotp` ELLER e-post-engangskode via eksisterende SMTP,
|
||
brukerens eget valg):** `POST /auth/2fa/setup/start` genererer en
|
||
TOTP-secret UTEN å lagre den (rundturer til klienten, som ekkoer den
|
||
tilbake i `/setup/confirm` — unngår en halvferdig 2FA-tilstand i
|
||
databasen hvis brukeren forlater oppsettet). QR-kode generert
|
||
server-side (`qrcode`-biblioteket + eksisterende Pillow-avhengighet,
|
||
ingen ny ekstern tjeneste). `POST /auth/2fa/verify` fullfører en
|
||
PÅGÅENDE innlogging.
|
||
**Tvungen 2FA for org-eier/admin (ADR-021 Beslutning D):**
|
||
`user_requires_2fa_enrollment()` sjekket ved HVER innlogging (ikke bare
|
||
første gang, siden en bruker kan bli eier av en NY org etter at kontoen
|
||
allerede eksisterer uten 2FA) — verifisert eksplisitt: en fersk
|
||
org-eier uten 2FA ble korrekt blokkert fra all normal tilgang (401) og
|
||
tvunget inn i oppsett-flyten før noe annet ble tilgjengelig.
|
||
**Organisasjonseierskap (ADR-022):** `POST/GET/DELETE
|
||
/orgs/{id}/invitations` (owner→enhver rolle, admin→KUN member — ellers
|
||
en privilegie-eskaleringsvei), `PATCH/DELETE /orgs/{id}/memberships/
|
||
{id}` (owner kan endre/fjerne hvem som helst; en bruker kan ALLTID
|
||
SENKE egen rolle selv — aldri heve den, ville vært selv-forfremmelse —
|
||
og alltid forlate selv), «siste eier»-vern (409 `LAST_OWNER`, `FOR
|
||
UPDATE`-lås mot race på alle eier-rader, samme TOCTOU-mønster som
|
||
ADR-011s to-lags-grense). Superadmin (`app_user.is_super_admin`, KUN
|
||
manuelt DB-tildelt — bevisst INGEN API-vei til å gi seg selv eller
|
||
andre flagget) får en parallell autorisasjonssti
|
||
(`get_superadmin_user`) som kan sette medlemskap på ENHVER org,
|
||
uavhengig av eget medlemskap — bevisst avgrenset til nøyaktig dette,
|
||
ikke generell tilgang til andres turnering-/spillerdata.
|
||
**Tre reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde
|
||
produksjon:**
|
||
1. `verify_magic_link` sendte en rå asyncpg-`UUID` (ikke streng) videre
|
||
til sesjonsutstedelse — `jwt.encode()` sin JSON-serialisering
|
||
krasjet rått (500) på selve innloggingen. Fant umiddelbart ved første
|
||
reelle innloggingstest. Rettet med en eksplisitt `str()`.
|
||
2. OG 3. Både `2fa/setup/confirm` og `2fa/verify` kalte først den delte
|
||
`_issue_login_result()`-hjelpefunksjonen (ment for PRIMÆR
|
||
autentisering) EN GANG TIL etter at 2FA nettopp var bekreftet — som
|
||
så (korrekt, men feil kontekst) at `two_factor_method` nå var satt
|
||
og krevde EN NY runde med 2FA for akkurat den samme innloggingen,
|
||
en uendelig løkke. Fant ved å faktisk fullføre hele innloggings-
|
||
syklusen med ekte genererte TOTP-koder (`pyotp` i test-scriptet),
|
||
ikke bare ved å lese koden. Rettet ved at begge endepunktene nå
|
||
utsteder en full sesjon DIREKTE etter vellykket 2FA-bekreftelse,
|
||
ikke via gjenbruk av den generelle sjekken.
|
||
**Frontend:** `login-form.tsx` fikk en tredje modus (passord, ved siden
|
||
av magic-link og ADR-020s invitasjonskode-modus). Ny delt
|
||
`two-factor-flow.tsx` (`TwoFactorVerifyForm`/`TwoFactorSetupForm`),
|
||
brukt av BÅDE `login-form.tsx` og `verify-form.tsx` siden begge
|
||
primær-autentiseringsveiene kan returnere samme 2FA-mellomtilstand. Ny
|
||
`/account`-skjerm (sett/fjern passord, aktiver/deaktiver 2FA) og ny
|
||
`/orgs/[id]/members`-skjerm (invitere, endre rolle, fjerne/forlate),
|
||
begge lenket fra dashbordets header/org-visning. `next.config.mjs` sin
|
||
`rewrites()` utvidet med `/superadmin/:path*` (ADR-016s konsekvens for
|
||
enhver ny API-prefiks, selv om ingen frontend-UI faktisk bruker den
|
||
ennå).
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (001→012,
|
||
`test_isolation.sql` 12/12): full magic-link-bakoverkompatibilitet
|
||
(eksisterende flyt uendret for brukere uten 2FA), passord med ekte
|
||
spesialtegn/mellomrom/æøå satt og brukt til pålogging, full TOTP-runde
|
||
(oppsett→bekreft→logg ut→logg inn→krev 2FA→verifiser med ekte
|
||
`pyotp`-generert kode), full e-post-2FA-runde (samme mønster, feil kode
|
||
avvist, kode ikke gjenbrukbar), tvungen 2FA-registrering for ny
|
||
org-eier, invitasjon→auto-aksept ved førstegangsinnlogging,
|
||
rolle-eskalering-forsøk avvist på tre distinkte måter (medlem kan ikke
|
||
invitere, admin kan ikke gi eierskap, bruker kan ikke forfremme seg
|
||
selv), siste-eier-vern (både PATCH og DELETE), duplikat-invitasjon
|
||
avvist, superadmin-sti fungerer/avvises riktig i begge retninger. Ekte
|
||
typesjekket produksjonsbuild av frontend (alle nye ruter listet).
|
||
**Rullet ut mot ekte systemer 2026-07-19**, bruker bekreftet eksplisitt:
|
||
migrasjon 012 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt
|
||
12/12, begge containere (`teecup_api`, `teecup_frontend`) redeployet og
|
||
bekreftet ren oppstart, `/health` og `/dashboard` fortsatt 200 (eksisterende
|
||
sesjoner uendret av stage-bakoverkompatibiliteten), `/auth/login-password`
|
||
bekreftet nåbart over ekte https, `teeoff.no` upåvirket.
|
||
**ADR-021 og ADR-022 er dermed begge helt ferdig** — backend + frontend.
|
||
Bevisst utenfor omfang: dedikert superadmin-UI (brukes via API av en
|
||
betrodd operatør), SMS som 2FA-metode.
|
||
|
||
- **Program-skjerm: tydeligere klikk-hint på øktkort (2026-07-19):** brukeren
|
||
påpekte at ingenting i grensesnittet indikerte at et øktkort er klikkbart
|
||
inn til blind draw-skjermen. Lagt til en synlig "Sett opp flights og lås
|
||
oppstilling →"-rad nederst i hvert kort (`components/tournament-program.tsx`).
|
||
Ren frontend-endring, ingen backend-rørt.
|
||
- **Brukerroller: kaptein som reell autorisasjon, deltaker-avgrenset scoring
|
||
(2026-07-19, ADR-023):** direkte oppfølging av det lenge åpne
|
||
"Brukerroller"-punktet i FEATURE_BACKLOG.md. Fire beslutninger avklart
|
||
eksplisitt med bruker (AskUserQuestion) før bygging — alle anbefalte valg.
|
||
**Bygget:** `app/team_authz.py` skrevet om — `user_is_team_captain`
|
||
(erstatter `user_may_act_for_team`) krever `is_captain=true` på
|
||
`team_roster` (eller org-eier/admin) for å legge til/fjerne deltakere og
|
||
låse et lag (`matches.py`); ny `user_is_match_participant` krever en ekte
|
||
`match_participant`-rad for brukeren i AKKURAT den matchen (valgfritt
|
||
side-spesifikk via `team_side`) for å føre/korrigere score (`scoring.py`)
|
||
— uavhengig av kapteinmerket. Nye feilkoder `NOT_TEAM_CAPTAIN` og
|
||
`NOT_MATCH_PARTICIPANT` (erstatter `NOT_ROSTERED_ON_TEAM` på disse fem
|
||
stedene). `app/routers/tournaments.py` sin `PATCH`/`POST .../roster`
|
||
håndhever nå "kun én kaptein per lag" (fjerner automatisk forrige
|
||
kapteins merke i samme transaksjon).
|
||
**Reelt funn FØR utrulling, ikke antatt:** sjekket (kun lesing, superbruker
|
||
mot ekte `teecup_db`) om noen eksisterende lag ville blitt låst ute av en
|
||
ren kaptein-only-regel — "De Unge" i "De Gamle er Eldst" har i dag 0 av 2
|
||
roster-rader merket kaptein. Designet derfor en bevisst fallback i
|
||
`user_is_team_captain`: har laget INGEN utpekt kaptein ennå, godtas enhver
|
||
rostret spiller i stedet for å låse laget helt ute. Ingen lag hadde flere
|
||
kapteiner, så "kun én kaptein"-håndhevelsen krevde ingen data-opprydning.
|
||
**Reell bug funnet OG fikset UNDER scratch-testing:** `user_is_match_
|
||
participant` sin SQL sammenlignet `mp.team_side` (enum-kolonne) direkte
|
||
mot en tekst-parameter uten cast når `team_side=None` (hole_result-modus)
|
||
— ga en rå 500 (`UndefinedFunctionError: operator does not exist: team_side
|
||
= text`). Rettet med et eksplisitt `mp.team_side::text = $3`.
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
||
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container):
|
||
et 15-punkts Python/httpx-testskript som simulerte fire innloggede
|
||
brukere (organisator + tre rostrede spillere på to lag) gjennom hele
|
||
syklusen — lag uten kaptein tillater enhver rostret (fallback bekreftet),
|
||
kaptein utpekt fjerner andre rostredes rettighet, "kun én kaptein" bekreftet
|
||
(ny kaptein avsetter automatisk forrige), org-admin fungerer uendret
|
||
uavhengig av kaptein, og scoring (`hole_result`-modus) bekreftet begrenset
|
||
til faktiske matchdeltakere (en kaptein som IKKE selv spiller matchen ble
|
||
korrekt avvist med `NOT_MATCH_PARTICIPANT`, mens en faktisk deltaker og
|
||
org-admin begge fikk føre score). Måtte også oppdage og legge til et
|
||
forutsetning-steg underveis: `get_authorized_org` krever
|
||
`organization_membership` for ALLE org-scopede endepunkter uansett — en
|
||
rostret spiller må derfor også være invitert som org-medlem (minimum
|
||
'member', ADR-022s invitasjonsflyt) for i det hele tatt å nå
|
||
team_authz-vurderingen; ikke en bug, men en forutsetning testskriptet
|
||
først manglet. `test_isolation.sql` 12/12 uendret (ingen skjemaendring).
|
||
Ekte typesjekket produksjonsbuild av frontend (øktkort-hintet fra samme
|
||
runde) kjørt og bekreftet, alle 11 ruter listet.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent (`Application startup complete`, Next.js
|
||
`Ready`), `/health` og `/dashboard` → 200, `teeoff.no` upåvirket.
|
||
|
||
- **Walkover/konsesjon LIVE (2026-07-19, ADR-024):** direkte oppfølging av
|
||
Brukerroller-runden samme dag — brukeren ba eksplisitt om å ta fatt på
|
||
dette som naturlig neste steg. Løser det lenge kjente hullet: en side som
|
||
aldri stiller nok spillere fikk aldri beregnet handicap og matchen kunne
|
||
derfor aldri avgjøres — hang uendelig.
|
||
Fire beslutninger avklart eksplisitt med bruker (AskUserQuestion) før
|
||
bygging — tre anbefalte valg, ett (omfang: match+turnering-nivå samtidig,
|
||
ikke bare match) valgt utover anbefalingen.
|
||
**Bygget:** ny `apply_concession`-hjelpefunksjon i `app/routers/
|
||
scoring.py` (skriver til de samme fire kolonnene som
|
||
`recompute_and_cache_match_state` -- `status_text`/`points_side_a/b`/
|
||
`leading_side` -- men direkte, ikke utledet fra hull). Ny
|
||
`POST /orgs/{id}/matches/{id}/concede` (kun kaptein for det TAPENDE laget,
|
||
speiler ekte golf-etikette -- du gir bort DITT tap, krever ikke seier på
|
||
motstanderens vegne -- eller org-admin, gjenbruker `user_is_team_captain`
|
||
fra ADR-023 uendret). Ny `POST /orgs/{id}/tournaments/{id}/concede`
|
||
(`app/routers/tournaments.py`) som gir opp ALLE ikke-avgjorte matcher
|
||
laget har i turneringen i én operasjon -- v1s to-lags-grense (ADR-011)
|
||
gjør dette trivielt (bare én motstander uansett), `FOR UPDATE`-låser alle
|
||
berørte match-rader (samme race-vern som ellers). Kan erklæres uansett
|
||
hvor mange hull som allerede er registrert (match-play teller kun
|
||
seier/tap/delt for poeng, ikke marginen) -- allerede registrerte hull i
|
||
`hole_score`/`match_hole_result` forblir urørt, kun matchens
|
||
avgjørelses-felt endres.
|
||
**Frontend:** `session-scorecard.tsx` fikk en kollapsbar
|
||
"Gi opp matchen (walkover)"-seksjon (to knapper, én per lag, med
|
||
bekreftelsessteg) synlig når matchen ikke er avgjort.
|
||
`tournament-detail.tsx` sin `TeamPanel` fikk en tilsvarende "Gi opp resten
|
||
av turneringen for laget"-knapp nederst i hvert lagkort. Ingen
|
||
klientside-forhåndsfiltrering på kapteinstatus noe sted -- begge knappene
|
||
vises alltid, en 403 fra backend vises bare som vanlig feiltekst (samme
|
||
mønster som resten av appen).
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
||
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs
|
||
API-container): to separate 15-punkts Python/httpx-testløp. Match-nivå:
|
||
vinnende lags kaptein NEKTES å konsedere på vegne av det tapende laget
|
||
(kan ikke kreve seier på andres vegne), tapende lags kaptein FÅR, allerede
|
||
avgjort match avvist 409, fremmed team_id avvist 400, konsesjon ETTER at
|
||
ett hull allerede er registrert bekreftet å fungere OG bekreftet at det
|
||
registrerte hull-resultatet forblir synlig i scorekortet etterpå (ikke
|
||
overskrevet), org-admin FÅR konsedere direkte uavhengig av kapteinmerke.
|
||
Turnering-nivå: feil lags kaptein nektes, riktig kaptein FÅR gi opp
|
||
resten (kun de faktisk ikke-avgjorte matchene telles -- allerede avgjorte
|
||
matcher fra match-nivå-testene i samme løp ble korrekt hoppet over),
|
||
gjentatt kall er trygt (0 nye, idempotent i praksis), ukjent team_id gir
|
||
404. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). Ekte
|
||
typesjekket produksjonsbuild av frontend kjørt og bekreftet, alle 11
|
||
ruter listet.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent, `/health` og `/dashboard` → 200,
|
||
`teeoff.no` upåvirket.
|
||
|
||
- **Turnering-status via API, SCRATCH-VERIFISERT (2026-07-19):** brukeren
|
||
valgte dette som neste steg etter walkover/konsesjon-runden (fikk velge
|
||
mellom denne lille opprydningen og å starte Kommunikasjon-runden). `status`
|
||
(draft/active/completed/archived) har ligget i skjemaet siden migrasjon
|
||
001, men INGEN endepunkt kunne endre det — kun `INSERT`-defaulten `'draft'`
|
||
fra `create_tournament`.
|
||
**Bygget:** lagt til i `TournamentUpdate` (`app/routers/tournaments.py`),
|
||
settes via det eksisterende generiske `PATCH /orgs/{id}/tournaments/{id}`
|
||
(`exclude_unset`-mønsteret, ekte PATCH-semantikk uendret).
|
||
**Reelt funn UNDER scratch-testing, ikke antatt riktig på forhånd:**
|
||
`status` er -- ulikt `visibility` (som er ren `text`+`CHECK`) -- en EKTE
|
||
Postgres ENUM-type (`tournament_status`). Den generiske
|
||
`set_clauses`-byggeren (`f"{key} = ${i}"`) hadde derfor trengt et
|
||
eksplisitt cast for at asyncpg sin ukjent-typede parameter skulle løses
|
||
riktig mot en enum-kolonne -- lagt til en spesialsjekk (`::tournament_status`
|
||
kun for `status`-nøkkelen) FØR jeg antok mekanismen "bare fungerer" fordi
|
||
den gjør det for de andre feltene.
|
||
Bevisst INGEN tilstandsmaskin/overgangsregler bygget (kan f.eks. gå fra
|
||
`completed` tilbake til `draft` fritt) -- samme tillitsnivå som resten av
|
||
appen, ikke etterspurt.
|
||
**Frontend:** `tournament-status-badge.tsx` fikk en ny redigerbar
|
||
`TournamentStatusPicker` (dropdown over de fire verdiene, optimistisk
|
||
UI-oppdatering med rollback ved feil), koblet inn i `tournament-detail.tsx`
|
||
sin header ved siden av invitasjonskode-chipen. Den eksisterende
|
||
skrivebeskyttede `TournamentStatusBadge` (dashbordets kortliste) urørt.
|
||
**Verifisert i scratch:** ny turnering får riktig default `draft`, PATCH
|
||
til `active` bekreftet enum-castet faktisk løser problemet, PATCH med
|
||
status+et annet felt samtidig fungerer, PATCH UTEN status-felt lar
|
||
verdien stå urørt (regresjon på eksisterende PATCH-semantikk), ugyldig
|
||
status-verdi avvist med 422 (Pydantic-mønster), `GET`-listen viser samme
|
||
verdi etterpå. `test_isolation.sql` 12/12 uendret (ingen skjemaendring).
|
||
Ekte typesjekket produksjonsbuild kjørt og bekreftet.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent, `/health` og `/dashboard` → 200,
|
||
`teeoff.no` upåvirket.
|
||
|
||
- **Kommunikasjon LIVE (2026-07-19, ADR-025) — det største enkeltløftet i
|
||
prosjektet så langt:** lag-intern chat («det hemmelige rommet») + offentlig
|
||
runde-feed («Banter Board»), begge med bilder og ekte WebSocket-sanntid,
|
||
bygget i samme runde. Fire hovedbeslutninger avklart eksplisitt med bruker
|
||
(AskUserQuestion) før bygging — brukeren valgte den mest ambisiøse
|
||
kombinasjonen på alle fire (begge deler nå, WebSockets fremfor polling,
|
||
ekte privat chat, bilder fra start).
|
||
**Datamodell:** ny migrasjon `013_messaging.sql` — delt `message`-tabell
|
||
med `scope`-diskriminator (`team`/`tournament_feed`) i stedet for to
|
||
separate tabeller, RLS org_isolation som ellers. `author_display_name`
|
||
FRYSES ved skrivetidspunkt (samme prinsipp som handicap-snapshot,
|
||
ADR-007) — spillerens `player.display_name` i org-en hvis den finnes,
|
||
ellers e-postens lokaldel (dekker org-ansatte uten egen spillerprofil).
|
||
**Lag-chat er BEVISST ekte privat** — ny `user_is_rostered_on_team` i
|
||
`app/team_authz.py`, med VILJE uten org-admin-fallback, ulikt de to andre
|
||
funksjonene i samme fil (`user_is_team_captain`/`user_is_match_participant`,
|
||
ADR-023, som begge har et slikt unntak). Første sted i hele appen der
|
||
org-eier/admin er strukturelt utestengt fra noe.
|
||
**Offentlig feed:** LESING gjenbruker `registration.py` sitt eksisterende
|
||
trenivå-visibility-mønster (ADR-018) helt uendret, inkl. anonym tilgang.
|
||
POSTING er strengere enn lesing — krever ekte innlogging OG org-
|
||
medlemskap/faktisk deltakelse, selv på en `public`-synlig turnering (en
|
||
helt urelatert innlogget bruker skal ikke kunne poste på en fremmed
|
||
offentlig side). Moderering: forfatteren selv ELLER org-eier/admin kan
|
||
slette et feed-innlegg (motsatt av lag-chatten, som ikke har noen
|
||
ekstern moderator).
|
||
**`registration.py` sine fire interne hjelpefunksjoner gjort delt**
|
||
(fjernet ledende understrek — samme "gjort delt for gjenbruk"-mønster som
|
||
tidligere runder): `resolve_org`, `is_participant`, `code_matches`,
|
||
`check_visibility`. `team_authz.py` sin `_is_org_admin` likeens →
|
||
`is_org_admin`. Ingen atferdsendring, kun navn, for at `messaging.py`
|
||
skulle kunne gjenbruke dem uendret i stedet for å duplisere logikk.
|
||
**WebSockets, ikke polling:** in-memory tilkoblingsregister PER PROSESS i
|
||
`app/routers/messaging.py` — trygt med dagens ene `teecup_api`-container,
|
||
men deles IKKE på tvers av flere prosesser/containere (samme klasse
|
||
begrensning som den allerede aksepterte in-memory-cachen, se ARCHITECTURE_
|
||
DECISIONS.md "Åpne spørsmål"). Ny `get_current_user_from_websocket` i
|
||
`app/auth.py` — WS-ruter kan ikke bruke `get_current_user`/
|
||
`get_authorized_org` direkte via `Depends()` (de er `Request`-typet, ingen
|
||
ekte HTTP Request finnes i en WS-scope), derfor en bevisst minimal, egen
|
||
kopi av samme cookie-dekode-/oppslagslogikk.
|
||
**Reell infrastrukturoppdagelse FØR noe ble forsøkt, ikke i etterkant:**
|
||
Next.js sin `rewrites()` proxyer ikke WebSocket-oppgraderinger pålitelig i
|
||
"standalone"-modus — løst likt som MinIO-media-ruten (ADR-018): en egen
|
||
Caddy-rute (`handle /ws/* { reverse_proxy teecup_api:8000 }`) rett til
|
||
API-et, forbi Next.js/`teecup_frontend` helt. Caddyfile ligger i det
|
||
SEPARATE `/opt/teeoff`-repoet — samme stale-bind-mount-inode-oppførsel som
|
||
ALLE tidligere Caddyfile-runder (graceful reload plukker ikke opp
|
||
endringen), løst likt: full `docker restart teeoff_caddy`, brukeren
|
||
bekreftet eksplisitt på forhånd, noen sekunders nedetid for `teeoff.no`.
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
||
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container
|
||
med `websockets`-Python-biblioteket installert kun for testen): et
|
||
20-punkts asyncio/httpx/websockets-testskript som dekket BEGGE
|
||
meldingstyper ende-til-ende. Kritiske personvern-/sanntid-funn, alle
|
||
bekreftet med ekte tilkoblinger (ikke bare REST):
|
||
- Rostret spiller på lag A FÅR lese/skrive lag A sin chat; rostret spiller
|
||
på lag B NEKTES; **organisatoren (org-eier) NEKTES OGSÅ** — bekreftet
|
||
BÅDE over REST (`GET`) og over selve WebSocket-håndtrykket (avvist med
|
||
lukkekode 4403 før `accept()` i det hele tatt kalles).
|
||
- Sanntid bekreftet reelt: A1 koblet til lag A sin chat-socket, A2 sendte
|
||
en melding over vanlig REST, A1 mottok den umiddelbart over den åpne
|
||
WebSocket-tilkoblingen (ikke bare at REST-svaret så riktig ut).
|
||
- Bildeopplasting i chat bekreftet (ekte AVIF-konvertert `image_url`
|
||
returnert).
|
||
- Offentlig feed: anonym NEKTES å poste (401), en tilfeldig INNLOGGET men
|
||
uvedkommende bruker NEKTES (403 `NOT_A_PARTICIPANT`), org-medlem FÅR,
|
||
en faktisk deltaker (rostret, IKKE org-medlem) FÅR — beviser
|
||
`is_participant`-veien fungerer uavhengig av `is_member`-veien. Anonym
|
||
WebSocket-tilkobling til en `public`-synlig turnerings feed FÅR lov og
|
||
mottar sanntidsoppdateringer.
|
||
- Moderering bekreftet: forfatter sletter eget innlegg, org-admin sletter
|
||
ANDRES innlegg (feeden), uvedkommende NEKTES å slette andres innlegg.
|
||
`test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket
|
||
produksjonsbuild av frontend kjørt og bekreftet, inkl. den nye
|
||
`/tournaments/[id]/teams/[teamId]/chat`-ruten.
|
||
**Frontend:** ny `components/team-chat.tsx` (meldingsliste med egen/andres-
|
||
styling, bildeopplasting, sanntid via nettleserens native `WebSocket`,
|
||
slett-egen-melding), lenket fra en ny chat-ikon-knapp i `tournament-
|
||
detail.tsx` sin `TeamPanel`. Ny seksjon `TournamentFeed` i
|
||
`components/public-tournament.tsx` (nederst på den offentlige
|
||
turneringssiden) — viser 401/403-svar fra posting som forklarende
|
||
inline-tekst ("logg inn for å poste" / "du må være medlem/deltaker") i
|
||
stedet for en generisk feilmelding.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt for alle tre
|
||
stegene (migrasjon, containere, Caddy-restart): migrasjon 013 kjørt mot
|
||
ekte `teecup_db`, `test_isolation.sql` fortsatt 12/12, begge containere
|
||
boot-et rent, Caddy validert (`caddy validate` — "Valid configuration")
|
||
FØR restart, restarten ren (ingen feil i loggen). Verifisert grundig
|
||
etterpå: `/health`/`dashboard` → 200, `teeoff.no` → 200, et ekte
|
||
`wss://`-håndtrykk over produksjons-https bekreftet å nå helt frem til
|
||
applikasjonslaget (testet mot en ukjent turnering-id — ingen ekte data
|
||
berørt — ga korrekt 404 fra selve WS-ruten, ikke en Caddy/Next.js-feil),
|
||
og et ren-HTTP-kall mot en `/ws/*`-sti bekreftet å returnere FastAPI sin
|
||
egen JSON-404 (`{"detail":"Not Found"}`) og ikke Next.js sin HTML-404 —
|
||
beviser Caddy-ruten faktisk treffer `teecup_api`, ikke `teecup_frontend`.
|
||
|
||
- **Tilskuer-rolle LIVE (2026-07-19, ADR-026):** brukeren valgte dette som
|
||
neste steg rett etter Kommunikasjon-runden — "tilskuer" var bevisst
|
||
utsatt til feed-synligheten (ADR-025) fantes, og nå gjorde den det.
|
||
**Kjernebeslutning:** ingen ny rolle/tabell — "tilskuer" er ganske enkelt
|
||
enhver som kan SE en turnering per `tournament.visibility` (ADR-018),
|
||
utvidet til også å dekke LIVE-data (leaderboard, matcher, scorekort), ikke
|
||
bare info-siden/programtidene som før.
|
||
**Bygget:** `GET /orgs/.../leaderboard`, `.../sessions/{id}/matches` og
|
||
`.../matches/{id}/scorecard` fantes allerede (organisator-/spiller-siden),
|
||
men krevde org-medlemskap — en ren spectator kunne aldri se dem. Løst ved
|
||
å ekstrahere den delte kjernelogikken til gjenbrukbare funksjoner
|
||
(`fetch_leaderboard` i tournaments.py, `fetch_matches` i matches.py,
|
||
`fetch_scorecard` i scoring.py — samme "gjort delt"-mønster som tidligere
|
||
runder), og la tre nye offentlige endepunkter i `registration.py` kalle
|
||
dem etter egen visibility-sjekk. `own_team_ids()` (`blind_draw.py`) gjort
|
||
null-sikker (`user_id: str | None`) -- en anonym leser har per definisjon
|
||
ingen egne lag, korrekt oppførsel er tom mengde (ser kun avslørte
|
||
matcher), ikke en feil.
|
||
**To nye sikkerhetssjekker funnet under DESIGN, ikke i etterkant, samme
|
||
disiplin som tidligere ADR-018 Beslutning B-lærdommen:**
|
||
1. `session_id`/`match_id` i URL-en må eksplisitt verifiseres å høre til
|
||
NØYAKTIG `tournament_id` i samme URL — `org_connection()` setter kun
|
||
TENANT-grensen (RLS), ikke at stiens id-er faktisk henger sammen. Uten
|
||
dette kunne noen med tilgang til én offentlig turnering i en
|
||
organisasjon lest en HVILKEN SOM HELST økt/match i samme organisasjon
|
||
(inkl. en privat en) ved å gjette/prøve id-er.
|
||
2. Scorekortet krever eksplisitt at BEGGE lag har låst oppstillingen
|
||
(blind draw, ADR-013) — leaderboard/matchliste arver reveal-skjuling
|
||
automatisk via `own_team_ids()`, men scorekortet har ingen tilsvarende
|
||
innebygd sjekk.
|
||
**Bruker valgte omfang utover anbefalingen:** BÅDE leaderboard+matchliste
|
||
OG fullt hull-for-hull-scorekort per match i samme runde (anbefalingen var
|
||
kun de to første).
|
||
**Frontend:** ny `/t/[id]/live`-side (`components/public-live.tsx`) —
|
||
fargesegmentert stillingsbar (faktisk + projisert, samme visuelle idé som
|
||
den org-autentiserte leaderboard-skjermen, men egen enklere implementasjon
|
||
siden komponentene har ulik autentiseringskontekst), utvidbar øktliste →
|
||
matchliste → hull-for-hull-scorekort (fargede hull-chips per lag). Lenket
|
||
fra hovedsiden (`public-tournament.tsx`) med en ny "Følg live"-knapp.
|
||
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
||
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container):
|
||
16 automatiserte sjekker, inkl. en PRESIS test av den nye tenant-vs-sti-
|
||
sjekken (ikke bare en ukjent id, men en EKTE ANNEN turnering i SAMME org
|
||
— bekreftet at match/økt fra turnering A fortsatt ikke kan leses via
|
||
turnering B sin offentlige URL), full blind-draw-skjuling FØR/ETTER
|
||
reveal for en anonym leser, kode-overstyring (ADR-020) fungerer uendret
|
||
for de nye endepunktene, scorekort eksplisitt nektet før reveal. Ekte
|
||
typesjekket produksjonsbuild av frontend, inkl. den nye `/t/[id]/live`-
|
||
ruten.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent, `/health`/`dashboard` → 200, `teeoff.no`
|
||
upåvirket.
|
||
|
||
- **"Følg live"-siden koblet til sanntid (2026-07-19, ADR-027):** brukeren
|
||
fulgte anbefalingen fra forrige runde — `/t/[id]/live` (bygget i
|
||
tilskuer-runden samme dag) krevde omlasting for nye resultater.
|
||
**Bygget:** ny, RUTEFRI modul `app/realtime.py` (in-memory
|
||
tilkoblingsregister + `broadcast_live_update(tournament_id)`) -- ligger
|
||
bevisst BAK alle routere i importgrafen for å unngå en sirkulær import
|
||
(`registration.py` importerer allerede fra `scoring.py`/`tournaments.py`/
|
||
`matches.py`, og `messaging.py` importerer fra `registration.py`; en
|
||
kringkastingsfunksjon i noen av routerne ville derfor bitt seg selv i
|
||
halen). Kringkastingen er lagt INN I `recompute_and_cache_match_state` og
|
||
`apply_concession` selv (sistnevnte fikk en ny påkrevd `tournament_id`-
|
||
parameter) -- kallerne (hole-score/hole-result-innsending, walkover på
|
||
både match- og turnering-nivå) trenger ikke huske å gjøre noe selv. Nytt
|
||
WS-endepunkt `/ws/public/tournaments/{id}/live` i `messaging.py`
|
||
(gjenbruker `/ws/*`-Caddy-ruten fra ADR-025 uendret -- ingen ny
|
||
infrastruktur). Sender bevisst kun et "noe endret seg, hent på nytt"-
|
||
signal, ikke selve dataene -- unngår å duplisere leaderboardets
|
||
projeksjons-regnestykke/blind draw-filtrering i kringkastings-payloaden.
|
||
**Frontend:** `components/public-live.tsx` åpner en WebSocket ved
|
||
montering; hver melding teller opp en `refreshKey` som utløser refetch av
|
||
leaderboardet ALLTID, og av en økts matcher/et scorekort KUN hvis det
|
||
faktisk er utvidet/åpent på skjermen akkurat da (ingen unødvendige kall
|
||
for lukket innhold). Liten pulserende prikk lagt til ved siden av
|
||
"Følg live"-teksten som visuell bekreftelse.
|
||
**Verifisert i scratch:** anonym avvist på live-WS for en `org`-synlig
|
||
turnering (samme visibility-sjekk som REST), satt til `public`, deretter
|
||
bekreftet FAKTISK sanntidsmottak (ikke bare at REST-svaret så riktig ut)
|
||
for BÅDE et vanlig hull-resultat OG en walkover-konsesjon -- en åpen
|
||
WebSocket-tilkobling mottok kringkastings-signalet i begge tilfeller.
|
||
Leaderboard fortsatt korrekt lesbar over REST etterpå (regresjon). Ekte
|
||
typesjekket produksjonsbuild kjørt og bekreftet.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, ingen ny Caddy-endring, `docker compose up -d --build
|
||
teecup_api teecup_frontend`, begge containere boot-et rent. Verifisert
|
||
grundig etterpå: `/health`/`dashboard` → 200, `teeoff.no` upåvirket, OG et
|
||
ekte `wss://`-håndtrykk mot den NYE `/live`-ruten over produksjons-https
|
||
(ukjent turnering-id, ingen ekte data berørt) ga korrekt 404 fra selve
|
||
applikasjonslaget.
|
||
|
||
- **Fire brukerrapporterte UI-/UX-hull, DIAGNOSTISERT OG NOTERT, IKKE fikset
|
||
(2026-07-19):** brukeren rapporterte fire ting fra faktisk bruk av
|
||
`teecup.teeoff.no` rett før PWA-runden startet. Root cause funnet ved
|
||
kodegjennomgang for tre av fire (ikke gjettet). Full detalj i
|
||
FEATURE_BACKLOG.md sin nye seksjon "Rapporterte UI-/UX-hull (2026-07-19)".
|
||
Kort:
|
||
1. Dashboard-turneringskortet viser "Ingen datoer satt" alltid — leser
|
||
`tournament.start_date`/`end_date` (eget felt, ADR-015), som INGEN
|
||
UI-skjema noensinne skriver til. Skal enten få et faktisk skjemafelt,
|
||
eller kortet bør heller utlede datoen fra øktenes `scheduled_at`.
|
||
2. **Reell, bekreftet rewrite-bug:** `/orgs/{id}/members` gir en rå
|
||
FastAPI-404 (`{"detail":"Not Found"}`) i stedet for medlemssiden.
|
||
`next.config.mjs` sin `rewrites()` returnerer en plain array (implisitt
|
||
"afterFiles") — DYNAMISKE Next.js-sider sjekkes ETTER rewrites, så
|
||
`/orgs/:path*`-proxy-regelen (ADR-016) fanger kallet FØR
|
||
`app/orgs/[id]/members/page.tsx` noensinne nås. Dette er den FØRSTE
|
||
frontend-siden som er nestet direkte under et allerede proxyet prefiks
|
||
— ingen tidligere skjerm har truffet dette. Selve siden/komponenten er
|
||
riktig bygget, kun ruten dit er blokkert.
|
||
3. Ingen UI-vei til å opprette en ANDRE organisasjon når man allerede har
|
||
én — `CreateOrganizationState` i `dashboard.tsx` vises kun ved null
|
||
org-er. Backend støtter det fullt ut allerede (ADR-021).
|
||
4. Ingen sammendrag/indikator noe sted for "alle runder har fått dato" —
|
||
må sjekkes manuelt per øktkort på program-skjermen.
|
||
**Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens
|
||
eksplisitte instruks, PWA-runden prioriteres først.
|
||
|
||
- **PWA: installasjon + full offline scoreregistrering, BYGGET, IKKE ENNÅ
|
||
RULLET UT (2026-07-19, ADR-028):** bruker valgte det mest ambisiøse
|
||
omfanget (installerbar app OG offline scoreregistrering, ikke bare
|
||
installasjon), og valgte å generere enkle ikoner nå fremfor å vente på
|
||
ekte design (med eksplisitt beskjed om at de er midlertidige).
|
||
**Ikoner:** ingen `PIL`/`rsvg-convert`/`imagemagick` tilgjengelig i miljøet
|
||
— løst med et scratch npm-prosjekt (`sharp@0.33.5`, node18-kompatibel
|
||
versjon; nyeste `sharp` krever node ≥20 og feilet først) som genererte et
|
||
enkelt grønt golf-flagg-ikonsett (`public/icons/icon-192.png`,
|
||
`icon-512.png`, `icon-maskable-512.png`) + erstattet den gamle
|
||
`public/apple-icon.png` (var v0.app sin generiske plassholderlogo, ikke
|
||
TeeCup-merkevare i det hele tatt).
|
||
**Bygget:** `app/manifest.ts` (Next.js sin innebygde manifest-generator,
|
||
ikke en statisk `manifest.json`), `appleWebApp`-metadata i `layout.tsx`
|
||
(iOS leser ikke manifest.json for hjemskjerm-oppførsel),
|
||
`components/sw-register.tsx` (stille no-op uten SW-støtte),
|
||
håndskrevet `public/sw.js` (ingen next-pwa/workbox-avhengighet — nettverk
|
||
først/cache-fallback for navigasjon + `/orgs/*`-GET-er, BEVISST ikke
|
||
stale-while-revalidate, se ADR-028 for hvorfor), `public/offline.html`.
|
||
**Offline scoreregistrering:** ny `lib/offline-queue.ts` (IndexedDB-kø,
|
||
ren klientkode — ikke i SW-en), koblet inn i `session-scorecard.tsx` sin
|
||
`submitStroke`/`submitHoleResult`: sjekker `navigator.onLine` først, køer
|
||
kun ved en EKTE nettverksfeil (ikke ved et avvist HTTP-svar som "matchen
|
||
er avgjort" — det vises fortsatt som vanlig feiltekst). Lokalt overlay
|
||
(`pendingStrokes`/`pendingResults`) viser køede verdier umiddelbart,
|
||
merket "Lagret lokalt · venter på synk". Auto-synk ved `window`s
|
||
`online`-event PLUSS en manuell "Synkroniser nå"-knapp (bevisst IKKE
|
||
Background Sync API — iOS Safari støtter den ikke). Et definitivt avvist
|
||
synk-forsøk (f.eks. matchen ble avgjort på en annen enhet mens denne var
|
||
offline) fjernes fra køen og vises som feilmelding, henger aldri for
|
||
alltid.
|
||
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som
|
||
deployes) kjørt og bekreftet — alle 16 ruter listet inkl.
|
||
`/manifest.webmanifest`. Kort container-boot + `curl` bekreftet manifest/
|
||
service worker/ikoner/offline.html alle svarer riktig (200, riktig
|
||
innhold).
|
||
**IKKE gjort denne runden, viktig å være ærlig om:** ingen faktisk
|
||
nettleser-basert offline-test (Chrome DevTools sin Offline-bryter,
|
||
faktisk "Legg til på hjemskjerm") — intet nettleserverktøy tilgjengelig i
|
||
denne økten. Kun kodegjennomgang + build-verifisering. **Brukeren bør selv
|
||
teste scorekort-siden med DevTools Offline-modus før tillit i skarp
|
||
bruk.**
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: `docker
|
||
compose up -d --build teecup_frontend`. Compose gjenskapte OGSÅ
|
||
`teecup_api` som en bivirkning (avhengighets-oppløsning i `up --build`)
|
||
— ingen backend-kode rørt, ren uendret gjenoppbygging, begge containere
|
||
boot-et rent (`Application startup complete` / Next.js `Ready`).
|
||
Verifisert: `/health`/`dashboard` → 200, `/manifest.webmanifest`/`sw.js`/
|
||
`icons/icon-512.png` alle 200 over ekte https, `teeoff.no` upåvirket.
|
||
|
||
- **Fire nye hull rapportert fra faktisk testing av blind draw + scorekort
|
||
(foursome), DIAGNOSTISERT OG NOTERT, IKKE fikset (2026-07-19):** rett
|
||
etter PWA-runden. Full detalj i FEATURE_BACKLOG.md sin nye seksjon
|
||
"Rapporterte hull, blind draw + scorekort (2026-07-19)". Kort:
|
||
1. **Bekreftet root cause:** valgt spiller forsvinner ikke fra
|
||
nedtrekkslisten i `AddSlotForm` (`session-blind-draw.tsx`) — den
|
||
merkes kun `disabled` på `<option>`-nivå, som HTML fortsatt viser
|
||
(bare gråtonet). Skal FILTRERES bort, ikke deaktiveres.
|
||
2. **Bekreftet, større enn antatt:** tee-valg (Dame/Herre) bør følges av
|
||
spillerens registrerte kjønn automatisk. Krever en backend-utvidelse
|
||
først — `RosterEntry`/`list_roster` (`tournaments.py`) mangler
|
||
`player.gender` helt i responsen, selv om feltet finnes i skjemaet og
|
||
alt eksponeres via `GET /orgs/{id}/players`. Åpne spørsmål om låst vs.
|
||
forhåndsutfylt valg, og fallback ved ukjent kjønn/manglende
|
||
matchende tee, før bygging.
|
||
3. Ren frontend-UX: tallvelger (1–9 + utvidbar "10 eller flere") i stedet
|
||
for pluss/minus-steppere for slagregistrering. Ingen backend-endring.
|
||
4. **HCP "ikke hensyntatt" i foursome-test — IKKE bekreftet som bug.** Ett
|
||
definitivt, bekreftet hull uavhengig av alt annet: det beregnede
|
||
`course_handicap`/`playing_handicap` eksponeres ALDRI noe sted i
|
||
API-et eller UI-et (sjekket `matches.py`, `scoring.py`, begge
|
||
frontend-skjermene) — usynlig selv når beregningen er korrekt. I
|
||
TILLEGG en kjent, tidligere dokumentert begrensning som kan ha slått
|
||
inn: foursome/greensome/scramble sin side-handicap beregnes kun når
|
||
BEGGE deltakere har en matchende `tee_rating`, og `tee_rating`-rader
|
||
lages i dag ALLTID kun med `full_18`-omfang — en `front_9`/`back_9`-
|
||
testøkt ville derfor ALDRI fått handicap beregnet i det hele tatt.
|
||
**Trenger avklaring:** var testøktens `hole_config` `full_18`, og
|
||
hadde begge sider alle sine deltakere+tee lagt til før scoring?
|
||
**Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens
|
||
eksplisitte instruks.
|
||
|
||
- **Tre av fire hull FIKSET OG SCRATCH-VERIFISERT (2026-07-19), samme dag:**
|
||
bruker ba eksplisitt om punkt 1 og 3, og presiserte punkt 2 (se under)
|
||
samt bekreftet den nøyaktige handicap-formelen for punkt 4.
|
||
1. **Spillerliste-fiks:** `AddSlotForm` (`session-blind-draw.tsx`)
|
||
filtrerer nå allerede-valgte roster-rader helt bort i stedet for å
|
||
bare `disabled`-merke `<option>`-en (som HTML uansett viser gråtonet).
|
||
2. **Tee-valg — OMDEFINERT etter brukerens presisering, IKKE bygget
|
||
ennå:** min opprinnelige antakelse ("lås tee til spillerens kjønn")
|
||
var feil i premisset — en golfbane har ikke fysisk kjønnsdelte
|
||
utslag, kun eventuelt kjønnsdelt RATING av samme utslag (noen klubber
|
||
sloper bevisst ikke ett utslag for ett kjønn). Bekreftet mot ekte
|
||
Tjøme-data: hvert fysisk utslag ligger i dag som TO `tee`-rader med
|
||
samme navn (én per kjønn) — nøyaktig konflateringen brukeren pekte
|
||
på. Riktig fiks er å flytte `gender` fra `tee` til `tee_rating`
|
||
(skjemaendring + datamigrering + import-/handicap-/remap-kode).
|
||
Betydelig større enn antatt — se FEATURE_BACKLOG.md for full
|
||
analyse og de tre åpne designspørsmålene som trengs FØR bygging.
|
||
3. **Tallvelger:** ny `StrokePicker`-komponent
|
||
(`session-scorecard.tsx`) — 1-9 direkte, "10+"-knapp åpner 10-19,
|
||
med en "tilbake"-lenke. Erstatter ±-stepperen og all dens døde kode
|
||
helt.
|
||
4. **HCP-bug, BEKREFTET og FIKSET:** brukeren beskrev selv riktig
|
||
formel (kombinert hcp/2 justert for prosent, laveste side til 0
|
||
mottatte slag ved matchplay-hcp, resten fordelt fra stroke index 1) —
|
||
lest direkte mot `handicap_engine.py` og bekreftet at koden allerede
|
||
implementerer NØYAKTIG dette. Bugen lå ikke i formelen, men i at den
|
||
ALDRI kjørte for front_9/back_9-økter: `compute_and_store_side_
|
||
handicaps` (`app/handicap.py`) joinet `tee_rating` på øktens
|
||
`hole_config` som rating-scope, men slike rader lages i praksis kun
|
||
med `scope='full_18'` (matcher ADR-008 sin allerede etablerte
|
||
design — full_18-ratingen skal alltid brukes, front/back-9-
|
||
fordelingen skjer senere ved selve slagtildelingen). Bekreftet
|
||
direkte mot EKTE `teecup_db` (read-only): brukerens rapporterte
|
||
testøkt (`front_9`+`foursome`) hadde `course_handicap`/
|
||
`playing_handicap = NULL` på alle fire deltakere. Påvirket ALLE
|
||
formater på front_9/back_9-økter, ikke bare foursome. Fikset: scope
|
||
hardkodet til `full_18`, `hole_config`-parameteren fjernet helt fra
|
||
funksjonen og alle tre kallstedene (var død etter fiksen).
|
||
**Scratch-verifisert presist:** samme scenario gjenskapt (foursome+
|
||
front_9, hcp 10/20 mot 5/15) — course/playing handicap kom ut nøyaktig
|
||
som beregnet for hånd (10/21 vs. 5/16, kombinert 16/10), og et hull
|
||
med IDENTISK bruttoscore (5-5) på begge sider ga et IKKE-delt
|
||
resultat ("a" vant) — direkte bevis på at hcp nå faktisk brukes.
|
||
**Ny, urelatert bug funnet under samme scratch-test, IKKE fikset:**
|
||
stroke-modus-innsending på en bane uten registrerte hull krasjer rått
|
||
(500 `IndexError` i `allocate_over_played_holes`) i stedet for en ren
|
||
`VALIDATION_FAILED` — samme klasse feil som en tidligere fikset
|
||
manglende-handicap-krasj. Notert i FEATURE_BACKLOG.md, ikke bygget.
|
||
**Scratch-infrastruktur:** isolert `teecup_scratch`-database +
|
||
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
||
container (`python:3.12-slim`, `app/` og `handicap_engine.py` montert
|
||
read-only), alt ryddet opp etter verifisering. Typesjekket
|
||
produksjonsbuild kjørt for frontend-fiksene (1+3), alle 16 ruter listet.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt, se eget
|
||
punkt lenger ned for punkt 2 (som ble bygget og rullet ut sammen med
|
||
disse tre i én utrulling).
|
||
|
||
- **Punkt 2 (tee/kjønn) BYGGET OG SCRATCH-VERIFISERT (2026-07-19, ADR-029),
|
||
samme dag, rett etter designavklaringen:** bruker bekreftet begge
|
||
anbefalte alternativer (helautomatisk tee-valg, "feil høyt" ved
|
||
manglende kjønn/rating). Ny migrasjon `014_tee_gender_to_rating.sql`:
|
||
flytter `gender` fra `tee` til `tee_rating` (unikhet
|
||
`(tee_id, scope)` → `(tee_id, scope, gender)`), slår sammen
|
||
eksisterende kjønns-par-tee-rader til én fysisk tee-rad per (bane,
|
||
navn) — velger laveste id som "beholder", flytter `tee_rating`- og
|
||
`match_participant.tee_id`-referanser dit, sletter duplikatene.
|
||
**Reell bug funnet OG fikset UNDER selve migrasjonsskrivingen** (ikke i
|
||
produksjon): første versjon prøvde å droppe den GAMLE
|
||
`(tee_id, scope)`-unikheten ETTER sammenslåingen i stedet for FØR —
|
||
kolliderte da midlertidig med beholder-tee-ens egen eksisterende rad
|
||
for samme scope. Rettet ved å bytte rekkefølge (drop gammel unikhet FØR
|
||
sammenslåing, legg til ny kjønnsbevisst unikhet ETTER).
|
||
**Kodeendringer:** `app/handicap.py` sin `compute_and_store_side_
|
||
handicaps` joiner nå også på `player.gender` (i tillegg til forrige
|
||
rundes full_18-fiks). `app/routers/matches.py` sin `add_participant`
|
||
validerer FØR innsetting: spiller har registrert kjønn, OG valgt utslag
|
||
har en matchende rating — begge avvist med klar `VALIDATION_FAILED`.
|
||
`app/routers/tournaments.py` sin `_remap_course` (bane-bytte) matcher nå
|
||
på tee-navn OG bekrefter matchende kjønnsrating for hver berørte
|
||
spiller. `app/routers/courses.py`: `TeeCreate` redesignet fra ett flatt
|
||
kjønn+rating-sett til en `ratings`-liste (1-2 elementer, distinkte
|
||
kjønn); ADR-019 sin `import_official_course` lager nå ÉN tee-rad per
|
||
fysisk teeoff-utslag (før: to, én per kjønn) med inntil to
|
||
`tee_rating`-rader under. `session-blind-draw.tsx` forenklet — ingen
|
||
"H"/"D"-suffiks, ingen kjønnslogikk i det hele tatt lenger (serveren
|
||
løser det).
|
||
**Verifisert grundig, flere separate scratch-runder:**
|
||
1. Selve fletting-migrasjonen kjørt mot SYNTETISK data som gjenskaper
|
||
Tjøme-mønsteret nøyaktig (to par + én enslig utslag, pluss en
|
||
`match_participant`-rad som bevisst pekte til DUPLIKATEN, ikke
|
||
beholderen) — bekreftet: to rader ble til én, `tee_rating.gender`
|
||
riktig fylt inn for begge, `match_participant.tee_id` korrekt
|
||
reparert til beholderens id, det enslige utslaget urørt.
|
||
2. Full API-runde (18 automatiserte sjekker via et Python/urllib-
|
||
testskript — httpx var ikke tilgjengelig i vertsmiljøet, løst med
|
||
stdlib `http.cookiejar`/`urllib` i stedet): manuell tee-opprettelse
|
||
(to ratinger, kun én rating, duplikat kjønn avvist), `GET tees`
|
||
viser riktig sammenslått struktur, kvinne+dame-rating lykkes,
|
||
mann+kun-dame-rating avvist tydelig, spiller uten kjønn avvist
|
||
tydelig, full kjønnsblandet singel-match scoret korrekt.
|
||
3. Offisiell import kjørt mot EKTE `teeoff_api` (Borregaard Golfklubb,
|
||
samme mønster som ADR-019 sin opprinnelige verifisering) — bekreftet
|
||
4 fysiske utslag importert med begge kjønnsratinger hver, ikke 8
|
||
doble rader.
|
||
4. `_remap_course`: bane-bytte til en bane UTEN matchende kjønnsrating
|
||
avvist tydelig, bane-bytte til en bane MED matchende rating lykket.
|
||
Ekte typesjekket produksjonsbuild av frontend kjørt på nytt og
|
||
bekreftet etter blind draw-forenklingen.
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: migrasjon
|
||
014 kjørt mot ekte `teecup_db` FØRST (Tjømes 8 tee-rader slått sammen
|
||
til 4 — bekreftet 0 brutte `match_participant.tee_id`-referanser
|
||
etterpå med en direkte spørring), deretter `docker compose up -d
|
||
--build teecup_api teecup_frontend` sammen med punkt 1/3/4 i samme
|
||
utrulling. Begge containere boot-et rent, `/health`/`dashboard` → 200,
|
||
`teeoff.no` upåvirket.
|
||
|
||
- **Dashboard-dato-fiks (ADR-030) + rediger spiller, BYGGET OG SCRATCH-
|
||
VERIFISERT (2026-07-19/20):** brukeren viste et faktisk skjermbilde av
|
||
det tidligere dokumenterte datovisning-hullet (økt planlagt til "11.
|
||
juli" på Program-fanen, men dashbord-kortet viste fortsatt "Ingen
|
||
datoer satt") og ba samtidig om å kunne redigere spilleres HCP.
|
||
**Dato-fiks:** `list_tournaments` (`app/routers/tournaments.py`) utleder
|
||
nå datospennet fra øktenes `scheduled_at` (`COALESCE` med et evt.
|
||
eksplisitt satt `tournament.start_date`/`end_date`, som fortsatt vinner
|
||
om det noensinne settes — ingen UI gjør det i dag). Kun denne ene
|
||
spørringen endret, `_TOURNAMENT_COLUMNS` (brukt av opprett/PATCH sine
|
||
`RETURNING`-klausuler) urørt. Se ADR-030.
|
||
**Rediger spiller:** ny `PATCH /orgs/{id}/players/{id}`
|
||
(`app/routers/players.py`, vanlig `exclude_unset`-mønster, dekker alle
|
||
spillerfelt) + ny "Rediger spiller"-handling i rosterradens meny
|
||
(`tournament-detail.tsx`). **Bevisst grense, forklart i selve UI-et:**
|
||
endrer spillerpoolen, IKKE et lags allerede frosne
|
||
`handicap_index_snapshot` (ADR-007) — reproduserbarhet for allerede
|
||
opprettede lag er et bevisst, tidligere designvalg, ikke noe denne
|
||
fiksen skulle endre. Skjemaet sier dette rett ut i stedet for å late som
|
||
endringen slår inn overalt.
|
||
**Scratch-verifisert, 9 sjekker:** spiller-PATCH (hcp-endring, delvis
|
||
PATCH lar andre felt stå urørt, tomt PATCH avvist, ukjent id gir 404),
|
||
DEN KRITISKE sjekken (en spillers allerede frosne roster-snapshot for et
|
||
eksisterende lag forble UENDRET etter en påfølgende spiller-PATCH —
|
||
bekrefter ADR-007 fortsatt holder), dato-utledning (ingen økter → null,
|
||
to økter 11./12. juli → riktig utledet spenn, eksplisitt satt dato
|
||
vinner over utledet). Ekte typesjekket produksjonsbuild kjørt og
|
||
bekreftet.
|
||
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: `docker
|
||
compose up -d --build teecup_api teecup_frontend`, ingen migrasjon.
|
||
Begge containere boot-et rent, `/health`/`dashboard` → 200, `teeoff.no`
|
||
upåvirket. Verifisert mot EKTE data (ikke bare scratch): "De Gamle er
|
||
Eldst" viser nå korrekt 11. juli 2026 i stedet for "Ingen datoer satt".
|
||
|
||
- **To nye punkter reist 2026-07-20, GJENNOMTENKT OG FORESLÅTT, IKKE
|
||
bygget:** brukeren ba eksplisitt om at punkt 1 tenkes grundig gjennom
|
||
og legges frem som et forslag FØR bygging (ikke kode med en gang), og
|
||
markerte det eksplisitt som prioritet over punkt 2.
|
||
1. **Personlig landingsside for enhver registrert bruker (PRIORITERT).**
|
||
Bekreftet reelt hull ved kodegjennomgang: `app/page.tsx` sender
|
||
enhver innlogget bruker til `/dashboard`, som viser "opprett
|
||
organisasjon" så snart `organizations.length === 0` — også for en
|
||
bruker som KUN er spiller (koblet via `player.user_id`,
|
||
ADR-017 B), aldri organisator. Fullt forslag skrevet i
|
||
FEATURE_BACKLOG.md: ett samlet dashboard (ikke to atskilte ruter),
|
||
ny "Mine runder"-seksjon (tverr-org, krever en ny SECURITY
|
||
DEFINER-bro `player_organizations_for_user()` + utvidelse av
|
||
`/auth/me`, samme mønster som `public_tournament_org()` m.fl.),
|
||
organisasjonsseksjonen uendret under. Venter på brukerens
|
||
bekreftelse på retningen før bygging starter.
|
||
2. **Midlertidige spillere + automatisk etter-runde-e-post (lavere
|
||
prioritet, likevel dokumentert grundig).** Presisert ved
|
||
kodegjennomgang: det meste av "midlertidig spiller"-behovet
|
||
dekkes ALLEREDE av eksisterende `POST /orgs/{id}/players`
|
||
(krever aldri en konto). Det som faktisk mangler er en PROAKTIV
|
||
e-post-utsending etter runden (scorekort + innloggingslenke) —
|
||
foreslått som en eksplisitt organisator-knapp per økt (ikke en
|
||
automatisk bakgrunnsjobb, for å unngå uventede e-poster fra en
|
||
gjettet "runden er ferdig"-deteksjon). Tre åpne spørsmål notert i
|
||
FEATURE_BACKLOG.md (økt- vs. turnering-nivå, dobbel-utsending-
|
||
sperre, locale).
|
||
**Ingen kode skrevet for noen av de to ennå** — dette var bevisst en
|
||
tenke-og-foreslå-runde, ikke en byggerunde.
|
||
|
||
- **Punkt 1 BYGGET OG SCRATCH-VERIFISERT (2026-07-20), samme dag, rett etter
|
||
forslaget over:** bruker svarte "Gjør punkt 1" med et utvidet omfang —
|
||
inkluder også opprettelse/redigering/sletting av personlig informasjon
|
||
(profilbilde, fornavn, etternavn, fødselsdato, kjønn, HCP, hjemmeklubb).
|
||
**Ny migrasjon `015_user_profile.sql`:** `app_user` får de sju nye
|
||
profilfeltene — ETT sett PER KONTO, bevisst IKKE slått sammen med de
|
||
org-scopede `player`-radene (se ADR-031 Beslutning B for full
|
||
begrunnelse — to reelt atskilte konsepter). Ny
|
||
`player_organizations_for_user()`-bro (femte instans av samme
|
||
SECURITY DEFINER-mønster som `public_tournament_org()` m.fl.).
|
||
**Backend:** `/auth/me` utvidet med profilfeltene + `avatar_url` +
|
||
`my_tournaments` (turneringer brukeren er ROSTRET i, tverr-org, samme
|
||
N+1-org_connection()-mønster som organisasjonslisten). Ny
|
||
`PATCH /auth/profile` (vanlig exclude_unset), `POST`/
|
||
`DELETE /auth/profile/avatar` (samme ekte multipart→AVIF-mønster som
|
||
turnering-hero-bilder).
|
||
**Reelt sikkerhetshull funnet UNDER bygging, ikke antatt på forhånd:**
|
||
testet "Mine runder" mot en EKTE ren spiller (rostret, ingen
|
||
org-medlemskap) og oppdaget at `check_visibility()` (ADR-018) kun ga
|
||
deltaker-tilgang for `visibility='participants'` — IKKE for `'org'`
|
||
(DEFAULT for enhver ny turnering). En ren spiller ville altså vært
|
||
stengt ute fra sin EGEN, helt vanlige turnering — nøyaktig
|
||
brukergruppen "Mine runder" er bygget for. Fikset: deltaker-sjekken
|
||
gjelder nå begge ikke-offentlige tier, med eksplisitt begrunnelse om
|
||
at visibility styrer eksponering mot UTENFORSTÅENDE, aldri mot faktiske
|
||
deltakere. Verifisert presist at dette er en REN UTVIDELSE, ingen
|
||
innstramming: samme scratch-test bekreftet at en helt ubeslektet
|
||
FREMMED (innlogget, ikke deltaker) og en ANONYM leser fortsatt begge
|
||
avvises identisk som før (403 NOT_VISIBLE).
|
||
**Frontend:** `account-settings.tsx` fikk en ny "Personlig profil"-
|
||
seksjon (avatar-opplasting/fjerning, fornavn/etternavn/fødselsdato/
|
||
kjønn/HCP/hjemmeklubb-skjema). `dashboard.tsx` fikk en ny
|
||
`MyToursSection` («Mine runder», øverst, lenker til den offentlige
|
||
turnering-siden) + en mykere, sekundær utgave av "opprett organisasjon"-
|
||
tomtilstanden når brukeren allerede har spiller-data å vise.
|
||
**Bevisst UTENFOR omfang, klart flagget, IKKE en del av denne rundens
|
||
leveranse:** "Mine runder" lenker IKKE til lag-chat/scorekort ennå — de
|
||
krever fortsatt ekte organisasjonsmedlemskap (`get_authorized_org`), en
|
||
strengere, bredt brukt sperre som ikke ble endret denne runden (egen,
|
||
større og mer risikofylt endring, se ADR-031).
|
||
**Scratch-verifisert, 15 sjekker:** full profil-CRUD (alle felt satt,
|
||
delvis PATCH lar andre felt stå urørt, eksplisitt `null` sletter et
|
||
felt, tomt PATCH avvist, avatar lastet opp med ekte AVIF-URL og
|
||
slettet igjen, ugyldig filtype avvist), "Mine runder" for en EKTE ren
|
||
spiller uten org-medlemskap, OG den kritiske sikkerhetssjekken over.
|
||
Ekte typesjekket produksjonsbuild kjørt og bekreftet.
|
||
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: migrasjon
|
||
015 kjørt mot ekte `teecup_db` (bekreftet nye kolonner + funksjon
|
||
finnes), deretter `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`/
|
||
`/account` → 200, `teeoff.no` upåvirket.
|
||
|
||
- **Oppfølging samme dag: e-post + mobil, BYGGET OG SCRATCH-VERIFISERT
|
||
(ADR-032):** brukeren påpekte rett etter forrige runde at "identifikatoren"
|
||
(e-post) manglet i profilen, og etterspurte mobil med landsnummer.
|
||
Spurte samtidig hvorfor V0 ikke brukes til det visuelle her — svarte at
|
||
jeg ikke har V0 som et verktøy jeg selv kan kalle (all V0-bruk i
|
||
prosjektet har vært brukeren som designer i v0.app og sender meg
|
||
zip-eksporter), og at disse siste tilføyelsene er små, inkrementelle
|
||
skjemafelt i eksisterende komponenter (gjenbruker allerede etablerte
|
||
Tailwind/shadcn-mønstre) der en full V0-runde (design→eksport→diff→
|
||
sammenslåing) ville vært en unødvendig omvei.
|
||
**Mobil:** `mobile_country_code`+`mobile_number` (to separate felt, ikke
|
||
én sammensatt streng), lagt til i den EKSISTERENDE `PATCH /auth/profile`
|
||
— ren tilføyelse, ingen ny sikkerhetsvurdering nødvendig.
|
||
**E-post — bevisst IKKE en enkel PATCH:** e-post er innloggings-
|
||
identifikatoren (magic-link-mål) — en vanlig PATCH ville latt en
|
||
skrivefeil eller en kapret sesjon stjele kontoen for godt. Bygget som et
|
||
ekte to-stegs bekreftelsesløp i stedet, samme `token_hash`+
|
||
`expires_at`+`consumed_at`-mønster som `magic_link_token` (migrasjon
|
||
004): ny `email_change_token`-tabell (migrasjon `016_profile_
|
||
contact.sql`). `POST /auth/profile/email` (krever sesjon, sender
|
||
bekreftelseslenke til den NYE adressen -- ikke den gamle, beviser
|
||
eierskap av MÅLET). `POST /auth/profile/email/confirm` (ingen sesjon
|
||
påkrevd, samme mønster som selve magic-link-verifiseringen -- lenken
|
||
kan åpnes på en annen enhet enn den som ba om byttet). E-posten endres
|
||
ALDRI før lenken faktisk åpnes. Duplikat-sjekk kjøres TO GANGER (ved
|
||
forespørsel og rett før selve byttet, i tilfelle adressen ble tatt i
|
||
mellomtiden), pluss den eksisterende unike indeksen som siste
|
||
bakstopper. Ny `/verify-email`-side (samme mønster som `/verify`).
|
||
**Scratch-verifisert, 10 sjekker:** mobil satt via vanlig PATCH; vanlig
|
||
profil-PATCH rører aldri e-post; bytte til allerede brukt adresse
|
||
avvist (409); e-post uendret helt til bekreftelse; ugyldig kode avvist;
|
||
gyldig kode fullfører byttet; SAMME kode kan ikke gjenbrukes; en helt ny
|
||
innlogging med den GAMLE adressen oppretter en fersk, tom konto (beviser
|
||
byttet er reelt og fullstendig). Ekte typesjekket produksjonsbuild kjørt
|
||
og bekreftet (ny `/verify-email`-rute listet).
|
||
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt (spurte
|
||
samtidig og bekreftet at `teecup.teeoff.no/dashboard` nå er DEN samme
|
||
adressen for enhver innlogget bruker uansett rolle — nettopp poenget
|
||
med ADR-031/032): migrasjon 016 kjørt mot ekte `teecup_db` (bekreftet
|
||
nye kolonner + `email_change_token`-tabell finnes), deretter `docker
|
||
compose up -d --build teecup_api teecup_frontend`. Begge containere
|
||
boot-et rent, `/health`/`/dashboard`/`/account`/`/verify-email` → 200,
|
||
`teeoff.no` upåvirket.
|
||
|
||
- **Medlemsside-ruten FIKSET OG LIVE (2026-07-20):** brukeren ba eksplisitt
|
||
om å ta fatt på dette (det mest presserende av de fire UI-hullene notert
|
||
2026-07-19 — siden var helt utilgjengelig). Root cause var allerede
|
||
presist diagnostisert: `next.config.mjs` sin `rewrites()` (plain array,
|
||
implisitt "afterFiles") fanger `/orgs/:path*` FØR Next.js sine egne
|
||
DYNAMISKE sider sjekkes, så `app/orgs/[id]/members/page.tsx` ble aldri
|
||
nådd — kallet gikk til FastAPI i stedet, som ga en rå 404.
|
||
**Fikset:** siden flyttet til `app/organizations/[id]/members/page.tsx`
|
||
(utenfor `/orgs/*`-prefikset), eneste lenke (`dashboard.tsx`) oppdatert.
|
||
Lagt til en forklarende kommentar i `next.config.mjs` sin `rewrites()`
|
||
for å forhindre samme feil ved en fremtidig ny side.
|
||
**Verifisert med ekte produksjonsbuild + container-boot** (ikke bare
|
||
typesjekk): den nye ruten (`/organizations/{id}/members`) rendrer
|
||
faktisk `OrgMembers`-komponenten med riktig `organizationId`/`orgName`
|
||
i RSC-payloaden, IKKE en 404 eller innloggingssiden.
|
||
**Rullet ut live**, ren frontend-endring, ingen migrasjon, `teeoff.no`
|
||
upåvirket.
|
||
|
||
- **De to siste UI-/UX-hullene fra 2026-07-19 FIKSET OG LIVE (2026-07-21):**
|
||
alle fire punkter i den runden er dermed fikset.
|
||
1. **«Ny organisasjon»:** ny `NewOrganizationControl` i `dashboard.tsx`
|
||
(identisk inline-ekspanderende-form-mønster som `NewTournamentControl`),
|
||
lagt til i `OrganizationView` sin header ved siden av
|
||
«Medlemmer»/«Ny turnering» — synlig uansett hvor mange org-er brukeren
|
||
allerede har. Ingen backend-endring (`POST /orgs` hadde aldri en
|
||
grense).
|
||
2. **Dato-sammendrag:** ny `DateCoverageSummary` i `tournament-
|
||
program.tsx`, vist øverst i øktlisten når minst én økt finnes: «X av Y
|
||
runder har fått dato og klokkeslett» (uthevet når alle er satt). Ren
|
||
klientside-telling av allerede lastet `scheduled_at`, ingen
|
||
backend-endring.
|
||
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som
|
||
deployes) kjørt og bekreftet, alle 16 ruter listet. **Rullet ut live**,
|
||
bruker bekreftet eksplisitt: `docker compose up -d --build
|
||
teecup_frontend` (gjenskapte også `teecup_api` som compose sin vanlige
|
||
avhengighets-bivirkning, ingen backend-kode rørt). Begge containere
|
||
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
|
||
- **Dashboard/konto-runde (2026-07-21): to store punkter reist samtidig av
|
||
brukeren.** (1) "Alt relatert til dashboard/account/brukerkontoer" —
|
||
scopet sammen med brukeren via spørsmål: tom-tilstand-redesignet ble
|
||
PAUSERT (bruker ba om "juster retningen" og reiste et dypere spørsmål om
|
||
hvorvidt organisasjon fortsatt bør være "det som meldes først" — se eget
|
||
punkt i FEATURE_BACKLOG.md, min vurdering: nei, bør bli ett likestilt
|
||
valg blant flere). (2) Et helt nytt, stort forslag om frittstående
|
||
rundeføring + detaljert statistikk (putter/chip/bunkerslag/straffeslag/
|
||
førsteputt-lengde) UTEN turnering/organisasjon — grundig notert i
|
||
FEATURE_BACKLOG.md med en eksplisitt arkitektur-advarsel: dette
|
||
UTFORDRER tenant-invarianten (`organization_id` på alle domenetabeller)
|
||
direkte og trenger en egen ADR, ikke bygget denne runden.
|
||
**Deltaker-tilgang til lag-chat/scorekort — ✅ BYGGET OG LIVE
|
||
2026-07-21** (én av tre konkrete følgepunkter brukeren bekreftet i samme
|
||
runde, de to andre — sekundær e-post, HCP-historikk — tas fortløpende
|
||
etterpå): fjernet den blanke `get_authorized_org`-sperren fra ni
|
||
endepunkter på tvers av `messaging.py`/`scoring.py`/`matches.py`/
|
||
`tournaments.py`/`courses.py`, erstattet med de ALLEREDE eksisterende
|
||
domene-sjekkene (`user_is_rostered_on_team`/`user_is_match_participant`/
|
||
`user_is_team_captain`) som viste seg å støtte ikke-org-medlemmer helt
|
||
fint fra før — de var bare aldri nåbare. To nye delte hjelpefunksjoner i
|
||
`team_authz.py` (`is_org_member`, `user_is_tournament_participant` —
|
||
sistnevnte FLYTTET dit fra `registration.py` for å unngå sirkulær
|
||
import) dekker de endepunktene som IKKE hadde noen finkornet sjekk fra
|
||
før (ren fjerning der ville åpnet dem for enhver innlogget bruker).
|
||
`/auth/me` sin `my_tournaments` fikk `my_session_id`/`my_match_id`;
|
||
"Mine runder"-kortet fikk "Lag-chat"/"Scorekort"-lenker.
|
||
**Scratch-verifisert grundig, 43 sjekker** (isolert scratch-rolle+MinIO+
|
||
engangs API-container): rostret ikke-medlem fikk korrekt tilgang
|
||
overalt (inkl. faktisk sendt chat-melding og hull-resultat), fortsatt
|
||
avvist fra det ANDRE lagets chat, en helt fremmed bruker avvist overalt,
|
||
org-eier beholder alt UNNTATT lag-chat (uendret, med vilje), kryss-org-
|
||
isolasjon bekreftet. **Reelt funn UNDER selve scratch-testingen:** en
|
||
rostret-men-ikke-kaptein spiller ble først uventet GODTATT til walkover
|
||
— viste seg å være en allerede tiltenkt, dokumentert fallback
|
||
(`user_is_team_captain`: "ingen kaptein utpekt ennå = enhver rostret
|
||
spiller godtas"), ikke en bug — testen ble rettet (la til en faktisk
|
||
kaptein) og bekreftet deretter riktig avvisning. `test_isolation.sql`
|
||
12/12 uendret (ingen skjemaendring). Ekte typesjekket produksjonsbuild
|
||
kjørt og bekreftet.
|
||
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket.
|
||
|
||
- **Sekundær e-postadresse (del 1, det enkle tilfellet) — ✅ BYGGET OG LIVE
|
||
2026-07-21**, samme dag, rett etter deltaker-tilgang-runden. Ny
|
||
migrasjon `017_secondary_email.sql` (`secondary_email_token` +
|
||
`user_secondary_email`, samme token-hash-og-utløp-mønster som ADR-032s
|
||
`email_change_token`). Nye endepunkter `POST /auth/secondary-email`,
|
||
`POST /auth/secondary-email/confirm`, `DELETE /auth/secondary-email/{id}`.
|
||
**Kjernestykket:** `verify_magic_link`/`login_with_password` slår nå opp
|
||
`user_secondary_email` FØR sitt vanlige `app_user.email`-oppslag — en
|
||
innlogging på en verifisert sekundæradresse løses til EIERENS
|
||
eksisterende konto i stedet for å opprette en ny, separat en (nøyaktig
|
||
det hullet som gjorde funksjonen nødvendig i utgangspunktet). Lagt i
|
||
`/account` (ikke dashbordet som opprinnelig bedt om — bevisst avvik,
|
||
flagget eksplisitt: dette er kun del 1, dashbord-plassering er trolig
|
||
riktigere når/hvis del 2 (kontosammenslåing) bygges).
|
||
**Scratch-verifisert, 20 sjekker:** adresse ikke lagt til før bekreftet,
|
||
token ikke gjenbrukbart, dupliserte adresser (som andres primær- ELLER
|
||
sekundæradresse) avvist tydelig, innlogging via sekundæradresse (magic-
|
||
link OG passord) bekreftet å resolve til SAMME eksisterende konto,
|
||
fremmed kan ikke slette andres adresse, fjernet adresse oppretter en
|
||
genuint NY konto ved neste innlogging (beviser fjerning er reell).
|
||
`test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild kjørt og
|
||
bekreftet.
|
||
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon
|
||
017 kjørt mot ekte `teecup_db` (begge tabeller bekreftet, `test_
|
||
isolation.sql` fortsatt 12/12), deretter `docker compose up -d --build
|
||
teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||
`/health`/`/dashboard`/`/account`/`/verify-email` → 200, `teeoff.no`
|
||
upåvirket. Del 2 (ekte kontosammenslåing) fortsatt IKKE designet, egen
|
||
fremtidig runde, se FEATURE_BACKLOG.md.
|
||
|
||
- **HCP-historikk over tid — ✅ BYGGET OG LIVE 2026-07-21**, samme dag,
|
||
siste av de tre bekreftede punktene fra dashboard/konto-runden. Ny
|
||
migrasjon `018_handicap_history.sql` (append-only `handicap_history` —
|
||
kun for personlig profil sin `app_user.handicap_index`, IKKE de org-
|
||
scopede `player`/`team_roster`-radene, som har sitt eget uendrede
|
||
reproduserbarhets-prinsipp fra ADR-007). `PATCH /auth/profile` logger nå
|
||
en ny rad KUN ved en FAKTISK endring til en tallverdi — leser gjeldende
|
||
verdi FØR overskriving for å unngå duplikater ved gjentatt lagring av
|
||
samme verdi, og logger bevisst IKKE ved nullstilling. Ny
|
||
`GET /auth/profile/handicap-history`. Frontend: «Vis HCP-historikk»-
|
||
lenke i `/account` sin profilseksjon.
|
||
**Scratch-verifisert, 18 sjekker:** ingen duplikat ved uendret
|
||
gjenlagring, korrekt logging ved reell endring, ingen logg ved
|
||
nullstilling, ny logg ved gjeninnsetting etter nullstilling,
|
||
kronologisk rekkefølge riktig, full isolasjon mellom to brukeres
|
||
historikk. `test_isolation.sql` 12/12. Ekte typesjekket
|
||
produksjonsbuild kjørt og bekreftet.
|
||
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon
|
||
018 kjørt mot ekte `teecup_db` (tabell bekreftet, `test_isolation.sql`
|
||
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent,
|
||
`/health`/`/dashboard`/`/account` → 200, `teeoff.no` upåvirket.
|
||
**Dermed er alle tre bekreftede punktene fra dashboard/konto-runden
|
||
(2026-07-21) ferdig bygget** (deltaker-tilgang, sekundær e-post del 1,
|
||
HCP-historikk).
|
||
|
||
- **Obligatorisk profil-fullføring ved innlogging LIVE (2026-07-22):**
|
||
svar på det pauserte "dashbordets tom-tilstand"-spørsmålet over —
|
||
brukeren avklarte at det ALLER første en innlogget bruker med en
|
||
ufullstendig profil skal se, er en fokusert «Fullfør profilen din»-
|
||
visning, ikke dashbordet. Ny migrasjon `019_profile_country_bio.sql`
|
||
(`app_user.country`, `app_user.bio` — samme nullable-kolonne-mønster
|
||
som resten av profilen, "obligatorisk" håndheves i app-laget).
|
||
`/auth/me` fikk et nytt beregnet felt `profile_complete` (sant når
|
||
fornavn/etternavn/fødselsdato/kjønn/HCP/hjemmeklubb/land ALLE er
|
||
utfylt — bilde og beskrivelse er bevisst unntatt, valgfrie).
|
||
**HCP-grensetilfelle avklart med bruker FØR bygging** (nybegynnere har
|
||
sjelden en offisiell HCP ennå): WHS-maksimum 54 brukes som
|
||
forhåndsutfylt standardverdi i skjemaet (ikke en DB-default), og
|
||
`ProfileUpdate.handicap_index` fikk en hard `le=54`-grense (kan aldri
|
||
registreres høyere) — løser grensetilfellet uten en egen "har ikke
|
||
HCP ennå"-avkrysning.
|
||
`AccountSettings` (`/account`) grener nå: er profilen ufullstendig,
|
||
vises KUN et nytt, fokusert `ProfileOnboarding`-skjema (de obligatoriske
|
||
feltene + valgfri beskrivelse, «Logg ut» tilgjengelig, INGEN tilgang
|
||
til resten av kontosidene) — er den komplett, vises den vanlige
|
||
innstillingssiden som før (nå med land+beskrivelse lagt til i det
|
||
vanlige profilskjemaet, for redigering i etterkant). `app/page.tsx`
|
||
(rot-siden) og `Dashboard`-komponenten sender en innlogget bruker til
|
||
`/account` i stedet for `/dashboard` når profilen er ufullstendig —
|
||
dekker alle innloggingsveier (magic-link/passord/2FA lander alle på
|
||
`/dashboard`, som selv gjør sjekken ved mount).
|
||
**Bevisst avgrenset:** gaten håndheves kun ved disse to naturlige
|
||
inngangspunktene, ikke ved dypere direktelenker til andre autentiserte
|
||
sider — samme skope-disiplin som tidligere runder.
|
||
**Scratch-verifisert, 16 backend-sjekker** (isolert scratch-rolle+
|
||
MinIO+engangs API-container): fersk konto starter `profile_complete:
|
||
false`, delvis utfylling forblir ufullstendig, HCP>54 avvist (422),
|
||
full utfylling gir `true`, beskrivelse er reelt valgfri, å nullstille
|
||
et obligatorisk felt i etterkant slår `profile_complete` tilbake til
|
||
`false`, full isolasjon mellom to kontoer. `test_isolation.sql` 12/12
|
||
uendret. Ekte typesjekket produksjonsbuild + et ekte HTTP-nivå-bevis
|
||
mot en kjørende produksjonscontainer (anonym mot `/` → 200 innloggings-
|
||
skjema, en ekte innlogget-men-ufullstendig sesjonscookie mot `/` →
|
||
`307 → /account`).
|
||
**Rullet ut live 2026-07-22**, bruker bekreftet eksplisitt: migrasjon
|
||
019 kjørt mot ekte `teecup_db` (kolonner bekreftet, `test_isolation.sql`
|
||
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`/
|
||
`/account`/`/` (anonym) → 200, `teeoff.no` upåvirket. **Merk:** BEGGE
|
||
brukerens egne kontoer (`erol.haagenrud@envide.no` — eier av «Tjøme
|
||
Gents» — og `hei@erol.no`) mangler i dag alle disse feltene og vil
|
||
derfor begge se profil-fullførings-skjemaet ved neste innlogging —
|
||
bekreftet tilsiktet, ikke en bug.
|
||
- **Sju punkter fra faktisk bruk av scorekort-skjermen, BYGGET OG LIVE
|
||
2026-07-24:** starthull-bug fikset (`currentHole` respekterte aldri
|
||
`round.start_hole` — `1` er truthy i JS, så `prev || start_hole` var en
|
||
no-op — forklarer trolig også det samtidig rapporterte GIR-avviket,
|
||
siden formelen selv var korrekt), kølle-bag på profilen (28 faste
|
||
typer, maks 14), nytt statistikkfelt «Anywayslag», valgfritt
|
||
statistikknivå per deltaker (default kun slag — ny kolonne
|
||
`round_participant.stat_level`), putt-avstand endret fra fritekst til
|
||
seks faste bøtter, «Hullet er spilt»-avkrysningen fjernet (overflødig
|
||
— spilt settes allerede automatisk ved slagtall). Ny migrasjon
|
||
`022_round_stats_and_bag.sql`. Numpad-layout/retningskors-ikoner for
|
||
tallvelgerne (brukerens punkt 6) er BEVISST holdt utenfor — egen
|
||
V0-prompt utarbeidet i stedet, ikke bygget selv. Full detalj i
|
||
ADR-033. 18 scratch-sjekker, `test_isolation.sql` 12/12, rullet ut mot
|
||
ekte `teecup_db`/`teecup_api`/`teecup_frontend`, `teeoff.no` upåvirket.
|
||
- **V0-prompten for numpad/retningskors/sveip-vurdering (punkt 6) BYGGET
|
||
OG LIVE, samme dag:** bruker kjørte prompten, sendte zip 13. V0 valgte
|
||
trykk-baserte Score/Statistikk-faner fremfor sveip (godt begrunnet —
|
||
unngår en tredje sveiperetning på en skjerm som allerede har to).
|
||
Flettet inn i EKSISTERENDE, allerede fungerende datalag (statLevel-
|
||
gating, kølle-bag, anywayslag, putt-bøtter, merge-før-PATCH,
|
||
starthull-fiks, `/my-rounds`-lenker) — ikke en ren erstatning, siden
|
||
V0 ikke kjente til den runden. Full detalj i ADR-033. Typesjekket
|
||
build kompilerte rent, rullet ut (kun `teecup_frontend`), `teeoff.no`
|
||
upåvirket.
|
||
- **Enda en runde brukerpunkter, ALLE BYGGET OG LIVE, samme dag:**
|
||
utslagstidspunkt + automatisk tidsbruk-visning (gjenbruker eksisterende
|
||
"Fullfør runde" som "Ferdig", ny `round.started_at`-kolonne, migrasjon
|
||
023), "Idx" → "Hcp", "par"-merking på slag-tastaturet, ni-hulls-
|
||
navigasjonsbug fikset (respekterte aldri `holes_planned`, hoppet feil
|
||
ved "Forrige"), tak på putter/chip/bunker/straffeslag/anywayslag (kan
|
||
ikke overstige antall slag), "Slett runde"-knapp (backend fantes,
|
||
manglet UI), og en ny `PATCH /rounds/{id}` for å rette bane/utslag/
|
||
antall hull MIDT i runden uten å røre allerede registrerte slag —
|
||
sperret etter fullføring. 22+8 scratch-sjekker, `test_isolation.sql`
|
||
12/12, ren build. Full detalj i ADR-033.
|
||
- **To til punkter, BYGGET OG LIVE samme dag:** starthull kan nå endres
|
||
uansett (ren metadata), utslagstid justeres når som helst, og
|
||
fullført-tidspunkt kan korrigeres i etterkant — men KUN på en allerede
|
||
fullført runde (løser "glemte å trykke Fullfør runde i flere timer").
|
||
Pluss en ny "Nærmest deg"-liste i bane-søket ved ny runde (Haversine-
|
||
avstand mot alle 174 teeoff-anlegg, geolokasjon i nettleseren, feiler
|
||
stille hvis avslått). 14 nye scratch-sjekker inkl. et ekte nearby-kall
|
||
mot teeoff. Full detalj i ADR-033.
|
||
- **Reell UX-bug fikset + "Så langt i runden"-oversikt bygget, samme
|
||
dag (2026-07-25), klar for utrulling:** brukeren rapporterte at "All
|
||
statistikk" ikke viste noe utover slag/putter -- bekreftet (kun
|
||
lesing) mot ekte `teecup_db` at `stat_level='full'` FAKTISK var
|
||
lagret riktig, så feilen var presentasjonen: alle detaljfeltene lå
|
||
bak en "Score"/"Statistikk"-fane fra forrige V0-runde som brukeren
|
||
aldri oppdaget. Fikset ved å fjerne faneløsningen helt -- alt vises nå
|
||
alltid samlet. Samtidig bygget en ny "Så langt"-oversikt
|
||
(`ScoreSoFar`-komponent: kompakt linje + utvidbar full-oversikt-
|
||
tabell med netto per hull), med et nytt backend-felt
|
||
`strokes_received` (`allocate_strokes_by_index()`, ingen ny
|
||
algoritme). 11 nye scratch-sjekker (inkl. et presist tall-eksempel på
|
||
slagfordelingen), ren build. Full detalj i ADR-033.
|
||
**Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt: kun
|
||
`teecup_api`+`teecup_frontend` redeployet, ingen migrasjon,
|
||
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
- **Reell produksjonsregresjon rapportert av bruker rett etter utrullingen
|
||
over, funnet og fikset umiddelbart samme dag (2026-07-25):**
|
||
"Ingenting er klikkbart i den avanserte statistikken." Root cause var
|
||
IKKE frontend (all onClick-kabling var korrekt) -- funnet ved å faktisk
|
||
gjenskape brukerens klikk-sekvens mot en fersk scratch-container:
|
||
`update_hole` (hull-PATCH) manglet fortsatt det nye påkrevde
|
||
`strokes_received`-feltet i responsen sin (lagt til i GET-endepunktet i
|
||
runden rett over, glemt i PATCH) -- ga en 500 (Pydantic-valideringsfeil)
|
||
på HVER hull-lagring, ikke bare de avanserte feltene. Frontend svelger
|
||
feilresponsen stille, så symptomet så ut som "ingenting skjer" for
|
||
ALT, ikke bare avansert statistikk (brukeren merket det trolig først
|
||
der siden Slag/Putter fra tidligere runder allerede hadde lagrede
|
||
verdier som så riktige ut). Fikset: `update_hole` beregner nå
|
||
`strokes_received` for hullet som oppdateres, samme algoritme som
|
||
`list_holes`. 14 scratch-sjekker som gjenskaper eksakt klikk-
|
||
rekkefølgen, alle bestått. **Rullet ut live 2026-07-25**, bruker
|
||
bekreftet eksplisitt: kun `teecup_api` redeployet, `/health`/
|
||
`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
- **Rediger/Fullfør/Slett tonet ned + flyttet til toppen, auto-scroll ved
|
||
hull-bytte, LIVE samme dag (2026-07-25):** brukeren rapporterte at
|
||
"Fullfør runde"/"Slett runde" var for lette å trykke på ved et uhell
|
||
(lå rett under "Neste hull"). Flyttet alle tre handlingene til en
|
||
nedtonet rad øverst -- må nå aktivt scrolles til. Samtidig: "Neste
|
||
hull"/"Forrige" scroller nå automatisk opp til toppen av hull-panelet
|
||
(også ved direkte hull-valg), så det nye hullets Slag-felt alltid er
|
||
synlig med en gang. Ren frontend-endring, ingen backend/migrasjon.
|
||
Rullet ut, `teeoff.no` upåvirket.
|
||
- **"Så langt i runden" utvidet med netto/stableford-sum, putt-/kølle-
|
||
statistikk-totaler og grafisk fairway-/innspill-fordeling, LIVE samme
|
||
dag (2026-07-25):** etterspurt av bruker. Ny `StatPill`-rutenett
|
||
(Slag/Til par/Netto/Stableford/Putt/Chip/Bunker/Straffeslag/
|
||
Anywayslag), Stableford-kolonne i hull-tabellen, og to nye
|
||
`DistributionBar`-seksjoner (Fairwaytreff/Innspill, N-kategori-variant
|
||
av `SegmentedBar`-mønsteret fra tournament-leaderboard.tsx) +
|
||
gjennomsnittlig brutto score-til-par (to desimaler, fortegn) splittet
|
||
på treff-vs-bom for både fairway og innspill. Ingen backend-endring --
|
||
alt beregnes klientside fra data `GET .../holes` allerede returnerer.
|
||
Stableford beregnes alltid ut fra netto score når mulig (appen har
|
||
ingen egen "spilleform"-innstilling). Logikk verifisert manuelt mot et
|
||
regnet eksempel før utrulling. Ren frontend-endring, ingen migrasjon,
|
||
rullet ut, `teeoff.no` upåvirket.
|
||
- **"Avstand første putt" flyttet rett under "Putter", LIVE samme dag
|
||
(2026-07-25):** lå tidligere lenger ned i "full"-statistikk-blokken
|
||
(etter kølle/retning/chip-gruppen) -- flyttet til å bli første felt i
|
||
den blokken, rett under Putter-NumberPickeren (som ligger utenfor
|
||
"full"-gaten). Ren omrokkering, ingen ny logikk. Ekte typesjekket
|
||
build, rullet ut, `teeoff.no` upåvirket.
|
||
- **V0-prompt for en rikere rundestatistikk-skjerm skrevet, IKKE bygget
|
||
ennå (2026-07-25):** brukeren lastet opp en skjermopptaksvideo
|
||
(`screen-20260724-133013-1784892586987.mp4`, 26 sek, av en KONKURRENT-
|
||
apps statistikkskjerm) og ba om et V0-prompt inspirert av innholdet,
|
||
eksplisitt IKKE et plagiat. Video analysert bilde for bilde (ffmpeg i
|
||
en engangs Docker-container, ikke installert på verten). Innholdet
|
||
identifisert dekkes i stor grad av data vi ALLEREDE sporer
|
||
(fairwaytreff/innspill-retning/putts/chip/bunker/straffeslag/putt-
|
||
lengde-bøtte) -- ingen backend-endring skulle trengtes for de fleste
|
||
målene. Prompt skrevet til bruker i chatten (ikke lagret som egen
|
||
fil) -- beskriver mål/informasjonsarkitektur/tilgjengelighetskrav
|
||
abstrakt (donut med sentertall, gauge-stolper for avvik fra par,
|
||
retnings-diagram for bom-retning) UTEN å kopiere konkurrentens
|
||
eksakte fargevalg/layout/ordlyd, og ber eksplisitt om TeeCups egen
|
||
merkevareidentitet (grønn/oransje) + en egen visuell vri.
|
||
**Én bevisst forskjell fra videoen, valgt for å unngå plagiat OG fordi
|
||
det matcher vårt eget datamodell:** videoens puttlengde-bøtter
|
||
(<1m/1-2/2-4/4-8/+) er ANDRE enn TeeCups allerede lagrede seks bøtter
|
||
(<1m/<2m/<3m/<5m/<8m/8m+, ADR-033) -- promptet ber om TeeCups egne
|
||
bøtter, ikke videoens. "Lengste drive" (krever GPS/avstandsmåling vi
|
||
ikke har) bevisst utelatt fra promptet.
|
||
**Bygget og rullet ut live 2026-07-25, samme dag:** zip 14 mottatt.
|
||
Diffet mot live-treet FØR noe ble tatt inn (samme rutine som alltid) --
|
||
kun to reelt nye filer (`components/round-stats.tsx`,
|
||
`app/rounds/[id]/stats/page.tsx`), resten var V0s vanlige uvitende
|
||
reverts (bl.a. sin egen `/rounds/[id]`-ruteversjon fra FØR
|
||
`/my-rounds`-omdøpingen ADR-033 gjorde 2026-07-23 -- korrekt hoppet
|
||
over). Ruten lagt inn som `app/my-rounds/[id]/stats/page.tsx` i stedet
|
||
(samme kollisjon-unngåelse). `globals.css` sin nye `--chart-1..6`
|
||
data-viz-fargeskala slått sammen inn (V0 sine egne, mer omtrentlige
|
||
`--primary`/`--ring`/`--brand-orange`-verdier IKKE tatt inn -- beholdt
|
||
de presise OKLCH-verdiene fra ADR-016).
|
||
**Datalag skrevet fullstendig om fra mock:** henter `GET /rounds/{id}`
|
||
+ `GET .../participants/{id}/holes` (samme endepunkter round-detail.tsx
|
||
allerede bruker) -- ingen ny backend. Ny `computeStats()`-funksjon
|
||
regner ut ALT fra rå hull-data ved lesing (score-kategorier,
|
||
snitt-til-par totalt/per hulltype, fairway-fordeling + score-splitt,
|
||
GIR totalt/per hulltype/kryss-fairway + score-splitt, bom-retning på
|
||
green, putt-fordeling 1/2/3-putt + snitt per hulltype + med/uten GIR,
|
||
én-putt% per TeeCups egne seks puttlengde-bøtter + lengdefordeling
|
||
hit/miss, chip-fordeling, scrambling%, sand save%, bunker/straffeslag
|
||
per runde + score-splitt). Hver seksjon skjules helt når det ikke
|
||
finnes nok data (samme "vis kun det som faktisk finnes"-prinsipp som
|
||
"Så langt i runden"). Ny enkel spillervelger (pill-rad) lagt til når
|
||
runden har flere deltakere -- default til eieren.
|
||
**"Se full rundestatistikk"-lenke** lagt inn i `CompletedBanner` i
|
||
round-detail.tsx (V0s egen versjon hadde denne, men i sin ellers
|
||
fullstendig reverterte fil -- portert manuelt inn i vår LIVE versjon
|
||
i stedet for å ta hele filen).
|
||
**Matematikken verifisert FØR utrulling:** `computeStats()`-logikken
|
||
portert til et frittstående Node-script og kjørt mot et
|
||
hånd-etterregnet 6-hulls syntetisk datasett (blandet par 3/4/5,
|
||
fairwaytreff/-bom, GIR-treff/-bom, bunkerslag) -- alle 15+ utledede
|
||
tall stemte eksakt med manuell utregning. Ekte typesjekket
|
||
produksjonsbuild kjørt og bekreftet (`/my-rounds/[id]/stats` listet
|
||
som ny rute).
|
||
**Rullet ut live 2026-07-25**, ren frontend-endring, ingen migrasjon,
|
||
`docker compose up -d --build teecup_frontend`, `/health`/`/my-rounds`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Rundeliste + scorekort-redesign, LIVE samme dag (2026-07-25):**
|
||
brukeren delte en ny skjermopptaksvideo av "Egne runder"-listen (som
|
||
manglet ALL score-informasjon) og av scorekort-registreringen (rapportert
|
||
som "veldig dårlig designet" -- for mange ulike knapp-typer stablet
|
||
oppå hverandre) + "Runde fullført"-siden (rapportert som "veldig mye
|
||
dobbel informasjon"). Reflektert over problemstillingen (kort, per
|
||
brukerens ønske) før to V0-prompter ble skrevet.
|
||
**Fikset direkte, uten V0:** "Runde fullført"-duplikatet -- `ScoreSoFar`
|
||
("Så langt i runden") skjules nå helt når runden er fullført, siden
|
||
den nye rundestatistikk-siden dekker akkurat det samme, langt
|
||
grundigere. Ny backend-beregning i `_load_round_out`
|
||
(`app/routers/rounds.py`): `owner_holes_played`/`owner_total_score`/
|
||
`owner_score_to_par`, en enkel aggregatspørring mot `round_hole` for
|
||
eierens egen deltaker-rad -- verifisert med 10 scratch-sjekker
|
||
(inkl. at en gjests score IKKE påvirker eierens aggregat).
|
||
**Zip 15 og 16 mottatt** (samme v0.app-prosjekt, kontinuerlig --
|
||
`round-card.tsx`/`own-rounds.tsx` identiske i begge, zip 16s
|
||
`round-detail.tsx` var den nyeste med selve scorekort-redesignet; zip
|
||
15 brukt kun for å bekrefte at zip 16 var det riktige, endelige
|
||
eksportet).
|
||
`round-card.tsx` fikk en ny `ScoreTile` -- prominent resultat+til-par
|
||
for fullførte runder, hull-fremdrift-bar ("6/18 hull spilt") for
|
||
runder som pågår, pluss en HCP-differensial-chip når runden telte.
|
||
Datalag i `own-rounds.tsx` skrevet om fra mock til ekte fetch, kobler
|
||
de nye `owner_*`-feltene fra backend + eierens `score_differential`
|
||
(kun vist når `counts_for_handicap`).
|
||
`round-detail.tsx` fikk en ny kollapsbar "Flere detaljer"-seksjon
|
||
(lukket som default) som nå rommer kølle/utslag-retning/innspill-
|
||
retning/chip-bunker-straffeslag/anywayslag -- Slag/Putter/Avstand
|
||
første putt forblir alltid synlig over. Datalag/statLevel-gating/
|
||
bucket-basert puttlengde/maxValue-capping (alt bygget tidligere denne
|
||
økten) bevisst IKKE revertert til V0s eldre mock-baseline, kun selve
|
||
kollaps-mekanismen og plasseringen ble hentet derfra. Lagt til en kort
|
||
kode-kommentar (ikke noe bygget UI ennå) som reserverer plass ved
|
||
siden av hull-headeren til en fremtidig avstandsmåling-indikator,
|
||
bekreftet av bruker at dette kommer senere.
|
||
Ekte typesjekket produksjonsbuild kjørt og bekreftet (alle 19 ruter).
|
||
**Rullet ut live 2026-07-25**, ren frontend-endring (+ den lille
|
||
backend-tilføyelsen over), ingen migrasjon, `/health`/`/my-rounds` →
|
||
200, `teeoff.no` upåvirket.
|
||
- **Reelt hull funnet og fikset SAMME dag, rapportert av bruker rett
|
||
etter forrige punkt ("Hvor er scorekortet?"):** da "Så langt i runden"
|
||
ble skjult for fullførte runder (se over), forsvant OGSÅ den eneste
|
||
plassen den rå hull-for-hull-tabellen (Hull/Par/Score/Netto/Sum) fantes
|
||
-- `round-stats.tsx` (den nye dedikerte statistikk-siden) hadde KUN
|
||
utledet/aggregert statistikk (donuter, stolper), ingen tabell med de
|
||
faktiske tallene per hull. Fikset ved å legge til en ny "Scorekort"-
|
||
seksjon FØRST på statistikk-siden (samme tabell-mønster som "Så
|
||
langt" hadde -- Hull/Par/Score/Netto/Stableford/Sum), åpen som
|
||
default. La til `start_hole` i `round-stats.tsx` sin `ApiRound`-type
|
||
og `strokes_received` i `ApiHole`-typen (sistnevnte kom allerede fra
|
||
backend, bare ikke lest av denne siden ennå) for å kunne vise hullene
|
||
i rundens FAKTISKE rekkefølge (samme sirkulære start_hole-logikk som
|
||
round-detail.tsx) i stedet for bare rå hullnummer 1-18. Ekte
|
||
typesjekket build kjørt og bekreftet. Rullet ut, ren frontend-endring,
|
||
ingen backend/migrasjon, `teeoff.no` upåvirket.
|
||
- **Ny scorekort-presentasjonsregel + V0-prompt skrevet, IKKE bygget
|
||
ennå (2026-07-25):** brukeren delte et referansebilde av et
|
||
tradisjonelt horisontalt golf-scorekort og formulerte en generell
|
||
regel: hull listet HORISONTALT (som kolonner) → sum TIL HØYRE; hull
|
||
listet VERTIKALT (som rader) → sum UNDER. Dagens "Scorekort"-tabell
|
||
(bygget rett over samme dag) er vertikal med sum som løpende KOLONNE
|
||
-- ikke i tråd med regelen. V0-prompt skrevet (horisontalt scorekort,
|
||
hull 1-9/10-18 som kolonner, Ut/Inn-sum til høyre for hver halvdel,
|
||
Hcp/Par/Score/Netto/Stableford-rader, håndterer både 9- og 18-hulls
|
||
runder med vilkårlig start_hole), bevisst IKKE et plagiat av
|
||
referansebildet (egne farger, egen "Hcp"-term i stedet for bildets
|
||
"Slope", ingen kopiert spiller-header). Full detalj i
|
||
ARCHITECTURE_DECISIONS.md. **Presisert samme dag, før noe ble sendt:**
|
||
brukeren spurte om et mykt "scroll hvis nødvendig"-unntak fanget opp
|
||
målet om ALDRI å måtte scrolle -- svart nei (reell breddekonflikt med
|
||
"lesbar uten briller"-kravet, ikke bare ordlyd) og skrevet om til et
|
||
hardt "ingen scroll"-krav som eksplisitt forteller V0 HVORDAN det
|
||
oppnås (kompakte fete høykontrast-siffer i selve rutenettet, smale
|
||
forkortede rad-labels i en trang gutter -- "lesbar uten briller"
|
||
avgrenset til labels/knapper, ikke enkeltsifre). Full detalj i
|
||
ARCHITECTURE_DECISIONS.md.
|
||
**Zip 17 mottatt og BYGGET/LIVE samme dag:** en ekte HTML `<table>` med
|
||
`<colgroup>` faste kolonnebredder -- ingen scroll-container i det hele
|
||
tatt, løst med kompakte celler i stedet. Score-cellene bruker FORM
|
||
(sirkel=under par, firkant=over par) + fylt/ufylt (2+ slag av) for å
|
||
aldri stole på farge alene, pluss en egen symbolforklaring. Egen,
|
||
NY dedikert side `/my-rounds/[id]/scorecard`
|
||
(`components/round-scorecard.tsx`) -- ikke slått sammen med
|
||
`round-stats.tsx`, siden V0 designet den med egen side-chrome (header,
|
||
rundesammendrag). Datalag skrevet om fra mock til ekte fetch; V0s egen
|
||
`strokesReceived()`-formel (generisk modulo) BEVISST forkastet til
|
||
fordel for backend sin allerede beregnede `strokes_received` (samme
|
||
`allocate_strokes_by_index()` som resten av appen -- unngår to ulike
|
||
HCP-slagfordelings-implementasjoner). Samme sirkulære
|
||
start_hole-rekkefølge som resten av rundeskjermene. Den gamle
|
||
vertikale "Scorekort"-tabellen i `round-stats.tsx` FJERNET (erstattet
|
||
med en lenke til den nye siden) -- `round-stats.tsx` er nå rendyrket
|
||
aggregert statistikk, det rå scorekortet bor kun ett sted. V0 la selv
|
||
til en "Se scorekort"-knapp i `CompletedBanner` (round-detail.tsx) ved
|
||
siden av den eksisterende "Se full rundestatistikk" -- tatt inn.
|
||
**Samtidig, rapportert av bruker:** Anywayslag manglet helt fra
|
||
rundestatistikk-siden sin "Chip, bunker og straffeslag"-seksjon.
|
||
**Feilrettet rett etterpå, samme dag:** min første fiks slo feilaktig
|
||
sammen Anywayslag-tallet i DEN eksisterende seksjonen og omdøpte hele
|
||
seksjonen til "Annet" -- brukeren påpekte at "Chip, bunker og
|
||
straffeslag" skulle beholde navn+innhold uendret, og at "Annet" skulle
|
||
være en EGEN, ny seksjon RETT ETTER med kun anywayslag-tall (total per
|
||
runde + andel hull med anywayslag). Rettet umiddelbart. Notert at et
|
||
fritekst-notatfelt trolig havner i "Annet" senere. Ekte typesjekket
|
||
build kjørt og bekreftet (ny rute `/my-rounds/[id]/scorecard` listet).
|
||
Rullet ut (to runder), ren frontend-endring, ingen backend/migrasjon,
|
||
`teeoff.no` upåvirket.
|
||
- **Rundestatistikk-seksjonene starter nå kollapset, LIVE samme dag
|
||
(2026-07-25):** brukeren ba om at "trekkspillet" (StatCard-seksjonene
|
||
på `/my-rounds/[id]/stats`) skal vises sammenslått -- må klikkes for å
|
||
se innholdet. `StatCard` sin `defaultOpen` endret fra `true` til
|
||
`false` (ingen kallsted overstyrte den, så én linje dekket alle
|
||
seksjonene). Ekte typesjekket build, rullet ut, `teeoff.no` upåvirket.
|
||
- **Notert i FEATURE_BACKLOG.md, IKKE bygget:** brukeren ba om et notat
|
||
om et femte fremtidig turneringsformat, "Flaggturnering" (utover de
|
||
fire fra 2026-07-19-runden) -- krever en visning av GJENSTÅENDE slag
|
||
for spilleren, oppdatert etter hvert hull, pluss en fremtidig idé om
|
||
å bruke GPS (når/hvis integrert) til å markere hvor langt spilleren
|
||
faktisk kom. Lagt til i samme seksjon som de fire andre formatene.
|
||
|
||
- **Tre punkter fra brukeren, ALLE BYGGET, SCRATCH-VERIFISERT OG LIVE
|
||
2026-07-25:** (1) enkeltbane-anlegg (f.eks. Tjøme Golfklubb) dropper nå
|
||
det overflødige banenavnet ved import/runde-opprettelse — kun "Tjøme
|
||
Golfklubb", ikke "Tjøme Golfklubb – Hovedbanen" (flerbane-anlegg som
|
||
Ålesund beholder uendret "Anlegg – Bane"-navn). (2) Runder kan nå
|
||
navngis (`round.name`, migrasjon `024_round_name.sql`, valgfritt,
|
||
redigerbart i etterkant) — vises på tvers av rundeliste/rundeside/
|
||
scorekort/statistikk, faller tilbake til banenavn når ikke satt. (3)
|
||
Profilens "Land"-felt er nå en nedtrekksliste (kun "Norge" foreløpig,
|
||
klargjort for flere), plassert FØR "Hjemmeklubb", som selv ble
|
||
omgjort til en søkbar liste mot teeoffs klubbregister (gjenbruker
|
||
eksisterende `/rounds/official-search`, ingen ny backend-kode). Full
|
||
detalj i ARCHITECTURE_DECISIONS.md. Bruker bekreftet eksplisitt:
|
||
migrasjon 024 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt
|
||
12/12, begge containere redeployet, `/health`/`/dashboard`/`/my-rounds`/
|
||
`/account` → 200, `teeoff.no` upåvirket.
|
||
|
||
- **Refleksjonsrunde 2026-07-25: hvorfor organisasjon? + venner/deling —
|
||
DESIGNET, IKKE bygget.** Brukeren spurte hvorfor organisasjon i det
|
||
hele tatt trengs, gitt at ADR-033 nå gjør en enkelt bruker fullt
|
||
selvstendig. Veide A (bruker-eide turneringer, som runder) mot B
|
||
(organisasjon beholdt, men opprettelsen gjøres usynlig/automatisk) —
|
||
A avvist (ville krevd duplisering av HELE turnering-apparatet som er
|
||
bygget rundt RLS/`organization_id`, og er ikke reversibelt i noen
|
||
retning), B valgt (rører verken skjema eller de ni eksisterende
|
||
routerne, kun frontend-orkestrering — `POST /orgs` krever allerede kun
|
||
`name`). Skrevet som **ADR-035** (organisasjon-B-beslutningen + et
|
||
konkret 7-blokks dashbord-forslag: Hurtighandlinger/Kommende runder/
|
||
Kommende turneringer/Statistikk/Spilte baner/Venner/Organisasjoner —
|
||
sistnevnte nedtonet, kun synlig ved reelt flere org-er) og **ADR-036**
|
||
(nytt vennekonsept — gjensidig forespørsel/aksept, privat
|
||
kategorisering i faste grupper som Make/Nær familie/Golfvenner/osv.,
|
||
ny rundevisibilitet `public`/`private`/`friends` med eksplisitt
|
||
gruppevalg, og et tiered personsøk — venner→samme klubb→samme
|
||
land→globalt, navnerekkefølge-uavhengig — delt mellom "finn venn" og
|
||
en fremtidig "legg til ekte medspiller"-utvidelse av
|
||
`round_participant.user_id`, som allerede lå ubrukt i skjemaet som en
|
||
eksplisitt notert "v1-avgrensning" i ADR-033). V0-prompt for det nye
|
||
dashbordet skrevet (i FEATURE_BACKLOG.md), IKKE sendt til V0 ennå.
|
||
**Ingen kode skrevet** — bevisst en design-/dokumentasjonsrunde, ikke
|
||
en byggerunde, på brukerens eksplisitte instruks. Full detalj i
|
||
ARCHITECTURE_DECISIONS.md (ADR-035/036) og FEATURE_BACKLOG.md (åpne
|
||
spørsmål, foreslått 3-fase byggerekkefølge for venner-delen).
|
||
- **Oppfølging samme dag:** bruker bekreftet at en lagt-til ekte
|
||
medspiller SKAL se runden i sin egen "Egne runder"-liste (ADR-036 fase
|
||
3, tidligere bevisst uavklart). Presiserte samtidig en ny teknisk
|
||
konsekvens i ARCHITECTURE_DECISIONS.md: `RoundOut` sine
|
||
`owner_*`-statistikkfelt må bli viewer-relative, ikke alltid eierens,
|
||
og et NYTT åpent spørsmål dukket opp — skal medspilleren også kunne
|
||
SKRIVE egne hull-tall, ikke bare lese? Ikke avgjort. Fortsatt ingen
|
||
kode skrevet.
|
||
|
||
- **Dashbord-redesign (ADR-035) BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):**
|
||
zip 18 mottatt og integrert — kun `dashboard.tsx` var reelt nytt (samme
|
||
full-reeksport-mønster som alltid), pluss en genuin MERGE (ikke revert)
|
||
i `tournament-card.tsx`: V0s nye `organizer`-merkelapp lagt til SIDE OM
|
||
SIDE med den eksisterende `orgId`-baserte lenkelogikken (offentlig vs.
|
||
innlogget kontekst) som V0 ikke kjente til. Datalag skrevet fra bunnen:
|
||
"Kommende turneringer" slår sammen deltaker- (`me.my_tournaments`) og
|
||
arrangør-turneringer (hentet per org, alle organisasjoner brukeren er
|
||
medlem i) til ÉN tidssortert liste, filtrert til `draft`/`active`-status.
|
||
"Statistikk"/"Spilte baner" er REN klientside-utledning fra eksisterende
|
||
`GET /rounds` + `GET /auth/profile/handicap-history` — ingen nye
|
||
endepunkter trengt. "Ny turnering"-hurtighandlingen implementerer selve
|
||
ADR-035-mekanismen: oppretter/gjenbruker organisasjon USYNLIG først
|
||
(kun ved faktisk innsending, ikke når skjemaet åpnes — unngår en
|
||
foreldreløs org ved avbrutt skjema), deretter turneringen, deretter
|
||
navigerer rett inn i den. "Bli med med kode" gjenbruker samme
|
||
`by-code`-oppslag som `login-form.tsx` sin `JoinByCode`, nå som en
|
||
dashbord-lokal variant. "Venner"-seksjonen vises med en ærlig
|
||
tom-tilstand (ADR-036 er ikke bygget ennå) — CTA-en er bevisst uten
|
||
href/onClick, ikke en lenke til noe som ikke finnes. "Spilte baner"
|
||
fikk sine listeelementer gjort om til ren visning (ikke lenker) siden
|
||
API-et ikke eksponerer noen stabil bane-id å lenke til per runde.
|
||
Ekte typesjekket produksjonsbuild kompilerte rent, alle 21 ruter
|
||
listet. **Rullet ut live 2026-07-25**, bruker bekreftet eksplisitt:
|
||
`docker compose up -d --build teecup_frontend` (gjenskapte også
|
||
`teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
|
||
containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200,
|
||
`teeoff.no` upåvirket.
|
||
- **Oppfølging samme runde, avklart med bruker:** medspillere skal kunne
|
||
registrere score for HELE flighten (ikke bare egen rad) når ADR-036
|
||
fase 3 bygges — oppdatert i ARCHITECTURE_DECISIONS.md/FEATURE_BACKLOG.md.
|
||
To nye notater lagt i FEATURE_BACKLOG.md (ingen design/bygging ennå,
|
||
kun fanget opp): (1) turneringsoppsett mangler en vei til å FLYTTE en
|
||
rostret spiller til et annet lag (kun fjerning finnes i dag), (2) et
|
||
reelt modelleringsspørsmål om flere flighter i én frittstående runde
|
||
(f.eks. "min flight + vennenes flight bak oss samme dag") — to
|
||
prinsipielt ulike retninger skissert, ingen valgt.
|
||
|
||
- **ADR-036 fase 1 (venner-kjernen) BYGGET OG SCRATCH-VERIFISERT, IKKE
|
||
ENNÅ RULLET UT (2026-07-25):** ny migrasjon `025_friends.sql`
|
||
(`friendship` + `friend_categorization`, ingen RLS — samme
|
||
`plain_connection()`-mønster som runder) og nytt `app/routers/
|
||
friends.py`: `GET /people/search` (tiered venn→klubb→land→globalt,
|
||
navnerekkefølge-uavhengig token-matching, min. 2 tegn før noe
|
||
returneres), `POST /friends`/`POST /friends/{id}/accept`/
|
||
`DELETE /friends/{id}` (forespørsel/aksept/avslå-kanseller-avvenn — én
|
||
DELETE dekker alle tre), `GET /friends`, `PUT /friends/{friend_user_id}/
|
||
categories` (erstatter hele settet, krever akseptert vennskap). Router
|
||
registrert i `main.py`, `/people`+`/friends`-rewrites lagt til i
|
||
`next.config.mjs` (ny frontend-rute vil hete `/my-friends`, IKKE
|
||
`/friends` — unngår samme kollisjonsfelle som `/rounds` fra før).
|
||
**Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API-
|
||
container, 5 syntetiske brukere): 31 sjekker, inkl. presist bevist
|
||
tiered rangering for 4 distinkte brukere samtidig, og at
|
||
kategorisering er EKTE PRIVAT (bekreftet: B ser aldri kategoriene A
|
||
satte B i). `test_isolation.sql` fortsatt 12/12. Ekte typesjekket
|
||
produksjonsbuild kompilerte rent. **Rullet ut live 2026-07-25**, bruker
|
||
bekreftet eksplisitt: migrasjon 025 kjørt mot ekte `teecup_db` (begge
|
||
tabeller bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
|
||
`docker compose up -d --build teecup_api teecup_frontend`. Begge
|
||
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket. Verifisert presist at ruten faktisk når FastAPI (ikke bare
|
||
at Next.js svarte): anonymt `GET /people/search`/`GET /friends` over
|
||
ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404. Frontend
|
||
bevisst IKKE hånd-kodet denne gangen (V0-prompt skrevet i
|
||
FEATURE_BACKLOG.md i stedet, matcher etablert mønster/tidligere
|
||
korrigering) — venter på at brukeren kjører den i v0.app.
|
||
|
||
- **ADR-036 fase 1 FRONTEND BYGGET, IKKE ENNÅ RULLET UT (2026-07-25):**
|
||
zip 19 mottatt og integrert — kun `components/friends.tsx` og
|
||
`app/friends/page.tsx` var reelt nye fra V0 (samme full-reeksport-
|
||
mønster som alltid, resten forventede reverts av allerede tilpassede
|
||
filer, hoppet over). **V0s egen rute (`/friends`) BEVISST IKKE brukt**
|
||
— flyttet til `/my-friends`, siden `/friends` nå er API-prefikset
|
||
(samme kollisjonsklasse som `/rounds` → `/my-rounds` tidligere, unngått
|
||
fra start denne gangen i stedet for oppdaget i produksjon).
|
||
Datalag skrevet fullstendig om fra V0s mock til ekte fetch mot
|
||
`/people/search`+`/friends`-endepunktene — TS-typene speiler Pydantic-
|
||
modellene i `app/routers/friends.py` felt-for-felt. Kategori-koder
|
||
(`spouse`, `golf_friends`, osv.) mappet mot V0s norske visningsnavn via
|
||
en delt `CATEGORY_OPTIONS`-liste i SAMME rekkefølge som backend sin
|
||
`Category`-type. Søkefeltet håndhever samme 2-tegns-minimum som
|
||
backend (viser en forklarende tekst i stedet for å bare returnere
|
||
tomt). "Send forespørsel"/"Godta"/"Avslå"/"Kanseller"/"Fjern venn"
|
||
trigger alle en full refetch av `GET /friends` etterpå (samme
|
||
refetch-etter-mutasjon-mønster som resten av appen) — kun kategori-
|
||
avkrysning er lokalt optimistisk (matcher PUT-endepunktets
|
||
erstatt-hele-settet-kontrakt).
|
||
**Dashbordets "Venner"-blokk koblet til ekte data i samme runde:**
|
||
root-komponenten henter nå `GET /friends`, viser ekte avatar-initialer
|
||
+ antall venner + ventende forespørsler (samme visuelle design som
|
||
opprinnelig i zip 18, som ble bevisst forenklet til en inert tom-
|
||
tilstand forrige runde siden backend ikke fantes ennå) — begge
|
||
knappene ("Se venner"/"Søk etter venner") lenker nå til `/my-friends`.
|
||
Ekte typesjekket produksjonsbuild kompilerte rent, `/my-friends` listet
|
||
blant 22 ruter. **Rullet ut live 2026-07-25**, bruker bekreftet
|
||
eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte
|
||
også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt).
|
||
Begge containere boot-et rent, `/health`/`/dashboard`/`/my-friends` →
|
||
200, `teeoff.no` upåvirket. **ADR-036 fase 1 (venner-kjernen) er
|
||
dermed helt ferdig, backend + frontend, live.**
|
||
|
||
- **Notat 2026-07-25: scramble-statistikk (utslag brukt per spiller) —
|
||
IKKE bygget, kun fanget opp.** Brukeren ba om at scramble-turneringer
|
||
skal føre statistikk over hvor mange ganger hver spillers utslag ble
|
||
valgt av laget. Dette er reelt NY datamodell — dagens `hole_score`/
|
||
`match_hole_result` for scramble er en delt rad per side, ingen
|
||
kobling til HVILKEN spiller sitt utslag ble brukt. Trolig samme
|
||
problemstilling for greensome. Krysset mot det allerede eksisterende
|
||
"Scramble-grensesnitt"-punktet i ARCHITECTURE_DECISIONS.md sin "Åpne
|
||
spørsmål"-seksjon, full detalj i FEATURE_BACKLOG.md.
|
||
|
||
- **Bug fikset: `display_name` synkroniserte aldri med for-/etternavn
|
||
(2026-07-25), rapportert av bruker med skjermbilde av dashbordet.**
|
||
`app_user.display_name` settes i dag KUN fra e-postens lokaldel ved
|
||
kontoopprettelse (`verify_magic_link`) — ADR-031s profil-fullføring la
|
||
til atskilte `first_name`/`last_name`-felt, men rørte aldri
|
||
`display_name`. Konsekvens: en bruker med fullstendig utfylt profil
|
||
viste fortsatt e-post-avledet plassholdernavn overalt `display_name`
|
||
brukes (org-medlemslister, invitasjons-e-post, dashbord-hilsen — ikke
|
||
bare dashbordet). **Fikset** i `app/routers/auth.py` sin
|
||
`update_profile`: synkroniserer nå `display_name` automatisk til
|
||
`"{first_name} {last_name}"` hver gang et av de to feltene endres via
|
||
`PATCH /auth/profile` — KUN når begge er satt etterpå (unngår et
|
||
halvferdig navn ved delvis utfylling). Scratch-verifisert (7/7 sjekker:
|
||
fersk konto får fortsatt e-post-plassholder, delvis utfylling (kun
|
||
fornavn) rører IKKE `display_name` ennå, komplett for-/etternavn
|
||
synkroniserer korrekt, urelaterte PATCH-er (f.eks. HCP) lar
|
||
`display_name` stå urørt, en SENERE navneendring re-synkroniserer på
|
||
nytt).
|
||
**Rullet ut mot ekte systemer 2026-07-25**, bruker bekreftet
|
||
eksplisitt (valgte "kjør begge deler"): `teecup_api` redeployet, samt
|
||
en engangs data-rettelse kjørt direkte mot ekte `teecup_db`
|
||
(`UPDATE app_user SET display_name = ... WHERE first_name/last_name
|
||
utfylt OG display_name avvek`) for å rette allerede-utfylte kontoer
|
||
som ikke ville blitt rettet av seg selv (krever en NY navneendring for
|
||
å trigge synk-koden). Bekreftet FØR kjøring med en dry-run `SELECT`
|
||
(2 kontoer berørt: `erolhaagenrud@gmail.com` → "Tore Morell",
|
||
`hei@erol.no` → "Erol Haagenrud"), kjørt med `RETURNING` for å
|
||
bekrefte nøyaktig hvilke rader som ble endret, `test_isolation.sql`
|
||
fortsatt 12/12 etterpå. Ingen migrasjon (ren datarettelse + kodefiks).
|
||
|
||
- **Varsler (push til telefon + in-app varslingssenter) — DESIGNET
|
||
2026-07-25, IKKE bygget.** Brukeren spurte om dagens PWA kan varsle
|
||
telefonens eget system (f.eks. ny venneforespørsel), og ba om en
|
||
in-app-fallback (bjelle-indikator + uleste-side) med et V0-prompt klart
|
||
"i tilfelle". Svar: ekte push er teknisk mulig (ADR-028s PWA-fundament),
|
||
men iOS Safari krever PWA-en installert til hjemskjermen for at Web
|
||
Push skal fungere i det hele tatt, pluss ny infrastruktur (VAPID,
|
||
`push_subscription`-tabell, `pywebpush`-utsending, eksplisitt
|
||
tillatelse) — anbefalt som EGEN, senere runde, ikke første steg.
|
||
Anbefalte i stedet et in-app varslingssenter først (ny
|
||
`notification`-tabell, triggerpunkter ved venneforespørsel sendt/
|
||
akseptert, ny `/my-notifications`-rute — ikke `/notifications`, samme
|
||
kollisjonsklasse unngått som `/rounds`/`/friends`). V0-prompt skrevet
|
||
i FEATURE_BACKLOG.md, ikke sendt ennå. Ingen kode skrevet — ren
|
||
design-/dokumentasjonsrunde.
|
||
|
||
- **Oppfølging samme dag: e-post-fallback for varsler + PWA-
|
||
installasjon vurdert, IKKE bygget.** Varsel-V0-prompten sendt til
|
||
bruker. Bruker foreslo en betinget e-post-fallback ved venneforespørsel
|
||
(kun hvis mottaker har samtykket til e-post fra TeeCup) — sjekket at
|
||
INGEN generell kommunikasjons-samtykke-flagg finnes i dag (kun
|
||
ADR-017s turnering-registrerings-samtykke, noe annet), foreslo nytt
|
||
opt-in `app_user.notification_emails_enabled` + en sjette
|
||
`send_friend_request_email()`-funksjon i `app/email.py` (samme mønster
|
||
som de fem eksisterende). Bruker spurte også hvordan få brukere til å
|
||
installere PWA-en "nærmest umiddelbart" — vurdert grundig: Android kan
|
||
fange `beforeinstallprompt` og vise egen timing, iOS Safari har INGEN
|
||
programmatisk installasjonsvei (kun instruksjonsoverlegg mulig, hard
|
||
Apple-begrensning) — anbefalte å vise oppfordringen rett etter
|
||
obligatorisk profil-fullføring (universelt sjekkpunkt alle nye brukere
|
||
allerede går gjennom) fremfor bokstavelig "umiddelbart". Begge kun
|
||
vurdert/skissert i FEATURE_BACKLOG.md, ingen kode skrevet, intet
|
||
V0-prompt for PWA-delen ennå.
|
||
|
||
- **To nye drøftingspunkter, IKKE besluttet eller bygget (2026-07-25):**
|
||
bruker lastet opp to skjermbilder av Golf GameBooks leaderboard-løsning
|
||
(egen "Leaderboards"-fane, rangert liste, trykk-ut til fullt
|
||
scorekort) og spurte om (1) et tilsvarende leaderboard for
|
||
runder/turneringer, usikker på plassering, og (2) om TeeCup burde få
|
||
1-2 nye designfarger. Drøftet grundig i FEATURE_BACKLOG.md, ikke
|
||
konkludert: fant at "runder" kan få dette NÅ (flere-deltakere-støtte
|
||
finnes allerede, ingen ADR-036-avhengighet) mens "turneringer" sitt
|
||
EKSISTERENDE leaderboard er lag-poeng (Ryder Cup-matchplay), strukturelt
|
||
noe annet enn Golf GameBooks individuelle rangering — åpent
|
||
avklaringsspørsmål. For fargespørsmålet: fant at en blåtone ALLEREDE
|
||
finnes i `--chart-3` (bare ikke løftet til en kjerne-designtoken) —
|
||
anbefalte å gjenbruke DEN i stedet for å finne på en helt ny, og
|
||
anbefalte mot flere enn én ekstra farge. Ingen kode skrevet, ingen
|
||
V0-prompt — ren refleksjonsrunde på brukerens eksplisitte instruks.
|
||
|
||
- **In-app varslingssenter + rundeleaderboard-backend BYGGET OG SCRATCH-
|
||
VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** to ting i samme runde.
|
||
(1) Brukeren lastet opp zip 20 — resultatet av varsel-V0-prompten fra
|
||
2026-07-25 (bjelle-ikon i dashbord-header, egen varslingsside). Bygget
|
||
matchende backend: migrasjon `026_notifications.sql` (tabell
|
||
`notification`, `plain_connection()`-mønster som resten av bruker-eid
|
||
data), `app/routers/notifications.py` (fire endepunkter + delt
|
||
`create_notification()`-hjelpefunksjon), to trigger-punkter i
|
||
`friends.py` (venneforespørsel sendt/akseptert — de eneste hendelsene
|
||
som faktisk finnes i dag). Frontend integrert kirurgisk (kun de nye
|
||
filene + et uttrekk fra V0s dashbord-eksport, ikke en full revert):
|
||
`components/notifications.tsx` skrevet om fra V0s mock-scenario til
|
||
ekte fetch, ny rute `/my-notifications` (IKKE `/notifications` — samme
|
||
kollisjonsklasse unngått fra start som `/rounds`/`/friends`),
|
||
bjelle-komponenten portert inn i den LIVE `dashboard.tsx` sin header,
|
||
koblet til et ekte `GET /notifications/unread-count`-kall.
|
||
(2) Brukeren ba samtidig om et leaderboard for frittstående runder
|
||
(opptil 13+ spillere mulig, flere flighter). Bygget ny
|
||
`GET /rounds/{round_id}/leaderboard` i `app/routers/rounds.py` — brutto
|
||
OG netto score-til-par + "thru"-antall PER deltaker, sortert stigende
|
||
på brutto. Netto bruker samme `allocate_strokes_by_index`-algoritme som
|
||
resten av appen (`strokes_received` i `list_holes`), denne gangen
|
||
summert over KUN de faktisk spilte hullene.
|
||
**Scratch-verifisert grundig, 39/39 sjekker i to testløp** (isolert
|
||
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
||
container, samme mønster som hele prosjektet): full varsel-syklus begge
|
||
retninger + `mark-all-read` + kryss-bruker-isolasjon (bruker B kan ikke
|
||
markere bruker A sitt varsel som lest via id-gjetting), full
|
||
leaderboard-runde (3 deltakere, ulik fremdrift/score, riktig rangering/
|
||
thru/to-par for alle), kryss-bruker-autorisasjon (403), ukjent
|
||
runde-id (404) — OG en dedikert netto-kryssjekk der resultatet ble
|
||
sammenlignet mot en HELT UAVHENGIG beregning via
|
||
`handicap_engine.allocate_strokes_by_index` kalt direkte fra
|
||
testskriptet (ikke bare "endepunktet svarte 200") — stemte eksakt,
|
||
inkl. et bevisst vekslende stroke-index-mønster og kun 9 av 18 hull
|
||
spilt, for å teste allokeringen over et REELT delvis spilt sett, ikke
|
||
et trivielt sammenfallende tilfelle. `test_isolation.sql` fortsatt
|
||
12/12. Ekte typesjekket produksjonsbuild av frontend kompilerte rent,
|
||
`/my-notifications` listet blant rutene.
|
||
**V0-prompt for selve leaderboard-VISNINGEN skrevet og sendt til
|
||
bruker** (se FEATURE_BACKLOG.md for hele prompten) — rangering med
|
||
delt plassering, brutto/netto-veksling, "thru X"/"Ferdig"-tilstander,
|
||
kompakt mini-variant til rundens detaljside, alltid form+farge for
|
||
over/under par. Ikke kjørt i v0.app ennå — leaderboardets FRONTEND
|
||
kommer i en senere runde.
|
||
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon
|
||
026 kjørt mot ekte `teecup_db` (tabell `notification` bekreftet,
|
||
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
|
||
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||
`/health`/`/dashboard`/`/my-notifications` → 200, `teeoff.no`
|
||
upåvirket. Verifisert presist at `/notifications`-ruten faktisk når
|
||
FastAPI (ikke bare at Next.js svarte): anonymt `GET /notifications`
|
||
over ekte https ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.
|
||
|
||
- **Navneformat-regel + turnering-scope avklart, IKKE bygget (2026-07-26):**
|
||
brukeren instruerte at direkte adressering (hilsener, e-post/varsler
|
||
rettet til mottakeren) alltid skal bruke KUN fornavn, mens vanlige
|
||
visninger (lister, roster, chat) fortsatt bruker fullt navn/initialer —
|
||
lagt til som ny stående regel (se egen seksjon over). Kjent brudd
|
||
notert: dashbord-hilsenen bruker i dag fullt navn, ikke rettet ennå.
|
||
Deretter bedt om analyse av .md-filene for prioritering — landet på å
|
||
avklare turnering-leaderboard-spørsmålet fra 2026-07-25 først. Svaret
|
||
avdekket et vesentlig STØRRE ønske enn antatt: TeeCup skal etter hvert
|
||
støtte ekte INDIVIDUELLE turneringer (ikke bare lag), disse skal kunne
|
||
gå over flere runder, og det skal være mulig med et Order of Merit
|
||
(sesong-sammenlagt på tvers av flere arrangementer, brukerens eksempel:
|
||
"klubbdager" gjennom en sesong). Grundig notert i FEATURE_BACKLOG.md og
|
||
ARCHITECTURE_DECISIONS.md (ADR-011s eget notat + ny "Åpne spørsmål"-
|
||
post 6) — inkl. en reell strukturell kollisjon identifisert: en
|
||
flerrunde-individuell-turnering vil kollidere med ADR-033 sitt bevisst
|
||
org-UAVHENGIGE frittstående rundesystem, må avklares eksplisitt før
|
||
bygging. **Ren notat-runde, ingen ADR skrevet, ingen kode** — brukeren
|
||
ba eksplisitt kun om at dette dokumenteres nå.
|
||
- **Flere flighter i én frittstående runde — presisert videre, fortsatt
|
||
IKKE besluttet (2026-07-26):** oppfølgende avklaring samme runde.
|
||
Brukeren presiserte: oppsett av flere flighter er ÉN handling utført av
|
||
én person (samme gjest-mønster som i dag, bare flere flight-grupper),
|
||
scoreregistrering per flight er et SENERE, separat ansvar (forventet
|
||
løst av ekte medspillere, ADR-036 fase 3) — og viktigst: leaderboardet
|
||
skal KUN dekke flightene som ble satt opp SAMMEN i én handling, ikke
|
||
"alle som spilte samme bane samme dag". Det siste peker sterkt mot
|
||
retning 1 (løs gruppering av separate `round`-rader) fra forrige
|
||
runde, men er ikke formelt bekreftet som byggeretning. Brukeren
|
||
påpekte selv at dette ligger i grenselandet mot "individuelle
|
||
turneringer"-punktet rett over — notert eksplisitt som en mulig
|
||
forening av de to, ikke to separate systemer. Se FEATURE_BACKLOG.md
|
||
for full detalj. Ren notat-runde, ingen kode, ingen ADR.
|
||
|
||
- **ADR-037 skrevet: grunnstruktur for individuelle/flerrunde-turneringer
|
||
(2026-07-26), samme dag som de to foregående notat-rundene.** Bruker ba
|
||
om å starte ADR-runden på strukturspørsmålet direkte (ikke bare
|
||
notere). Fire load-bærende beslutninger avklart eksplisitt
|
||
(AskUserQuestion), i rekkefølge: (1) ny, PARALLELL org-scopet
|
||
datamodell for individuelle turneringer — IKKE en utvidelse av
|
||
ADR-033s frittstående `round`-tabeller, bevisst for å unngå hybrid/
|
||
betinget RLS (samme risikoklasse som den tidligere RLS-tomstreng-
|
||
bugen) og for å bevare ADR-033 Beslutning A urørt; (2) SAMME
|
||
`tournament`-tabell med en ny `format_type`-diskriminator
|
||
(`'team'`/`'individual'`), ikke en helt ny toppnivå-entitet —
|
||
gjenbruker synlighet/join-kode/status (ADR-018/020) uendret; (3)
|
||
flerrunde-støtte fra START via en ny `tournament_round`-tabell
|
||
(økt-lignende), sammenlagt resultat summert ved lesing på ekte
|
||
deltaker-id (samme prinsipp som dagens lag-leaderboard); (4) rå
|
||
brutto slagtall lagres OG et ferdig utregnet poengtall CACHES per
|
||
scoringsmetode — samme etablerte mønster som `match.status_text`/
|
||
`points_side_a/b` — nye formater (Københavner m.fl.) blir dermed i
|
||
hovedsak én ny motorfunksjon + én ny CHECK-verdi, ikke en
|
||
skjemaendring.
|
||
**Viktig presisering som oppsto underveis, endrer forrige rundes
|
||
antakelse:** "flight" i en formell org-turnering er KUN en
|
||
tee-tid-gruppering — leaderboardet spenner alltid HELE feltet.
|
||
Dette er strukturelt ULIKT den ad hoc "flere flighter i en
|
||
frittstående runde"-ideen (der leaderboardet bevisst avgrenses til
|
||
det man selv satte opp) — de to holdes derfor bevisst ADSKILT, ikke
|
||
forent slik forrige runde antydet. Begge berørte FEATURE_BACKLOG.md-
|
||
seksjoner oppdatert med denne presiseringen.
|
||
**Fortsatt IKKE avgjort:** de fem konkrete formatenes egne poengregler
|
||
(Københavner m.fl., egne mindre design-runder oppå denne strukturen),
|
||
og Order of Merit (sesong-sammenlagt, bekreftet som naturlig SISTE
|
||
steg). **Ingen migrasjon eller kode skrevet** — ADR-037 er ren
|
||
struktur-beslutning; neste steg er et konkret migrasjonsutkast
|
||
(nye tabeller + `tournament.format_type`) lagt frem til gjennomgang
|
||
før noe kjøres.
|
||
|
||
- **"Fiks alle kjente små bugs"-runde, BEGGE FIKSET OG SCRATCH-VERIFISERT
|
||
(2026-07-26), samme dag som ADR-037:** brukeren ba eksplisitt om å
|
||
rydde opp i alle dokumenterte, fortsatt åpne små bugs. Systematisk
|
||
gjennomgang av CLAUDE.md/FEATURE_BACKLOG.md (søk på "IKKE fikset"/
|
||
"urelatert bug" m.fl.) fant at nesten alt tidligere flagget som
|
||
"ikke fikset" faktisk ble fikset i en SENERE runde lenger ned i loggen
|
||
(stale seksjonsoverskrifter, ikke reelt åpne bugs) — kun to genuint
|
||
fortsatt åpne:
|
||
1. **Dashbord-hilsen brukte fullt navn** (nettopp etablert
|
||
navneformat-regel, se egen seksjon over) — `me.first_name ??
|
||
me.display_name` i stedet for `me.display_name` alene.
|
||
`/auth/me` eksponerte allerede `first_name`, ingen backend-endring.
|
||
2. **Stroke-modus-scoreinnsending på en bane UTEN registrerte hull
|
||
krasjet rått (500 IndexError)** i stedet for en ren
|
||
`VALIDATION_FAILED` — `scoring.py` sin `_compute_hole_results`
|
||
kalte `allocate_over_played_holes` med en TOM stroke-indeks-liste
|
||
når `hole`-tabellen var tom for banen (f.eks. en egendefinert bane
|
||
der noen glemte å legge til de 18 hullene). Fikset med en eksplisitt
|
||
`len(all_18_si) != 18`-sjekk RETT FØR beregningen — samme mønster
|
||
som den allerede kjente/fikset manglende-handicap-indeks-krasjen
|
||
fra blind draw-runden (2026-07-18).
|
||
**Scratch-verifisert grundig (5/5 sjekker)** for backend-fiksen
|
||
(isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
|
||
API-container, samme mønster som ellers i prosjektet): en bane UTEN
|
||
hull ga korrekt `400 VALIDATION_FAILED` ved scoreinnsending (ikke
|
||
500), OG en egen regresjonssjekk bekreftet at en NORMAL bane (alle 18
|
||
hull) fortsatt scorer helt uendret (begge sider, full
|
||
scorekort-henting via `GET .../scorecard`). `test_isolation.sql`
|
||
12/12 uendret — ren Python-logikk-fiks, ingen migrasjon. Ekte
|
||
typesjekket produksjonsbuild av frontend kompilerte rent (dashbord-
|
||
fiksen). To stale FEATURE_BACKLOG.md-seksjonsoverskrifter rettet
|
||
samtidig ("Rapporterte UI-/UX-hull" og selve bug-notatet), siden de
|
||
fortsatt sa "IKKE fikset" lenge etter at innholdet faktisk var
|
||
fikset.
|
||
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent (`Application startup complete`, Next.js
|
||
`Ready`), `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
|
||
- **ADR-036 fase 3 (delvis): søk+legg til ekte medspiller på en
|
||
frittstående runde, BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE
|
||
ENNÅ RULLET UT:** brukeren rapporterte at "+ Gjest"-skjemaet ikke
|
||
søkte etter spillere når man skrev et navn — bekreftet reelt (rent
|
||
tekstfelt, `/people/search` var aldri koblet på). Spurte om
|
||
omfang før bygging: brukeren ville ha "full tilgang nå" (medspilleren
|
||
kan selv registrere score), ikke bare rask utfylling — et bevisst
|
||
større valg enn det anbefalte minimum.
|
||
**Backend:** `POST /rounds/{id}/participants` tar nå ENTEN `user_id`
|
||
(funnet via tiered `/people/search`, samme algoritme som vennesøket)
|
||
ELLER `guest_name` (uendret) — kjønn/HCP hentes automatisk fra den
|
||
valgte personens egen profil. Ny migrasjon `027_round_participant_
|
||
user_unique.sql` (partiell unik indeks). Ny `_get_accessible_round_
|
||
or_404` (eier ELLER lenket medspiller) for lesing/hull-scoring/
|
||
fullføring — `_get_owned_round_or_404` (strengt eier-only) beholdt
|
||
for rediger/slett/legg til/fjern/endre stat_level. `RoundOut` sine
|
||
`owner_*`-felt omdøpt til `my_*` og gjort VIEWER-relative (regnes nå
|
||
fra den spørrende brukerens egen deltaker-rad).
|
||
**Reell latent bug funnet OG fikset i SAMME runde, før den nådde
|
||
produksjon:** leaderboard-endepunktet (bygget tidligere samme dag,
|
||
se over) ville vist et TOMT navn for enhver lenket medspiller, siden
|
||
dets `display_name`-utledning kun sjekket `is_owner`, ikke om
|
||
`user_id` var satt i det hele tatt. Fanget under scratch-testing av
|
||
DENNE runden, fikset før utrulling av noen av delene.
|
||
**Frontend:** "+ Gjest" omdøpt til "+ Medspiller" i `round-detail.tsx`,
|
||
nytt søk-som-du-skriver-felt (avatar-initialer, hjemmeklubb) med
|
||
"Legg til uten konto"-fallback til det gamle tekstfeltet. Viewer-
|
||
relativ "Deg"-visning (ny `/auth/me`-bruk for viewerId) — samme fiks
|
||
portert til `round-stats.tsx`/`round-scorecard.tsx`, som hadde
|
||
identisk latent bug (ville vist eieren som "Deg" for en medspiller
|
||
som så på). Rediger/slett/legg-til/fjern-knappene skjules nå for en
|
||
ikke-eier-viewer.
|
||
**Scratch-verifisert grundig, 39/39 sjekker i to testløp:** søk-og-
|
||
legg-til med auto-utfylt kjønn/HCP, avvist duplikat/selv-tillegg/
|
||
ukjent bruker/ufullstendig profil, lenket medspiller kan lese runden
|
||
+ registrere score for BÅDE egen OG andres rad (whole-flight-
|
||
regelen bekreftet), men nektes å forvalte runden (alle
|
||
forvaltningskall 403), lenket medspiller KAN fullføre runden,
|
||
`/rounds`-listen viser nå runden for en lenket medspiller med DERES
|
||
EGEN fremdrift (ikke eierens), urelatert bruker fortsatt 403/ikke i
|
||
listen, leaderboard-navn stemmer for alle tre deltakertyper.
|
||
`test_isolation.sql` 12/12 uendret. Ekte typesjekket produksjonsbuild
|
||
kompilerte rent.
|
||
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: migrasjon
|
||
027 kjørt mot ekte `teecup_db` (unik indeks bekreftet,
|
||
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
|
||
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||
`/health`/`/dashboard`/`/my-rounds` → 200, `teeoff.no` upåvirket.
|
||
|
||
- **Sanntid for frittstående runder, BYGGET OG SCRATCH-VERIFISERT
|
||
(2026-07-26), samme dag som medspiller-søket over:** brukeren spurte om
|
||
spillere ser i sanntid at en annen har registrert en score — svaret var
|
||
nei (kun engangs-henting ved lasting), og brukeren ba om at det bygges,
|
||
gjenbruk av det etablerte "noe endret seg, hent på nytt"-WebSocket-
|
||
mønsteret fra ADR-027 ("Følg live" for turneringer).
|
||
`app/realtime.py` utvidet (fortsatt rutefri, se moduldoc) med en andre,
|
||
parallell kringkastings-registry for runder (`live_sockets_for_round`/
|
||
`broadcast_round_update`, delt `_broadcast()`-hjelpefunksjon for å unngå
|
||
duplisert kringkastingslogikk). Nytt `@router.websocket("/ws/rounds/
|
||
{round_id}/live")` i `rounds.py` — ALDRI anonym tilgang (ulikt
|
||
turnering-live), krever eier ELLER lenket medspiller
|
||
(`_get_accessible_round_or_404`, samme sjekk som resten av
|
||
medspiller-utvidelsen). Kringkasting lagt inn i `update_hole`,
|
||
`complete_round`, `add_participant`, `remove_guest_participant`,
|
||
`update_round` og `delete_round` — alt som endrer noe de andre på
|
||
skjermen bør få vite om. Ingen ny Caddy-endring nødvendig (`/ws/*` er
|
||
allerede en wildcard-rute fra ADR-025).
|
||
**Reelt funn under scratch-testing, ikke en bug, men verdt å dokumentere:**
|
||
Starlette avviser en WebSocket FØR `.accept()` alltid som en bar HTTP
|
||
403 under selve håndtrykket — de tre distinkte lukkekodene (4401/4403/
|
||
4404) jeg satte når til `websocket.close()` server-side, men skiller seg
|
||
IKKE fra hverandre i klientens håndtrykk-avvisning (alle tre ga HTTP 403
|
||
i en ekte WS-klienttest, ikke bare curl). Selve sikkerheten (tilkobling
|
||
korrekt avvist i alle tre tilfeller) er upåvirket — samme underliggende
|
||
Starlette-oppførsel gjelder trolig også den eksisterende turnering-live-
|
||
ruten (ADR-027), bare ikke tidligere testet med en ekte WS-klient på
|
||
dette presisjonsnivået.
|
||
**Frontend:** `round-detail.tsx` åpner en WebSocket ved montering,
|
||
refetcher runden ved signal og henter aktiv spillers hull DIREKTE på
|
||
nytt (ingen mellomsteg med tom stat som ville blinket for spilleren som
|
||
selv nettopp registrerte et slag) — andre spilleres hull-cache droppes
|
||
i stedet, hentes friskt ved neste fanebytte. Bruker en ref for å unngå
|
||
at socket-en kobles til/fra ved hvert fanebytte. `round-stats.tsx`/
|
||
`round-scorecard.tsx` (rene lesevisninger) fikk en enklere
|
||
`refreshKey`-basert variant, samme mønster som `public-live.tsx` fra
|
||
ADR-027.
|
||
**Scratch-verifisert grundig, 10/10 sjekker, ekte WebSocket-klient (ikke
|
||
bare REST):** ekte kringkasting bekreftet begge veier — eierens åpne
|
||
socket mottok et signal da medspilleren registrerte et slag via REST,
|
||
OG medspillerens åpne socket mottok et signal da eieren fullførte runden
|
||
— ikke bare at REST-svarene så riktige ut. Alle tre avvisningstilfellene
|
||
(uautentisert, urelatert fremmed, ukjent runde-id) korrekt avvist.
|
||
`test_isolation.sql` 12/12 uendret (ingen migrasjon). Ekte typesjekket
|
||
produksjonsbuild kompilerte rent.
|
||
**Rullet ut live 2026-07-26**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
|
||
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket. Verifisert presist at `/ws/rounds/*`-ruten faktisk når
|
||
FastAPI: et ekte WS-håndtrykk-forsøk mot en ukjent runde-id over
|
||
produksjons-https ga FastAPI sin egen JSON-`{"detail":"Not Found"}`,
|
||
ikke Next.js sin HTML-404 — samme verifiseringsmønster som ADR-027s
|
||
tournament-live-rute.
|
||
- **HCP i medspiller-søk + rediger utslag/HCP per deltaker, BYGGET OG
|
||
SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren viste et
|
||
skjermbilde av "+ Medspiller"-søket og påpekte at spillerens HCP burde
|
||
vises der (kun navn+klubb vistes), og ba samtidig om å kunne endre
|
||
utslagssted og HCP for medspillere -- "det er ikke sikkert de spiller fra
|
||
samme utslagssted som meg, og det er ikke sikkert HCP er riktig", eksplisitt
|
||
presisert som en endring KUN for denne runden/turneringen, ikke spillerens
|
||
faktiske profil.
|
||
**Reelt hull bekreftet ved kodegjennomgang FØR bygging:** ALLE deltakere
|
||
på en runde har i dag brukt SAMME `round.tee_name_snapshot` -- ingen
|
||
per-deltaker-tee har noensinne eksistert (rating-tallene har vært per
|
||
deltaker siden migrasjon 021, men selve utslags-NAVNET var aldri
|
||
eksplisitt per rad).
|
||
**Bygget:** `PersonMatch` (`app/routers/friends.py`, delt av vennesøk OG
|
||
medspiller-søk) fikk `handicap_index: float | None`. Ny migrasjon
|
||
`028_round_participant_tee.sql` -- `round_participant.tee_name_snapshot`,
|
||
backfylt fra rundens eksisterende tee for alle eksisterende rader. `_create_
|
||
participant` skriver nå tee-navnet eksplisitt; `ParticipantCreate` (add_
|
||
participant) fikk et valgfritt `tee_name` som faller tilbake til rundens
|
||
tee hvis utelatt. Ny `GET /rounds/{id}/tee-options` (gjenbruker
|
||
`_ResolvedCourse` sin rating-dict via en ny `tee_options()`-metode) --
|
||
brukt av "rediger spiller"-panelet til å liste banens faktiske utslag.
|
||
`ParticipantUpdate` skrevet om til vanlig `exclude_unset`-PATCH-semantikk
|
||
(var tidligere kun `stat_level`, alltid påkrevd) -- nye valgfrie
|
||
`tee_name`/`handicap_index`-felt trigger en full re-beregning av
|
||
rating-snapshottene + `course_handicap_snapshot` for AKKURAT den
|
||
deltakeren, uten å røre spillerens egen `app_user.handicap_index`.
|
||
Eksplisitt `null` på `handicap_index` fjerner HCP-sporing for denne
|
||
deltakeren i denne runden (samme mønster som profil-PATCH). **Bevisst
|
||
avvist etter at runden er fullført** (409 `ALREADY_COMPLETED`, samme
|
||
presedens som `RoundUpdate` sitt bane-bytte -- differensialen er da
|
||
allerede beregnet fra det gamle grunnlaget); `stat_level` alene er
|
||
fortsatt tillatt uansett fullført-status. `RoundUpdate` sitt eksisterende
|
||
bane-bytte (ADR-033-oppfølging 2026-07-24) nullstiller nå eksplisitt ALLE
|
||
deltakeres `tee_name_snapshot` til rundens nye utslag (et bane-bytte gjør
|
||
individuelle tee-valg fra den gamle banen meningsløse).
|
||
**Frontend (`round-detail.tsx`):** søkeresultatene i "+ Medspiller" viser
|
||
nå HCP ved siden av hjemmeklubb. Ny "Utslag: X · HCP: Y"-rad under
|
||
statistikknivå-velgeren for aktiv spiller, med en "Rediger for denne
|
||
runden"-lenke (kun eier, kun før fullført) som åpner en ny
|
||
`EditParticipantPanel` -- utslagssted som en `<select>` fylt fra det nye
|
||
tee-options-endepunktet (filtrert på spillerens kjønn), HCP som fritekst,
|
||
forklarende "endrer ikke profilen"-tekst. Gjelder likt for eierens egen
|
||
rad som for medspillere (ingen spesialtilfelle).
|
||
**Scratch-verifisert, 27/27 sjekker** (isolert `teecup_app_scratch`-
|
||
rolle + isolert scratch-MinIO + engangs API-container): HCP i søk, legg
|
||
til medspiller med eksplisitt AVVIKENDE utslag fra eieren, gjest uten
|
||
eksplisitt tee faller korrekt tilbake til rundens tee, utslag uten rating
|
||
for kjønn avvist (400) både ved tilføyelse og redigering, tee-options
|
||
lister riktige utslag, rediger tee+HCP for medspiller lykkes og
|
||
course_handicap regnes om, **medspillerens egen profil-HCP forblir
|
||
UENDRET** (den kritiske sjekken -- bekrefter runde-scoping), delvis PATCH
|
||
(kun stat_level) lar tee/HCP stå urørt, eksplisitt `null` fjerner HCP
|
||
round-scoped, lenket medspiller (ikke eier) nektes å redigere deltakere
|
||
(403, forvaltning fortsatt eier-only), tom PATCH avvist (400), redigering
|
||
blokkert etter fullføring (409) mens stat_level fortsatt tillates. Full
|
||
regresjonskjøring av forrige rundes 35-punkts medspiller-søk-testsuite
|
||
(`test_round_coplayer.py`) mot samme friske scratch-database, alle 35
|
||
fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket
|
||
produksjonsbuild kompilerte rent.
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
migrasjon 028 kjørt mot ekte `teecup_db` (kolonne bekreftet `NOT NULL`,
|
||
2 eksisterende rader backfylt korrekt, `test_isolation.sql` fortsatt
|
||
12/12), deretter `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent (`Application startup
|
||
complete`, Next.js `Ready`), `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket.
|
||
- **V0-prompt for rundeleaderboardet rettet (2026-07-26), FØR den ble
|
||
kjørt i v0.app:** samme runde som over, brukeren ba eksplisitt om å få
|
||
leaderboardet for frittstående runder på plass. Backend
|
||
(`GET /rounds/{id}/leaderboard`) var allerede live siden tidligere samme
|
||
dag; V0-prompten (skrevet samme dag, se FEATURE_BACKLOG.md) hadde
|
||
derimot en utdatert antakelse -- den ba om et "Deg"-merke basert på at
|
||
kun eieren noensinne ser sin egen runde, som ikke lenger stemmer etter
|
||
ADR-036 fase 3 (medspillere kan nå også se runden). Rettet til to
|
||
uavhengige merker: "Eier" (fra API-ets `is_owner`) og "Deg" (fra en
|
||
`participant_id`-prop komponenten mottar utenfra, ikke fra selve
|
||
leaderboard-dataen). Prompten er ikke sendt til v0.app ennå.
|
||
- **Mottatte slag i hull-headeren + gjeste-e-post/kjønn/navn redigerbart,
|
||
BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren
|
||
viste et skjermbilde av det nettopp rullede "Rediger for denne runden"-
|
||
tillegget og ba om to ting: (1) hvor mange slag aktiv spiller MOTTAR på
|
||
det aktive hullet vist i selve hull-headeren (eksempel "Hull 7 - Par 4 -
|
||
Hcp 5 - -1"), og (2) for en midlertidig spiller (gjest) skal Navn/Kjønn/
|
||
E-post også være redigerbart, ikke bare utslag/HCP. Ba samtidig om et
|
||
V0-prompt for å designe om selve spillerlisten (utslag/HCP/rediger inn i
|
||
selve spillerknappen, listet vertikalt i stedet for dagens horisontale
|
||
scroll-rad).
|
||
**Punkt 1 bygget direkte** (triviell, ren frontend, ingen backend-endring
|
||
-- `strokes_received` var allerede hentet fra `GET .../holes` fra før,
|
||
bare aldri vist FØR scoring): `round-detail.tsx` sin hull-header viser nå
|
||
`· −N` (ekte minustegn, golfvis fortegn) når aktiv spiller mottar minst
|
||
ett slag på hullet, ellers uendret.
|
||
**Punkt 2 krevde ny backend:** ny migrasjon `029_round_participant_
|
||
guest_email.sql` (`round_participant.guest_email`, nullable). `Participant
|
||
Create` fikk et valgfritt `guest_email: EmailStr | None` (avvist sammen
|
||
med `user_id`). `ParticipantUpdate` fikk `guest_name`/`gender`/
|
||
`guest_email` -- KUN gyldig for en gjest (`user_id IS NULL`), avvist
|
||
(400) på en lenket deltaker. `gender`-endring inngår nå i samme "rating_
|
||
changed"-bunt som utslag/HCP (påvirker gyldig rating), avvist etter
|
||
fullføring (409, samme presedens); `guest_name` alene har ingen rating-
|
||
implikasjon og forblir redigerbar selv etter fullføring (ren metadata).
|
||
**Frontend for punkt 2 IKKE bygget ennå** -- overlatt til V0-prompten
|
||
(se FEATURE_BACKLOG.md), siden brukeren eksplisitt ba om det og den
|
||
eksisterende hånd-bygde `EditParticipantPanel` uansett skal erstattes av
|
||
den nye vertikale spillerlisten.
|
||
**Scratch-verifisert, 22/22 nye sjekker** (gjest med e-post ved
|
||
opprettelse, avvist sammen med user_id, navn+kjønn+e-post-redigering
|
||
lykkes og rating regnes om for nytt kjønn, eksplisitt null fjerner
|
||
e-post, kjønnsendring til urepresentert rating avvist med full rollback,
|
||
alle tre gjeste-feltene avvist på en LENKET deltaker mens utslag/HCP
|
||
fortsatt fungerer uendret der, kjønn/utslag/HCP-endring blokkert etter
|
||
fullføring mens navneendring fortsatt tillates). Full regresjonskjøring
|
||
av samme dags 27+35-punkts testsuiter mot samme friske scratch-database,
|
||
alle 62 fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket
|
||
produksjonsbuild kompilerte rent (inkl. punkt 1).
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
migrasjon 029 kjørt mot ekte `teecup_db` (kolonne bekreftet,
|
||
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
|
||
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
- **Spillerliste-redesign HÅNDKODET (2026-07-26), IKKE ENNÅ RULLET UT:**
|
||
brukeren gikk tom for V0-credits rett etter at prompten over ble sendt.
|
||
Spurt eksplisitt (AskUserQuestion): bygg direkte, eller vent på fornyede
|
||
credits — svar: bygg direkte. Bygget nøyaktig etter samme prompt/data-
|
||
kontrakt som allerede var skrevet (se FEATURE_BACKLOG.md for full
|
||
detalj), i samme Tailwind/shadcn-stil som resten av `round-detail.tsx`.
|
||
Ny `PlayerList` (erstatter `PlayerTabs`) — vertikal liste av spillerkort,
|
||
hvert kort en stor "velg som aktiv spiller"-knapp + separate Rediger-/
|
||
fjern-knapper (unngår nestede interaktive elementer), "Deg"/"Eier" som
|
||
`Badge`-komponenter. `EditParticipantPanel` utvidet med statistikknivå
|
||
(den frittstående `StatLevelPicker` er nå død kode, fjernet) og — kun
|
||
for gjester — Navn/Kjønn/E-post, med reaktivt utslagsfilter ved
|
||
kjønnsendring. "+ Medspiller"-flyten flyttet inn i selve `PlayerList`.
|
||
**Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, pluss
|
||
en engangs `next dev`-container mot scratch-backend (ekte runde med en
|
||
gjest med avvikende utslag/kjønn/e-post) — siden ga 200, ingen Next.js-
|
||
feilside. **Ærlig begrensning:** siden er en klient-komponent (data
|
||
hentes etter hydrering), så server-HTML-en viser kun last-skjelettet —
|
||
INGEN ekte nettleser-interaksjonstest utført (intet nettleserverktøy
|
||
tilgjengelig).
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen ny migrasjon, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket. **Bruker bør selv klikke gjennom flyten**
|
||
(spesielt gjeste-kjønnsendringens reaktive utslagsfilter og "velg vs.
|
||
rediger"-trykkflatene) før full tillit.
|
||
- **Tildelte slag + jevn korthøyde + rundeleaderboard, HÅNDKODET, IKKE ENNÅ
|
||
RULLET UT (2026-07-26):** to oppfølgingspunkter samme dag, full detalj i
|
||
FEATURE_BACKLOG.md. (1) Spillerkortet manglet "tildelte slag" (course
|
||
handicap for runden) og hadde variabel høyde — rettet: `Player` fikk
|
||
`courseHandicap`, kortet fikk fast `min-h-[68px]` med begge tekstlinjer
|
||
trunkert til én linje (ikke wrap), alle kort like høye uansett innhold.
|
||
(2) Brukeren spurte om jeg "med designerbrillene på" trodde jeg kunne få
|
||
rundeleaderboardet til å se like profesjonelt ut som V0 — svarte ja
|
||
(gjenbruk av etablerte mønstre, ikke fri utforskning) og bygget det: ny
|
||
`components/round-leaderboard.tsx` + rute `/my-rounds/[id]/leaderboard`,
|
||
gjenbruker `ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`
|
||
(som `ToParMark`), samme WS-sanntid-mønster som `round-stats.tsx`, delt
|
||
plassering ("T-N"), brutto/netto-veksling, mini-variant på rundens egen
|
||
side. Rangeringslogikken VERIFISERT UAVHENGIG i et frittstående
|
||
Node-script (19/19, inkl. tie-håndtering og uspilte spillere), pluss en
|
||
fersk scratch-runde med ekte API-kall som bekreftet leaderboard-JSON-en
|
||
stemte. Ekte typesjekket produksjonsbuild kompilerte rent. Samme ærlige
|
||
begrensning som over: ingen ekte nettleser-interaksjonstest.
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen migrasjon, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Hullscorer i leaderboardet + lenke fra rundelisten, BYGGET, IKKE ENNÅ
|
||
RULLET UT (2026-07-26):** brukeren presiserte rett etter forrige rulling
|
||
tre krav: leaderboardet skal ha en egen visning (allerede tilfellet, se
|
||
over), med lenke fra scoreføringssiden (allerede der, `RoundLeaderboardMini`)
|
||
OG fra rundelisten (`/my-rounds`, manglet), og skal vise hullscorer
|
||
(manglet helt -- kun aggregerte tall fantes).
|
||
**Backend:** `LeaderboardEntryOut` (`GET /rounds/{id}/leaderboard`) fikk
|
||
et nytt `holes: list[LeaderboardHoleOut]`-felt (hole_number/par/
|
||
stroke_index/played/score) -- data var ALLEREDE hentet per deltaker for
|
||
å beregne total_score/net_score_to_par, bare aldri eksponert rått før nå.
|
||
Ren tilføyelse, ingen migrasjon.
|
||
**Frontend:** `round-leaderboard.tsx` sine rader er nå klikkbare
|
||
(utvider/kollapser, default kollapset -- samme "trekkspill starter
|
||
lukket"-konvensjon som `round-stats.tsx`) og viser en horisontal strip
|
||
med per-hull-merker (`HoleMark`/`HoleStrip`, egen lokal variant av
|
||
`ScoreMark`s "form + farge"-språk fra `round-scorecard.tsx`, tilpasset en
|
||
kompakt flerspiller-liste i stedet for én tabell). `round-card.tsx`
|
||
(brukt av BÅDE `/my-rounds`-listen og dashbordets "Kommende runder") fikk
|
||
en ny "Se leaderboard"-lenke for runder med mer enn én deltaker --
|
||
krevde å gjøre om kortets ytre element fra selve lenken til en `<div>`
|
||
med lenken som ETT av to barn (unngår nestet `<a>`, samme feilklasse som
|
||
tidligere nestet-form-/knapp-feller i prosjektet).
|
||
**Verifisert:** backend-endringen bekreftet mot en fersk scratch-runde
|
||
(ekte API-kall, `holes`-arrayet inneholder riktige 18 rader per deltaker
|
||
inkl. korrekt `played`/`score` for både spilte og uspilte hull), full
|
||
regresjon av forrige rundes 35-punkts co-player-testsuite fortsatt grønn,
|
||
`test_isolation.sql` 12/12 (uendret, ingen migrasjon). Ekte typesjekket
|
||
produksjonsbuild kompilerte rent (ingen nye ruter, kun endrede
|
||
komponenter). Samme engangs `next dev`-container-sjekk som tidligere --
|
||
`/my-rounds`, `/my-rounds/[id]` og `/my-rounds/[id]/leaderboard` ga alle
|
||
200, ingen Next.js-feilside.
|
||
**Samme ærlige begrensning som tidligere håndkodede runder:** ingen ekte
|
||
nettleser-interaksjonstest (klikk for å utvide en rad, se hull-stripen,
|
||
se "Se leaderboard"-lenken i rundelisten) er utført.
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen migrasjon, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Leaderboard-oppfølging: poeng-modus + minimalistiske rader, BYGGET, IKKE
|
||
ENNÅ RULLET UT (2026-07-26):** brukeren påpekte at brutto/netto ble
|
||
presentert forskjellig (bryteren byttet BÅDE hvilket tall som vises OG
|
||
hele rangeringsrekkefølgen -- forvirrende hopp), og spurte om et eget
|
||
stableford-alternativ burde vært med. Foreslo og fikk bekreftet: gi alle
|
||
tre modiene (nå: Brutto/Netto/**Poeng**) IDENTISK radform (rangering,
|
||
navn, ETT tall, ferdig) i stedet for å prøve å vise flere tall samtidig.
|
||
Samtidig ba brukeren om å fjerne alt fra den kollapsede raden utover
|
||
navn+tall (Deg/Eier/pokal/hull-spilt-tekst) og fjerne selve
|
||
"kort"-følelsen -- radene skal flyte sømløst sammen, kun ekspandere ved
|
||
klikk.
|
||
**Backend:** `LeaderboardHoleOut` fikk `strokes_received` (samme
|
||
allerede-beregnede allokering som `net_score_to_par`, nå eksponert per
|
||
hull også). `LeaderboardEntryOut` fikk `total_points` (stableford-total,
|
||
samme formel/betingelse som `net_score_to_par` -- null uten HCP). Ingen
|
||
migrasjon.
|
||
**Frontend (`round-leaderboard.tsx`):** `Mode` utvidet til tre verdier,
|
||
poeng rangeres SYNKENDE (motsatt av til-par). Ny `PointsMark`/`ValueMark`
|
||
(poeng har ingen retning, kun fylt/uthevet for lederen). Listen er nå ÉN
|
||
sammenhengende `<ul>` (`divide-y`, kun ytterkanten avrundet/rammet) i
|
||
stedet for separate kort med mellomrom mellom hver rad -- ingen
|
||
per-rad-bakgrunn utenom en svak tone på en UTVIDET rad. Kollapset rad:
|
||
kun rangering+navn+tall+pil. Deg/Eier/pokal/fremdrift FLYTTET (ikke
|
||
fjernet) inn i den utvidbare seksjonen sammen med hull-stripen.
|
||
**Verifisert:** rangeringslogikken for poeng-modus (synkende sortering,
|
||
delt plassering, uten-HCP sortert nederst) UAVHENGIG testet i Node
|
||
(10/10). Selve stableford-formelen verifisert mot en fersk scratch-runde
|
||
med HÅNDREGNET forventet resultat (course handicap 9 over 18 hull →
|
||
slag KUN på de 9 laveste stroke-index-hullene, ikke jevnt fordelt --
|
||
fanget en feilaktig antakelse i testens FØRSTE versjon, rettet før
|
||
den ble rapportert som bestått) -- 11/11, inkl. kryssjekk av
|
||
`total_points`/`net_score_to_par` mot uavhengig rekalkulering fra de
|
||
samme rå hull-dataene API-et returnerte. Full regresjon (35-punkts
|
||
co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12. Ekte
|
||
typesjekket produksjonsbuild kompilerte rent. Samme engangs `next
|
||
dev`-container-sjekk som tidligere -- alle tre rutene ga 200.
|
||
**Samme ærlige begrensning:** ingen ekte nettleser-interaksjonstest av
|
||
det faktiske "sømløse rader"-uttrykket eller poeng-modusen.
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen migrasjon, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Hullscorer i leaderboardet: netto/poeng under brutto, BYGGET, IKKE ENNÅ
|
||
RULLET UT (2026-07-26):** brukeren viste to referansebilder fra en
|
||
konkurrentapp (Golf GameBook) sin leaderboard-visning -- brutto fremhevet
|
||
(fylt/farget merke) på én rad, netto/stableford i vanlig tekst RETT under,
|
||
eksplisitt presisert at det skal se likt STRUKTURELT ut men IKKE være en
|
||
kopi visuelt. Ren frontend-endring, ingen backend-endring (all data --
|
||
`strokes_received` per hull -- var allerede lagt til forrige runde samme
|
||
dag).
|
||
**`HoleMark`** (`round-leaderboard.tsx`) fikk en tredje, umerket tekstlinje
|
||
RETT under det fremhevede brutto-merket, som viser netto eller
|
||
stableford-poeng for AKKURAT det hullet -- kun i netto-/poeng-modus (ingen
|
||
ny linje i brutto-modus, ingenting nytt å vise der). Nye lokale
|
||
`netForHole()`/`pointsForHole()`-hjelpefunksjoner (samme formel som
|
||
backend sin `total_points`, men per hull -- bruker `strokes_received`
|
||
direkte fra API-et, regner ALDRI ut egen slagfordeling). `HoleStrip` fikk
|
||
en liten "Slag · Netto"/"Slag · Poeng"-bildetekst over stripen når en
|
||
sekundærrad faktisk vises.
|
||
**Bevisst IKKE en kopi:** beholder TeeCups egne farger/former (sirkel/
|
||
firkant, grønn/oransje) fra `ScoreMark`-språket som allerede var etablert
|
||
-- endret ikke fargevalg for å etterligne referansebildets blåtoner, kun
|
||
gjenskapte det STRUKTURELLE prinsippet (fremhevet brutto øverst, rolig
|
||
sekundærtall under).
|
||
**Verifisert:** netto/poeng-per-hull-formelen testet UAVHENGIG i Node
|
||
(9/9, inkl. et gulv-på-0-tilfelle og manglende-HCP/uspilt-hull), samme
|
||
tall som det håndregnede scratch-scenarioet fra forrige runde samme dag
|
||
(hull med slag: netto 3/poeng 3, hull uten slag: netto 5/poeng 1). Ekte
|
||
typesjekket produksjonsbuild kompilerte rent. Samme engangs `next
|
||
dev`-container-sjekk som tidligere -- leaderboard-ruten ga 200.
|
||
**Samme ærlige begrensning som resten av de håndkodede rundene:** ingen
|
||
ekte nettleser-interaksjonstest av selve det visuelle uttrykket.
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen migrasjon, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Scoringsflyt: samlebånd-fremdrift + golf-term-taltastatur, BYGGET, IKKE
|
||
ENNÅ RULLET UT (2026-07-26), inspirert av en konkurrentapp (Golf
|
||
GameBook):** brukeren delte en skjermopptaksvideo av GameBooks
|
||
score-registrering og spurte om jeg forstod HVORFOR den flyten fungerer
|
||
bedre enn TeeCups, og om jeg kunne bygge det selv (ingen V0-credits
|
||
igjen). Video analysert bilde for bilde (ffmpeg i en engangs Docker-
|
||
container, samme mønster som en tidligere videoanalyse i prosjektet) --
|
||
identifiserte presist samlebånd-mønsteret: taltastatur med kontekstuelle
|
||
golf-termer per tall (Eagle/Birdie/Par/Bogey ut fra hullets par, ikke
|
||
bare "par"-knappen merket), og at fullført registrering for én spiller
|
||
automatisk åpner NESTE spillers registrering for samme hull -- uten å
|
||
måtte navigere manuelt tilbake til en spillerliste.
|
||
**Bevisst IKKE en full skjermovertakende steg-for-steg-modal** (GameBooks
|
||
egen løsning) -- vurdert som unødvendig risikofylt å bygge korrekt uten
|
||
visuell testing. I stedet: samme etablerte inline-side beholdt, men to
|
||
konkrete forbedringer lagt til:
|
||
1. `NumberPicker` sin Slag-instans fikk en ny `showGolfTerms`-modus --
|
||
HVERT synlig tall viser nå Albatross/Eagle/Birdie/Par/Bogey/Dobbel
|
||
bogey relativt til hullets par (ikke bare selve par-knappen som før).
|
||
Ny lokal `golfTermForScore()`-hjelpefunksjon.
|
||
2. Ny `isEntryComplete()`-sjekk (krever kun det `stat_level` faktisk gjør
|
||
obligatorisk -- slag alene, eller slag+putter; "full"-nivåets ekstra
|
||
detaljer forblir valgfrie og blokkerer ALDRI fremdrift) + ny
|
||
`advanceToNextPlayerOrHole()`. Bunnknappraden "Forrige/Neste hull"
|
||
endret til "Forrige hull" (uendret) + en kontekstsensitiv primærknapp
|
||
som enten viser "Neste: {navn på neste spiller}" (bytter aktiv
|
||
spiller på SAMME hull) eller "Neste hull" (er aktiv spiller den
|
||
siste, går videre til neste hull OG starter på spiller 1 igjen) --
|
||
disabled med forklarende hjelpetekst til de påkrevde feltene er fylt.
|
||
**Verifisert:** golf-term-tabellen og fullført-sjekken UAVHENGIG testet
|
||
i Node (matcher videoens egne eksempler nøyaktig, f.eks. par 4 + slag
|
||
6 = "Dobbel bogey"), samt selve samlebånds-syklusen simulert for 3
|
||
spillere over flere hull-grenser OG for en solo-runde (ingen spillerbytte,
|
||
kun hull-fremgang) -- 21/21 sjekker. Full regresjon (35-punkts
|
||
co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ingen
|
||
backend-endring denne runden). Ekte typesjekket produksjonsbuild
|
||
kompilerte rent, samme engangs `next dev`-container-sjekk som tidligere
|
||
ga 200.
|
||
**Samme ærlige begrensning som resten av de håndkodede rundene:** ingen
|
||
ekte nettleser-interaksjonstest av selve fremdrifts-følelsen (kun logikk
|
||
+ server-render bekreftet).
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen migrasjon, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Scoringsflyt v2: full skjermovertagende veiviser, BYGGET, IKKE ENNÅ
|
||
RULLET UT (2026-07-26) -- ERSTATTER forrige rundes forsøk.** Brukeren
|
||
testet forrige rundes "auto-fremdrift-knapp"-versjon live og var tydelig:
|
||
"ingen forbedring i det hele tatt", "visuelt like overveldende og
|
||
rotete" -- den inline-baserte tilnærmingen var IKKE nok, en ekte
|
||
skjermovertagende veiviser (som opprinnelig vurdert og lagt til side pga.
|
||
risiko uten visuell testing) var det som faktisk kreves. Bygget nå for
|
||
ekte, pluss et eksplisitt nytt krav: akkumulert score-så-langt for RUNDEN
|
||
synlig for HVER spiller samtidig (ikke bare aktiv), matchende
|
||
konkurrentappens vedvarende "E"/"+1"-visning ved siden av hvert navn.
|
||
**Datalasting endret** (nødvendig for punktet over): laster nå hull for
|
||
ALLE deltakere med det samme rundén lastes (ikke lenger lat lasting kun
|
||
for aktiv spiller), og sanntid-signalet (WS) henter nå alles hull på
|
||
nytt, ikke bare énes.
|
||
**Ny `ScoringWizard`-komponent** (fullskjerm, `fixed inset-0 z-50`, egen
|
||
stack utenfor `<main>`): tre steg maks, drevet av `stat_level` --
|
||
"strokes_only" (kun Slag), "strokes_and_putts" (+ Putter/avstand første
|
||
putt), "full" (+ ett samlet detalj-steg: kølle/utslag/innspill/chip/
|
||
bunker/straffeslag/anywayslag). Bevisst FÆRRE, grovere steg enn
|
||
konkurrentens egne 5-6 skjermer (risikoreduksjon uten visuell testing,
|
||
og TeeCups stat_level-modell gjør en så fin oppdeling mindre naturlig).
|
||
Alle spillerne vises som en fast, ikke-trykkbar kontekst-rad øverst i
|
||
veiviseren (aktiv fremhevet) -- speiler konkurrentappens "mist aldri
|
||
oversikten"-prinsipp. "Forrige"/"Neste" beveger seg gjennom stegene;
|
||
siste steg for siste spiller blir "Ferdig" (lukker + går til neste hull,
|
||
starter på spiller 1 igjen), ellers "Neste: {navn}" (bytter spiller i
|
||
SAMME veiviser, nullstiller til steg 1).
|
||
**Hovedsiden forenklet radikalt:** den gamle Slag/Putter/"flere
|
||
detaljer"-inline-blokken er FJERNET -- erstattet med én kompakt liste,
|
||
ett kort per spiller: navn, "HCP X · {til-par så langt} ({N} hull)", og
|
||
en stor rund knapp som viser gjeldende hulls slagtall (eller "–") og
|
||
åpner veiviseren ved trykk. `NumberPicker`s `showGolfTerms`-funksjon fra
|
||
forrige runde gjenbrukes uendret inni veiviserens Slag-steg (det arbeidet
|
||
var ikke bortkastet).
|
||
**Verifisert:** stegmaskinen (steg-antall per stat_level, fremover/
|
||
bakover-navigasjon, disabled-gating per steg, "avbryt på steg 1 lukker",
|
||
"siste steg for siste spiller fullfører") og akkumulert-score-
|
||
beregningen UAVHENGIG simulert i Node (16/16). Et ekte API-rundtur-
|
||
script som sender NØYAKTIG samme felt-kombinasjon som veiviseren ville
|
||
sendt (fullt detalj-steg for en "full"-spiller, kun slag for en
|
||
"strokes_only"-gjest) bekreftet begge lagres korrekt. Full regresjon
|
||
(35-punkts co-player-testsuite) fortsatt grønn, `test_isolation.sql`
|
||
12/12 (ingen migrasjon). Ekte typesjekket produksjonsbuild kompilerte
|
||
rent, samme engangs `next dev`-container-sjekk ga 200.
|
||
**Samme ærlige begrensning som alle håndkodede runder denne uken:** ingen
|
||
ekte nettleser-interaksjonstest -- gitt at FORRIGE runde ble avvist
|
||
nettopp fordi den så gal ut i praksis til tross for at logikken var
|
||
korrekt, er dette IKKE en ubetydelig forbehold denne gangen. Bruker bør
|
||
teste grundig før tillit.
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen migrasjon, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Scoringsflyt v3: administrasjon og scoring adskilt i to faner, BYGGET,
|
||
IKKE ENNÅ RULLET UT (2026-07-26) -- direkte svar på at bruker fortsatt
|
||
fant siden "bråkete og lite intuitiv" etter v2.** Brukeren viste et NYTT
|
||
sidestilt skjermbilde-par (samme mønster som første gang) og påpekte at
|
||
TeeCup fortsatt hadde mye synlig FØR selve scoringslisten (leaderboard-
|
||
forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten med Rediger-
|
||
ikoner, "+Medspiller") -- nøyaktig det jeg selv identifiserte som GameBooks
|
||
kjerneprinsipp i den aller første analysen ("administrasjon og scoring er
|
||
ADSKILTE tabs"), men aldri fullt ut gjennomførte i v2 (kun `ScoreSoFar`
|
||
ble flyttet forrige runde, resten av det administrative innholdet ble
|
||
stående igjen øverst).
|
||
**Fikset denne gangen for ekte:** ny `pageTab`-state (`"score" | "manage"`,
|
||
default `"score"`), en enkel fanevelger rett under feilmeldingen. "Score"
|
||
(default) inneholder nå KUN: fullført-banner (hvis relevant), hull-
|
||
navigasjon, og selve hull-panelet (header + scoringslisten fra v2 +
|
||
Forrige/Neste hull) -- ingenting annet. "Spillere og runde" samler
|
||
leaderboard-forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten
|
||
(`PlayerList`), OG `ScoreSoFar` (flyttet HIT fra forrige rundes
|
||
"under scoringslisten"-plassering, siden den er detaljert stats-innsyn,
|
||
ikke selve registreringsoppgaven).
|
||
**Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, samme
|
||
engangs `next dev`-container-sjekk ga 200, full regresjon (35-punkts
|
||
co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ren
|
||
frontend-omrokkering, ingen migrasjon).
|
||
**Samme ærlige begrensning som v1/v2:** ingen ekte nettleser-
|
||
interaksjonstest av selve fane-følelsen.
|
||
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
|
||
ingen migrasjon, `docker compose up -d --build teecup_frontend`
|
||
(gjenskapte også `teecup_api` som vanlig bivirkning). Begge containere
|
||
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
- **`DESIGN_SYSTEM.md` opprettet (2026-07-27):** brukeren ba om en ny fil
|
||
som viser designsystemet arbeidet tar utgangspunkt i. Skrevet fra bunnen,
|
||
DESKRIPTIVT (hva som ER i koden, ikke et mål) — grunnet direkte i
|
||
`globals.css`/`components/ui/*.tsx` og etablerte tvers-fil-mønstre:
|
||
fargetoken (inkl. OKLCH-opprinnelsen til `primary`/`brand-orange` fra
|
||
ADR-009/016), typografi, avstand/hjørner/lag (inkl. "én sammenhengende
|
||
liste med `divide-y`, ikke separate kort"-regelen fra 2026-07-26s
|
||
leaderboard-fiks), lokale komponentmønstre (NumberPicker/Stepper/
|
||
ChoiceRow/DirectionCross — bevisst per-fil, ikke delte importer),
|
||
golfscore-språket (`ScoreMark`/`ToParMark`/`PointsMark`/`HoleMark`),
|
||
ikonografi, tilbakemelding/tilstander, og navneformat (pekt til CLAUDE.md).
|
||
- **`teecup-scorekort-og-entry-spec.md` lest og evaluert, delvis BYGGET SOM
|
||
COMPLIANCE-PASS SAMME DAG (2026-07-27):** brukeren lastet opp et
|
||
PRESKRIPTIVT motstykke til DESIGN_SYSTEM.md (5-app-sammenligning: Golf
|
||
GameBook/Golf Pad/Golfshot/Hole 19/18Birdies) og spurte om det ga mening
|
||
og kunne forbedre TeeCup. Vurdert som reelt verdifullt — sylskarpeste nye
|
||
innsikt: "grønt-på-grønt"-diagnosen (§0.2) forklarer noe av "fortsatt
|
||
rotete"-følelsen fra v1-v3-rundene bedre enn tetthet alene: `primary`
|
||
(grønn) var brukt om hverandre for BÅDE "aktiv tilstand" og "generisk
|
||
fylt/positiv", uten at begge betydde det samme sted. Foreslo (og
|
||
brukeren bekreftet) å fikse spec-dokumentets §3 "Compliance-pass" FØR den
|
||
større strukturelle §1-omleggingen (scorekort som grid) vurderes som egen,
|
||
senere runde.
|
||
**To av sjekklistens fem punkter var KONKRETE, VERIFISERBARE bugs, bekreftet
|
||
direkte i koden (ikke antatt fra spec-teksten alene) FØR de ble fikset:**
|
||
1. **"Deg Deg"-duplikat:** `playerLabel()` (`round-detail.tsx`) erstattet
|
||
tidligere selve navnet med "Deg" for viewer-relativ egen rad, OG en
|
||
separat `<Badge>Deg</Badge>` sto ved siden av samme sted (spillerkort,
|
||
scoringslisten) — bekreftet duplikat, matchet et tidligere skjermbilde
|
||
brukeren delte. **Fikset:** `playerLabel()` returnerer nå alltid det
|
||
faktiske `display_name` (aldri "Deg") — Badge-en er nå ENESTE
|
||
selv-indikator. Dette retter samtidig et videre, ikke tidligere flagget
|
||
avvik: spec-dokumentet krever eksplisitt "roster-kontekst → fullt navn"
|
||
for BÅDE scorekort-gridet og score-entry-headeren — veiviserens header/
|
||
kontekst-rad/"Neste: {navn}"/fullført-banneret viste tidligere "Deg" i
|
||
stedet for et fullt navn der også, uten noen badge til å disambiguere
|
||
(reelt forvirrende på en delt telefon som sendes rundt en flight).
|
||
Det nå overflødige `rawName`-feltet (var identisk med `name` etter
|
||
fiksen) fjernet, tre kallsteder oppdatert.
|
||
2. **Score-knappen fulgte ikke `§Golfscore-språket`:** den store runde
|
||
knappen i den kompakte scoringslisten (`round-detail.tsx`, bygget i
|
||
v2-runden) var `rounded-full`+grønn UANSETT om resultatet var under,
|
||
over eller på par — bogey og eagle så identiske ut. **Fikset:** ny
|
||
`scoreMarkClasses(diff)`-hjelpefunksjon som gjenbruker EKSAKT samme
|
||
`primary`/`brand-orange`-fargespråk og fylt-vs-border+10%-tint-omfangs-
|
||
regel som `ScoreMark` i `round-scorecard.tsx` (bekreftet ved å lese
|
||
`ScoreMark` sin kildekode direkte, ikke gjettet) — sirkel under par,
|
||
nøytral sirkel på par, `rounded-2xl` (bevisst mildere enn scorekortets
|
||
`rounded-[4px]`, for å matche denne skjermens øvrige 56px-trykkflate-
|
||
avrunding) over par, fylt ved 2+ slag fra par.
|
||
**Resten av sjekklisten (44px-trykkgulv, `tabular-nums`) auditert
|
||
systematisk mot AKKURAT denne filen** (samme fil brukerens skjermbilder
|
||
viste) — 2 manglende `tabular-nums` (HCP/tildelte slag i spillerkortet)
|
||
og 11 knapper/lenker under 44px (fane-bryteren `min-h-10`→`min-h-11`,
|
||
`EditRoundPanel`s lukk-ikon `size-8`→`size-11`, feilside-tilbakelenken,
|
||
og åtte knapper i bane-bytte-/legg-til-medspiller-skjemaene) rettet.
|
||
**Bevisst UTENFOR omfang denne runden:** spec-dokumentets §1 (scorekort
|
||
som fullt grid, celle åpner veiviseren) — en STØRRE strukturell endring
|
||
som fortjener et eget, bevisst ja fra brukeren, ikke bygget stille inn i
|
||
en "fiks kjente bugs"-runde.
|
||
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile` som
|
||
deployes) kompilerte rent, alle 24 ruter listet. **Samme ærlige
|
||
begrensning som ALLE håndkodede runder denne uken:** ingen ekte
|
||
nettleser-interaksjonstest av det faktiske visuelle resultatet (kun kode-
|
||
lesing + build-verifisering + bevisst gjenbruk av en allerede lest,
|
||
eksisterende komponents fargespråk for å holde risikoen lav).
|
||
**Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
|
||
også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
|
||
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket.
|
||
- **Spec-dokumentets §1 (scorekort som fullt grid) BYGGET OG LIVE
|
||
(2026-07-27), samme dag rett etter compliance-passet:** brukeren
|
||
bekreftet eksplisitt "JA" på at grid-redesignet er en egen, bevisst
|
||
runde, egen informasjonsarkitektur enn ett-hull-om-gangen-listen
|
||
bygget dagen før. `round-detail.tsx` sin "Score"-fane fikk hull-strip
|
||
+ ett-hull-panel ERSTATTET av en ny `ScorecardGrid`: spillere som rader
|
||
(sticky venstre navnekolonne, navn+HCP, samme tap-target åpner
|
||
veiviseren for gjeldende hull), hull som horisontalt scrollbare
|
||
kolonner, `Hcp`/`Par`-referanserader over spillerradene (samme
|
||
konvensjon som den allerede shippede `round-scorecard.tsx`), sticky
|
||
`Ut`/`Inn`/`Sum` til høyre (Ut/Inn gruppert på FYSISK hullnummer 1-9/
|
||
10-18, uavhengig av øktens starthull — riktig konvensjon uansett
|
||
spillerekkefølge; kun vist for 18-hulls runder, 9-hulls runder får
|
||
én samlet Sum). Ny `ScorecardCell` gjenbruker EKSAKT samme klassifisering
|
||
og Tailwind-klasser som `ScoreMark`/`classify` i `round-scorecard.tsx`
|
||
(lest direkte, ikke gjettet) — ulikt compliance-passets softere
|
||
`rounded-2xl`-knapp-variant (fortsatt riktig der, egen visuell kontekst),
|
||
siden dette er en LITEN tabellcelle som spec eksplisitt ber om å følge
|
||
språket "UBRYTELIG".
|
||
**Interaksjon:** tapp en score-celle ELLER spillerens navnecelle ELLER
|
||
en hull-kolonneoverskrift åpner `ScoringWizard` (uendret komponent fra
|
||
v2-runden) for akkurat den (spiller, hull)-kombinasjonen — kolonne-
|
||
overskrift alene (uten å tappe en celle) setter kun "gjeldende hull"
|
||
uten å åpne veiviseren, samme jobb som den fjernede `HoleNav` gjorde.
|
||
Beholdt en kompakt "Forrige/Neste hull"-knapperad under gridet for
|
||
rask sekvensiell registrering uten bred scrolling.
|
||
**Dødt kode fjernet i samme runde:** `HoleNav`, `holeIsPlayed`,
|
||
GIR-merket/`showGir` (ga ikke lenger mening i en multi-hull-visning),
|
||
og OGSÅ compliance-passets `scoreMarkClasses`-hjelpefunksjon (var kun
|
||
brukt av den nå fjernede ett-hull-listens store runde knapp).
|
||
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
|
||
som deployes) kompilerte rent, alle 24 ruter listet. I TILLEGG en
|
||
engangs full runner-image bygget og kjørt i en isolert container (ikke
|
||
bare `--target builder`) — `/my-rounds/[id]` for en ukjent runde-id
|
||
ga 200, ingen React-feilgrense/krasj-markup i responsen. **Samme
|
||
ærlige begrensning som ALT håndkodet arbeid denne uken, men STØRRE
|
||
konsekvens denne gangen siden dette er en vesentlig strukturell endring
|
||
(ny informasjonsarkitektur), ikke en liten fiks:** ingen ekte
|
||
nettleser-interaksjonstest (scrolling, tapping av celler/hull-
|
||
overskrifter/navnerad, faktisk visuelt resultat av sticky-kolonnene)
|
||
er utført — flagget eksplisitt til bruker FØR utrulling, bruker bør
|
||
selv klikke seg grundig gjennom før full tillit.
|
||
**Rullet ut live 2026-07-27**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
|
||
også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
|
||
containere boot-et rent, `/health`/`/dashboard`/`/my-rounds` → 200,
|
||
`teeoff.no` upåvirket.
|
||
- **Reell produksjonsbug funnet OG fikset SAMME DAG via ekte nettleser-
|
||
testing (2026-07-27) — FØRSTE gang denne økten en Chrome DevTools MCP
|
||
har vært tilgjengelig.** Brukeren ba meg selv åpne gridet
|
||
(`localhost:3000/my-rounds/{id}`), logget meg inn (passord feilet først
|
||
— kontoen mangler passord/miljøet pekte annerledes; løst med en ekte
|
||
magic-link brukeren limte inn), og jeg tok et ekte skjermbilde av det
|
||
nettopp bygde scorekort-gridet på en mobil viewport (390×844) mot ekte
|
||
produksjonsdata (runden `c5db2e31-...`, 2 spillere, 4/18 hull spilt).
|
||
**Fant umiddelbart en alvorlig, reell rendering-bug** som ALDRI ble
|
||
fanget av typesjekking/container-boot-verifisering: `position: sticky`
|
||
på `<td>`/`<th>` inni en `<table>` med `border-collapse` rendret
|
||
fullstendig ødelagt i Chrome — de sticky `Ut`/`Inn`/`Sum`-kolonnene
|
||
overlappet/utvisket hull-kolonnene bak seg (synlige sammenblandede
|
||
siffer, f.eks. "436"/"472" der Par-radens tall lå oppå hverandre).
|
||
Nøyaktig den typen feil den gjentatte "ingen ekte nettleser-test
|
||
utført"-forbeholdet advarte mot hele uken.
|
||
**Første fiks-forsøk (kun `border-collapse` → `border-separate
|
||
border-spacing-0`) løste IKKE problemet** — re-skjermbilde etter
|
||
redeploy viste samme overlapp. **Rot-årsaken var strukturell, ikke
|
||
syntaktisk:** å kombinere en sticky VENSTRE-kolonne MED sticky HØYRE-
|
||
kolonner i en tabell som er mye bredere enn viewporten er i seg selv
|
||
et ustabilt mønster — ved scroll-posisjon 0 blir de sticky høyre-
|
||
kolonnene umiddelbart trukket til synlig høyre kant, og alt som
|
||
"egentlig" befinner seg der i normal dokument-flyt (hull 3+) blir
|
||
liggende RETT BAK dem, delvis synlig gjennom `bg-muted/60`s
|
||
delvise gjennomsiktighet. **Fikset ved å fjerne sticky-posisjonering
|
||
fra `Ut`/`Inn`/`Sum`-kolonnene helt** (de scroller nå med resten av
|
||
hullene i normal flyt, samme velprøvde, veletablerte mønster som den
|
||
gjenværende sticky VENSTRE navnekolonnen, som fungerte korrekt hele
|
||
tiden) — droppet også `/60`-gjennomsiktigheten til fordel for en helt
|
||
opak `bg-muted`.
|
||
**Verifisert presist, ekte, etter fiksen** (ikke bare "ser bedre ut"):
|
||
nytt skjermbilde ved scroll-posisjon 0 viste alle tall rene og lesbare
|
||
(Hcp/Par-radene, begge spilleres scoringsceller, korrekt sirkel/firkant-
|
||
form for bogey/dobbel bogey/par), OG et script som scrollet gridet helt
|
||
til høyre (`scrollLeft = scrollWidth`) bekreftet `Ut`/`Inn`/`Sum` også
|
||
rene der (`Ut 36/Inn 36/Sum 72` på Par-raden — stemmer eksakt med en
|
||
18-hulls par-72-bane), med navnekolonnen fortsatt korrekt pinnet til
|
||
venstre gjennom hele scrollingen. Tilgjengelighetstreet (`take_snapshot`)
|
||
bekreftet også at aria-labels med golftermer ("Bogey", "Dobbel bogey",
|
||
"Par") faktisk leses ut korrekt — `§Golfscore-språket`s "aldri farge
|
||
alene"-regel holder i praksis, ikke bare i teorien.
|
||
**Rullet ut live 2026-07-27**, samme dag: `docker compose up -d --build
|
||
teecup_frontend` kjørt to ganger (én for det mislykkede første forsøket,
|
||
én for den faktiske fiksen), `/health` 200 begge ganger, `teeoff.no`
|
||
upåvirket.
|
||
**Lærdom:** Chrome DevTools MCP-tilgangen endrer risikobildet for alt
|
||
fremtidig håndkodet frontend-arbeid denne økten — bruk den til å
|
||
FAKTISK se resultatet før noe rapporteres som ferdig, i stedet for kun
|
||
typesjekk+container-boot+`curl`-baserte proxyer for "det virker".
|
||
- **Full nettleser-gjennomgang av ALLE 22 skjermer, 2026-07-27 —
|
||
systematisk browsersjekk av alt som IKKE var reelt nettleser-testet
|
||
tidligere.** Brukeren spurte først hvilke visninger som faktisk finnes
|
||
(svart med en gruppert oversikt over `frontend/app/`s 22 `page.tsx`-
|
||
ruter), deretter ba om at ALLE de ikke-browsersjekkede skjermene faktisk
|
||
ble sjekket. Logget inn som `hei@erol.no` (spiller-konto, egne runder)
|
||
og — etter en egen runde med å spore opp riktig konto (magic-link fra
|
||
brukeren landet først på feil konto to ganger: `erol.haagenrud@gmail.com`
|
||
og kontoen manglet 2FA — satt opp TOTP for ekte ved å hente ut den rå
|
||
base32-secreten fra oppsett-skjermen og regne ut en gyldig 6-sifret kode
|
||
selv med et frittstående RFC 6238-script, ingen autentisator-app
|
||
involvert) — som `erol.haagenrud@envide.no` (org-eier for «Tjøme
|
||
Gents») for de org-/turnering-scopede skjermene. Gikk gjennom alle 22
|
||
ruter med ekte skjermbilder + konsoll-feil-sjekk (`list_console_
|
||
messages`) på hver.
|
||
**To reelle funn:**
|
||
1. Scorekort-gridet (§1, bygget dagen før) hadde EN NY sticky-kolonne-
|
||
overlapp-bug som IKKE fantes i utgangspunktet -- se eget punkt over,
|
||
fikset samme økt.
|
||
2. **Ny, ekte bug funnet i `round-scorecard.tsx`** (post-runde-
|
||
scorekortet, bygget i en tidligere økt) -- "til par" i BÅDE
|
||
header-hero-tallet og bunn-"TIL PAR"-brikken regnet
|
||
`totalGross - totalPar` der `totalPar` var summen av ALLE 18 hulls
|
||
par, ikke bare de faktisk spilte -- ga en absurd "−53 til par" for
|
||
en runde med 19 slag på kun 4 hull (skulle vært "+2"). Bekreftet
|
||
ved at `/my-rounds/[id]/stats` (en ANNEN komponent) viste riktig
|
||
"+2,00 til par" for SAMME runde, som isolerte feilen presist til
|
||
`round-scorecard.tsx`. Fikset med en ny `playedPar`-variabel
|
||
(paret for KUN spilte hull) brukt i de to til-par-utregningene --
|
||
"Par"-brikken nederst beholdt bevisst `totalPar` (hele rundens
|
||
par, riktig som statisk referanse). Bunn-"Par"/øvre "Hcp"/"Par"-
|
||
referanseradene i `ScoreBlock` ble sjekket og bekreftet IKKE
|
||
rammet (brukes kun til den statiske referansen, aldri til en
|
||
til-par-utregning).
|
||
**For å nå de org-/turnering-scopede skjermene: satt org «Tjøme
|
||
Gents» sin `public_profile`/`slug` MIDLERTIDIG (bekreftet med bruker
|
||
FØR endring) for å teste `/clubs/[slug]`, deretter revertert
|
||
eksplisitt til nøyaktig opprinnelig tilstand (`slug=null`,
|
||
`public_profile=false`) — bekreftet med en direkte databasespørring
|
||
etterpå at reverten var eksakt.
|
||
**Alle 22 skjermer bekreftet uten krasj/konsoll-feil** (utenom
|
||
forventede 401/403 på steder som SKAL avvise — feil passord-forsøk,
|
||
privat lagchat, ugyldig verify-token). Full liste med status i
|
||
chat-loggen denne runden.
|
||
**Rullet ut live 2026-07-27**, ingen migrasjon for til-par-fiksen,
|
||
`docker compose up -d --build teecup_frontend`, verifisert direkte i
|
||
nettleseren mot den samme runden som viste bugen (nå "+2 til par",
|
||
korrekt).
|
||
- **Dashboard-runde: bane-navn-fiks, to nye designfarger, bane-detaljvisning
|
||
og aggregert statistikk — BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE
|
||
(2026-07-28):** fem punkter reist av brukeren i samme runde, alle
|
||
bekreftet eksplisitt før bygging (AskUserQuestion) unntatt fargevalget,
|
||
der brukeren ba om at jeg selv "tok på designerbrillene" og foreslo.
|
||
1. **"Kommende runder" → "Runder"** på dashbordet (`dashboard.tsx`,
|
||
`UpcomingRounds`) — ren tekstendring, ingen annen forekomst i kodebasen.
|
||
2. **Tjøme-bane-duplikatet rettet i ekte `teecup_db`:** presist
|
||
diagnostisert FØR noe ble kjørt — alle tre av `hei@erol.no` sine
|
||
runder pekte til nøyaktig samme `teeoff_facility_slug`/
|
||
`teeoff_course_id` (`tjome-golfklubb`/140), kun `round.
|
||
course_name_snapshot`-TEKSTEN differerte (én runde fra FØR
|
||
enkeltbane-navnefiksen 25.07 hadde "Tjøme Golfklubb – Hovedbanen").
|
||
Ingen delt banetabell å slå sammen — frittstående runder har (bevisst,
|
||
ADR-033 Beslutning C) ingen egen `course`-rad, kun et navn-snapshot per
|
||
runde. Fiksen var én presist scopet `UPDATE round SET
|
||
course_name_snapshot = 'Tjøme Golfklubb' WHERE id = 'fa6e528f-...'`
|
||
(1 rad), kjørt etter eksplisitt bekreftelse, verifisert med en
|
||
read-only spørring rett etterpå.
|
||
3. **To nye kjernefarger** — brukeren ba eksplisitt om minst én, "kanskje
|
||
to", etter først å ha fått presentert (og avvist "kun én") en
|
||
tidligere anbefaling fra 25.07 om å løfte den allerede eksisterende
|
||
`--chart-3`-blåtonen. Løftet BEGGE allerede validerte statistikk-
|
||
fargene i stedet for å finne opp nye OKLCH-verdier: `--info`
|
||
(fra `--chart-3`, blå) og `--gold` (fra `--chart-4`, gul/gull) —
|
||
nye tokens i `globals.css` (`:root`/`.dark`/media-dark-blokken, alle
|
||
tre synkronisert som vanlig). Konkret, begrunnet førstebruk for
|
||
begge, ikke bare dekorativt: `--info` på et nytt "Hcp spilt til
|
||
X"-merke (før: ren grå tekst) på rundekort (`round-card.tsx`),
|
||
`--gold` på et nytt "Personlig rekord"-merke (`Medal`-ikon) som vises
|
||
når en fullført runde er brukerens laveste til-par blant MINST to
|
||
fullførte runder (unngår at den eneste fullførte runden feilaktig
|
||
kalles en "rekord"). Beregnet i `dashboard.tsx`/`own-rounds.tsx`/
|
||
`course-rounds.tsx` sine respektive `findPersonalBestRoundId()`.
|
||
4. **Bane-detaljvisning:** "Spilte baner" på dashbordet er nå klikkbar
|
||
(`PlayedCourses`, `dashboard.tsx`) — ny rute
|
||
`/my-rounds/course/[name]` (`components/course-rounds.tsx`), gjenbruker
|
||
eksisterende `GET /rounds` (ingen nytt backend-endepunkt), filtrerer
|
||
client-side på nøyaktig samme `course_name_snapshot`-nøkkel dashbordets
|
||
egen gruppering allerede bruker. "Personlig rekord" regnes likevel over
|
||
HELE rundelisten, ikke bare denne banens, for at merket skal bety det
|
||
samme uansett hvor et rundekort vises.
|
||
**Reell bug funnet OG fikset UNDER browserverifisering, ikke antatt
|
||
riktig fra kildekoden alene:** første versjon leste `params.name` rått
|
||
uten `decodeURIComponent` (matchet et eksisterende mønster i
|
||
`/clubs/[slug]/page.tsx` som aldri hadde blitt testet med et navn som
|
||
inneholder mellomrom/æøå) — et ekte skjermbilde viste tittelen som
|
||
`Tj%C3%B8me%20G...` og "0 runder funnet", siden matchen skjedde mot den
|
||
RÅ URL-kodede strengen. Rettet med et eksplisitt `decodeURIComponent`,
|
||
bekreftet med et nytt skjermbilde: riktig tittel og alle tre Tjøme-
|
||
rundene listet, inkl. både `--info`- og `--gold`-merkene rendret
|
||
korrekt.
|
||
5. **Aggregert statistikk, klikkbar fra dashbordet:** ny
|
||
`GET /rounds/stats/summary` (`app/routers/rounds.py`), ny side
|
||
`/my-rounds/stats` (`components/rounds-stats-summary.tsx`), lenket
|
||
fra dashbordets "Statistikk"-seksjon ("Se full statistikk"). Bruker
|
||
valgte det BREDESTE av tre foreslåtte omfang (utover kun runder/snitt-
|
||
til-par/putt: fairwaytreff, GIR, én-putt, scrambling, sand save,
|
||
snitt chip/bunker/straffeslag/anywayslag per runde) — portert fra de
|
||
allerede R&A/manuelt verifiserte formlene i `round-stats.tsx` sin
|
||
`computeStats()` for ÉN runde, generalisert til å pole alle kvalifiserte
|
||
hull på tvers av ALLE fullførte runder (ikke gjennomsnitt av per-runde-
|
||
prosenter, som ville vektet små utvalg feil).
|
||
**Putt/18-hull-regelen** (brukerens eksplisitte instruks, presist
|
||
bekreftet tolkning FØR bygging via AskUserQuestion): `round_hole` har
|
||
alltid nøyaktig 18 rader per deltaker uansett `holes_planned` (9 eller
|
||
18) — verifisert i `_create_participant`. For hver fullført runde der
|
||
puttsporing faktisk var på (`stat_level != 'strokes_only'`) telles
|
||
derfor alle 18 lagrede rader, et hull uten registrert putt-verdi
|
||
(uspilt, eller utenfor et 9-hulls spilleomfang) telles som 2 putter —
|
||
runder UTEN puttsporing holdes helt utenfor tallet (ellers ville alle
|
||
18 hull feilaktig blitt padded). Kun putt-tallet padder — alle andre
|
||
andelstall bruker KUN faktisk registrerte hull, som instruert.
|
||
**Verifisert i to lag:** (a) en frittstående Python-simulering av
|
||
nøyaktig samme aggregeringslogikk mot et hånd-konstruert 3-runde-
|
||
datasett (én `strokes_only`-runde padding-ekskludert, én
|
||
`strokes_and_putts`-runde med 9 av 18 hull padded) — alle hånd-regnede
|
||
forventninger stemte eksakt, inkl. det kritiske tilfellet (padded runde
|
||
ga nøyaktig 36 putt/18, strokes_only-runden talte 0 mot totalen); (b)
|
||
ekte typesjekket produksjonsbuild + import-sjekk av hele FastAPI-appen
|
||
i det faktiske prod-imaget (ikke bare syntaks) + et ekte browserbesøk
|
||
som viste reelle, korrekt utregnede tall for `hei@erol.no` sine 2
|
||
fullførte runder.
|
||
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt for
|
||
databaseskrivingen (punkt 2) og fargevalget (punkt 3, "to farger i stedet"
|
||
for anbefalt én); resten bygget direkte på brukerens egen presise
|
||
instruks. Ingen migrasjon. `docker compose up -d --build teecup_api
|
||
teecup_frontend` (kjørt to ganger — én gang for hovedleveransen, én gang
|
||
for `decodeURIComponent`-fiksen over). `/health`/`/dashboard`/
|
||
`/my-rounds/stats`/`/my-rounds/course/...` alle bekreftet 200 og
|
||
konsoll-feilfrie i en ekte innlogget nettleser-sesjon, `teeoff.no`
|
||
upåvirket.
|
||
- **Statistikk-siden utvidet: miss-retning, tidsvindu + forrige-periode-
|
||
sammenligning — BYGGET, TESTET OG LIVE (2026-07-28), samme dag, rett
|
||
etter forrige punkt:** brukeren reiste tre ting samtidig — ønsket
|
||
miss-RETNING (ikke bare treff%), trender, og et presist spørsmål om
|
||
hvorvidt GIR-fra-score-og-putt-inferens faktisk var forstått/utnyttet.
|
||
**Siste punkt avklart, ikke en bug:** bekreftet at `isGir = score - putts
|
||
<= par - 2` (allerede i `round-stats.tsx`, portert uendret til det nye
|
||
aggregerte endepunktet dagen før) ER akkurat denne generelle inferensen —
|
||
fungerer for ALLE score/putt/par-kombinasjoner, ikke bare brukerens
|
||
eksempel. Eneste reelle begrensning (forklart, ikke fikset): krever
|
||
putt-tall, så `strokes_only`-runder får aldri en GIR-verdi — score alene
|
||
er nesten aldri nok til å BEVISE GIR (en scrambling-birdie fra utenfor
|
||
green gir identisk score som en ekte GIR+1-putt).
|
||
**Miss-retning bygget:** `_summarize_rounds` i `app/routers/rounds.py`
|
||
utvidet med `fairway_left_pct`/`fairway_right_pct` (samme
|
||
`fairway_tracked`-pool som treff%) og `green_miss_long/short/left/
|
||
right_pct` (egen pool — hull der `approach_result` er registrert og ≠
|
||
"hit", UAVHENGIG av om putt er kjent, samme adskilte spor som
|
||
`round-stats.tsx` sin `missedGreen`/`missDir`). Ny `MissBar`-komponent i
|
||
`rounds-stats-summary.tsx` (fordelingsbar, samme visuelle idé som
|
||
`SegmentedBar`/`DistributionBar` andre steder i appen).
|
||
**Tidsvindu + forrige-periode bygget:** ny `StatsWindow`-type (`Literal`)
|
||
og `_resolve_stats_window()` — brukeren ba eksplisitt om BÅDE en full
|
||
velger (Siste runde/5/10/Denne måneden/I år/Siste år/Alltid) OG
|
||
sammenligningspiler mot forrige periode (utover det opprinnelig
|
||
anbefalte "kun siste 5 vs. alltid, ingen piler"). Én ENSARTET regel for
|
||
"forrige periode" på tvers av alle vindutyper — samme LENGDE (antall
|
||
runder for rullerende antall-vinduer, antall DAGER for dato-vinduer)
|
||
rett før gjeldende vindus start — i stedet for å måtte spesialdefinere
|
||
"forrige måned"/"forrige år" ulikt for hver kalenderbasert type.
|
||
`GET /rounds/stats/summary?window=...` returnerer nå `{window, current,
|
||
previous}` (`RoundStatsWindowSummary`) — samme `_summarize_rounds()`-
|
||
funksjon kalt to ganger på to ulike round_id-mengder, ingen duplisert
|
||
aggregeringslogikk. Frontend fikk en ny `Delta`-komponent (pil + farge,
|
||
farget etter en eksplisitt `goodDirection`-per-tall — lavere er bedre
|
||
for til-par/putt/chip/bunker/straffeslag/anywayslag, høyere er bedre for
|
||
treff-/rednings-prosentene, INGEN farge på de rene miss-retnings-tallene
|
||
siden venstre/høyre ikke er "bedre/verre").
|
||
**Verifisert i to lag:** (a) en frittstående Python-simulering av
|
||
`_resolve_stats_window()` mot syntetiske datasett — rullerende antalls-
|
||
vindu (riktig current/previous-splitt, riktig tomt `previous` når for få
|
||
runder finnes), dato-vindu (kalender-til-dato + rullerende forrige
|
||
periode), og en isolert miss-retning-poolingstest (`None`-verdier
|
||
korrekt ekskludert fra nevneren); (b) ekte typesjekket produksjonsbuild
|
||
(måtte rette en TypeScript-nullbarhets-feil underveis — `current`/
|
||
`previous` pakket ut via en IIFE inni JSX for at TS skulle smalne begge
|
||
riktig), full import-sjekk av hele FastAPI-appen i prod-imaget, OG et
|
||
ekte browserbesøk som viste reelle tall (fairway-miss 36/43/21,
|
||
green-miss-retning 0/75/13/13) + bekreftet vindu-velgeren fungerer
|
||
(klikket "Siste 10", pillen ble grønn, tallene oppdaterte seg) + et
|
||
direkte `fetch()`-kall fra siden selv som bekreftet `{window, current,
|
||
previous}`-formen (2 runder i `current`, korrekt tomt `previous` siden
|
||
brukeren kun har 2 fullførte runder totalt).
|
||
**Rullet ut live 2026-07-28**, ingen migrasjon, `docker compose up -d
|
||
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||
`/health` → 200, anonymt `GET /rounds/stats/summary?window=last_5` → 401
|
||
(bekrefter query-parameteren ruter riktig gjennom Caddy/rewrites),
|
||
`teeoff.no` upåvirket.
|
||
- **Statistikk-siden: visuell v2 (kompass-diagram + donuter), HÅNDKODET og
|
||
BROWSERVERIFISERT MED EKTE ITERASJON, LIVE (2026-07-28), samme dag:**
|
||
brukeren delte et referansebilde av en konkurrent-app sin statistikk-
|
||
skjerm (donuter med sentertall, kompass for miss-retning) og ba om noe
|
||
tilsvarende. Skrev først et fullt V0-prompt (data-kontrakt, komponent-
|
||
for-komponent) — brukeren var tom for V0-credits og spurte eksplisitt om
|
||
jeg kunne "gi promptet til meg selv" og bygge det direkte, siden Chrome
|
||
DevTools MCP nå gjør det mulig å faktisk SE resultatet underveis (ulikt
|
||
tidligere håndkodede runder denne uken som kun var typesjekket).
|
||
**Backend:** tre nye felt i `RoundStatsSummary`/`_summarize_rounds`
|
||
(`app/routers/rounds.py`) — `putt_dist_one_pct`/`two_pct`/
|
||
`three_plus_pct`, EGNE eksakte bøtter (`putts==1`/`==2`/`>=3`, samme som
|
||
`round-stats.tsx` sin `puttCategories`), bevisst forskjellig fra det
|
||
allerede eksisterende `one_putt_pct` (som bruker `<=1` — en annen,
|
||
allerede etablert rate, ikke en distribusjon).
|
||
**Frontend, `rounds-stats-summary.tsx` skrevet om betydelig:** ny
|
||
`GirCompass` — greentreff-prosenten i midten (grønn fremheving), fire
|
||
retningsceller rundt (Langt=topp, Kort=bunn, Venstre=venstre,
|
||
Høyre=høyre), samme visuelle språk som den allerede etablerte
|
||
`DirectionCross`/`DirButton` fra `round-detail.tsx` (plusstegn-rutenett,
|
||
`min-h-16 rounded-2xl border`-celler) — bevisst IKKE fargekodet
|
||
grønn/oransje på retningscellene (retning er beskrivende, ikke god/
|
||
dårlig), kun sentercellen. Ny gjenbrukbar `Donut`-komponent
|
||
(conic-gradient, sentertall + valgfri forklaringsliste via
|
||
`showLegend`) brukt til BÅDE puttfordelingen (3 segmenter: 1-putt/
|
||
2-putt/3-putt+) og to enkle rednings-gauger (scrambling/sand save, ett
|
||
segment). Bevisst IKKE et flyt-/beslutningstre-diagram for scrambling/
|
||
sand save (som referansebildet hadde) — det er to uavhengige prosenter i
|
||
datamodellen, ikke en forgrening, et flytdiagram ville antydet en
|
||
struktur som ikke finnes. Bevisst INGEN trendlinje (referansebildets
|
||
fjerde element) — for lite rundehistorikk til å vise noe meningsfullt
|
||
ennå, samme vurdering som tidligere samme dag.
|
||
**Reelt funn UNDER selve visuell iterasjon, ikke bare kodegjennomgang:**
|
||
første versjon av "Redning"-kortet viste samme tall TO GANGER per gauge
|
||
(`Donut` sin egen auto-genererte forklaringsliste "● Scrambling 9 %" RETT
|
||
OVER en egen, manuelt lagt til "Scrambling"-bildetekst) — sett direkte i
|
||
et ekte skjermbilde, ikke antatt. Fikset ved å legge til en
|
||
`showLegend`-prop på `Donut` og slå den av for de to gaugene, beholdt kun
|
||
min egen kompakte bildetekst+delta under ringen.
|
||
**Verifisert:** ekte typesjekket produksjonsbuild (to runder — én for
|
||
hovedversjonen, én for legend-fiksen), full backend-import-sjekk i
|
||
prod-imaget, OG ekte skjermbilder tatt FØR og ETTER legend-fiksen i en
|
||
innlogget nettleser-sesjon (samme mønster som sticky-kolonne-bug-fiksen
|
||
tidligere denne uken) — kompasset viser reelle tall (39 % greentreff,
|
||
75 % kort, 13/13 % venstre/høyre, 0 % langt for `hei@erol.no` sine 2
|
||
runder), puttfordelings-donuten viser korrekt fargede segmenter (17/61/
|
||
22 %), vindu-velgeren fungerer fortsatt uendret (klikket "Siste 5" etter
|
||
redesignet, ingen konsoll-feil). Ingen migrasjon.
|
||
**Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api
|
||
teecup_frontend` (kjørt to ganger). `/health` → 200 begge ganger,
|
||
`teeoff.no` upåvirket.
|
||
**Oppfølging, samme dag:** brukeren ba om at fairway-baren sin venstre/
|
||
høyre-bom bruker SAMME farge (i stedet for to ulike chart-farger) --
|
||
begge er tross alt bare "bom", ingen grunn til å skille dem visuelt.
|
||
Byttet begge til `--brand-orange` (appens etablerte "bom/over par"-farge),
|
||
beholdt grønn kun for selve fairwaytreffet. Verifisert med et nytt
|
||
skjermbilde. Rullet ut, ingen migrasjon.
|
||
- **"Til par: med vs. uten"-splitt, BYGGET, TESTET OG LIVE (2026-07-28),
|
||
samme dag:** brukeren ba eksplisitt om snitt-til-par splittet på om en
|
||
hull-hendelse inntraff eller ikke -- greentreff, fairwaytreff, bunker,
|
||
OG anywayslag (eksplisitt fremhevet). Fire nye par felt i
|
||
`RoundStatsSummary`/`_summarize_rounds` (`app/routers/rounds.py`):
|
||
`avg_to_par_with/without_gir`, `_fairway_hit/miss`, `_with/without_bunker`,
|
||
`_with/without_anyway` -- pooler ENKELTHULL på tvers av alle runder i
|
||
perioden (ikke per-runde-snitt, siden dette er hull-nivå-betingelser),
|
||
samme `diff()`-formel som `round-stats.tsx` sin `avgToParWithGir`/
|
||
`avgToParFairwayHit`/`avgToParWithBunker` bruker for én runde (portert
|
||
uendret) -- anywayslag-splitten er en ny, konsekvent utvidelse av
|
||
akkurat samme mønster (fantes ikke fra før for enkeltrunder heller).
|
||
Ny `CompareToPar`-komponent i `rounds-stats-summary.tsx`: to bokser side
|
||
om side, den med FAKTISK lavest til-par denne perioden fremheves grønn
|
||
-- ingen hardkodet antakelse om hvilken side som "skal" vinne (bekreftet
|
||
reelt i data: "Med anywayslag" var faktisk verre enn "Uten anywayslag"
|
||
som forventet, men "I bunker" var marginalt BEDRE enn "Ikke i bunker"
|
||
for denne brukerens 2 runder -- fremhevingen fulgte automatisk det
|
||
virkelige tallet, ikke en antakelse).
|
||
**Verifisert:** en frittstående Python-simulering av alle fire splittene
|
||
mot et hånd-konstruert 5-hulls datasett (eksakte forventede gjennomsnitt
|
||
regnet ut for hånd og sammenlignet), full backend-import-sjekk i
|
||
prod-imaget, ekte typesjekket build, OG et ekte skjermbilde i innlogget
|
||
nettleser som viste reelle, korrekt utregnede og korrekt fargede tall
|
||
for alle fire kategoriene. Ingen migrasjon.
|
||
**Rullet ut live 2026-07-28**, `docker compose up -d --build teecup_api
|
||
teecup_frontend`, `/health` → 200, `teeoff.no` upåvirket.
|
||
|
||
- **Gjennomgang av frittstående runder (ADR-033) + det viktigste hullet
|
||
lukket: faktisk (beregnet) HCP, BYGGET, SCRATCH-/BROWSERVERIFISERT OG
|
||
LIVE (2026-07-28, ADR-038):** brukeren ba om en vurdering av om alt var
|
||
tenkt gjennom for single-runder. Fant ved grep at hele WHS-indeksmotoren
|
||
(`handicap_index_from_differentials`/`low_handicap_index`/
|
||
`apply_index_caps` i `handicap_engine.py`, 41/41 testet siden ADR-033)
|
||
ALDRI ble kalt fra noe API-endepunkt — `round_participant.
|
||
score_differential` ble regnet og lagret per runde, men `app_user.
|
||
handicap_index` endret seg kun manuelt. Bekreftet eksplisitt i
|
||
`rounds.py` sin egen moduldoc ("skjer IKKE automatisk her -- eksplisitt
|
||
uavklart punkt i ADR-033"). Sekundære, mindre hull notert samtidig
|
||
(offline-kø kun for turnering-scorekortet, ikke frittstående runder;
|
||
ingen Stableford; ingen rundedeling/visibility) — brukeren valgte å ta
|
||
tak i HCP-hullet.
|
||
**To load-bærende design-avklaringer** (AskUserQuestion, se ADR-038):
|
||
(1) hver INNLOGGET deltaker (ikke bare eieren) styrer sin egen
|
||
eksklusjon fra faktisk HCP; (2) faktisk HCP designes NÅ for å kunne
|
||
inkludere begge kilder (frittstående runder OG en fremtidig turnering-
|
||
kilde), men v1 bygger kun runder-delen — løst med ett bevisst tynt,
|
||
navngitt skjøtepunkt (`_gather_qualifying_differentials`), IKKE en ny
|
||
generell "scoring record"-tabell (for tidlig abstraksjon).
|
||
**Ny migrasjon `030_actual_handicap_index.sql`:** `app_user.
|
||
computed_handicap_index`/`computed_handicap_index_updated_at` (den
|
||
faktiske, beregnede WHS-indeksen — ALDRI direkte redigerbar, kun
|
||
avledet), `round_participant.exclude_from_handicap` (manuell opt-out,
|
||
uavhengig av den automatiske `counts_for_handicap`-kvalifiseringen),
|
||
`round.play_format` (`stroke`/`match`, selvdeklarert — ingen egen
|
||
match-motor for frittstående runder), `handicap_history.source`
|
||
(`manual`/`computed` — gjenbruker EKSISTERENDE tabell fra ADR-031-
|
||
oppfølgingen i stedet for en parallell historikk-tabell, siden Low
|
||
Handicap Index/cap (Rule 5.7/5.8) trenger nøyaktig samme
|
||
dato+indeks-form som allerede fantes der).
|
||
**WHS-kilde lest og lagt til grunn** (`WHS_Rules_of_Handicapping_2024.
|
||
pdf`, Rule 3.3): matchspill-scorer ER teknisk et gyldig HCP-grunnlag
|
||
under WHS, MEN et konsedert/ikke-utspilt hull krever en subjektiv "most
|
||
likely score" TeeCups rene slagregistrering ikke har noen vei til å
|
||
representere presist — begrunner "spør (med anbefalt eksklusjon), ikke
|
||
tving"-designet i stedet for et hardkodet forbud mot matchspill i
|
||
HCP-grunnlaget.
|
||
**Backend (`app/routers/rounds.py`):** ny `_recompute_computed_
|
||
handicap_index(conn, user_id)` — henter de ≤20 nyeste kvalifiserende
|
||
differensialene, kaller den allerede-testede motoren, henter tidligere
|
||
`computed`-historikk for Low HI-cap (hopper bevisst over capping ved
|
||
FØRSTE beregning noensinne — Low HI er udefinert før en indeks er
|
||
etablert). Kalt fra `complete_round` (alle deltakere med `user_id`),
|
||
`update_participant` (eksklusjon endret på en ALLEREDE fullført runde),
|
||
`remove_guest_participant` (dekker faktisk enhver ikke-eier-fjerning,
|
||
til tross for navnet) og `delete_round` (fanger berørte brukere FØR
|
||
kaskade-slettingen fjerner radene). `update_participant` sin
|
||
autorisasjon utvidet presist: en ikke-eier kan KUN sende
|
||
`exclude_from_handicap`, KUN på sin egen rad (`_OWNER_ONLY_
|
||
PARTICIPANT_FIELDS`-sjekk + eksplisitt eier-eller-selv-gate) — alle
|
||
andre felt forblir strengt eier-only, uendret.
|
||
**Backend (`app/routers/auth.py`):** `Me` fikk `computed_handicap_
|
||
index`/`computed_handicap_index_updated_at`. Ny `POST /auth/profile/
|
||
handicap/apply-computed` — kopierer gjeldende beregnet verdi inn i det
|
||
manuelt satte HCP-et (samme skrivevei/historikk-logging som en vanlig
|
||
manuell PATCH, kun `source='manual'`). `GET /auth/profile/handicap-
|
||
history` eksponerer nå `source` også.
|
||
**Frontend:** `/my-rounds/new` fikk en "Spilleform"-bryter
|
||
(Slagspill/Matchspill) — velges Matchspill, forhåndsutfylles (ikke
|
||
tvinges) en eksklusjons-avkrysning med forklarende tekst.
|
||
`round-detail.tsx` fikk en "Matchspill"-badge i headeren, en
|
||
spilleform-bryter i `EditRoundPanel` (ren metadata, redigerbar uansett
|
||
fullført-status), og `EditParticipantPanel` fikk en ny `restricted`-
|
||
modus: en ikke-eier som redigerer SIN EGEN rad, ELLER EIEREN etter at
|
||
runden er fullført, ser KUN eksklusjons-toggelen (ikke tee/HCP/navn/
|
||
statistikk) — `PlayerList` sin "Rediger"-knapp vises nå også for en
|
||
ikke-eiers egen rad, ikke bare for eieren. `/account` fikk et nytt
|
||
"Faktisk HCP (beregnet)"-kort (verdi + sist-beregnet-dato +
|
||
"Bruk som mitt HCP →"-knapp), og HCP-historikk-listen viser nå
|
||
"beregnet"/"manuelt" per rad.
|
||
**Scratch-verifisert grundig, 111/111 sjekker** (isolert
|
||
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
||
container via ekte HTTP, samme mønster som hele prosjektet):
|
||
hånd-utregnet WHS-matte bekreftet PRESIST (tre runder med kjente
|
||
differensialer [10.0, 12.0, 14.0] ga nøyaktig 8.0 — beste-1-av-3 med
|
||
-2.0-justering fra Rule 5.2a-tabellen), <3 tellende runder gir fortsatt
|
||
`null` (ikke en feil), sletting av en tellende runde regner faktisk HCP
|
||
på nytt (tilbake til `null` under 3), matchspill-runde eksplisitt
|
||
ekskludert ved opprettelse telles korrekt IKKE med, "Bruk som mitt
|
||
HCP"-overføring bekreftet (+ `handicap_history` bærer nå begge kilder),
|
||
og hele autorisasjonsmatrisen for eksklusjons-feltet (medspiller nektes
|
||
andre felt og eierens rad, men kan endre EGEN eksklusjon; eieren kan
|
||
fortsatt overstyre medspillerens). `test_isolation.sql` 12/12 uendret.
|
||
**Reelt funn UNDER selve scratch-oppsettet, ikke i produksjon:** første
|
||
forsøk på en engangs API-container monterte kildekoden til feil sti
|
||
(`/app/app` i stedet for `/srv/app`, som er `Dockerfile` sin faktiske
|
||
`WORKDIR`) — containeren boot-et rent, men kjørte stille det GAMLE,
|
||
innbakte imagekoden uendret. Fanget FØR noe ble stolt på, ved at
|
||
`/auth/me` manglet det nye feltet helt i et faktisk API-svar — rettet
|
||
ved å montere til riktig `/srv`-sti, deretter bekreftet på nytt.
|
||
**Ekte nettleser-verifisert** (Chrome DevTools MCP, engangs `next dev`-
|
||
container mot scratch-backend — samme "sett resultatet, ikke bare
|
||
typesjekk det"-arbeidsmåte som resten av uken): logget inn via ekte
|
||
magic-link, opprettet en matchspill-runde → bekreftet
|
||
forhåndsutfylt-men-overstyrbar eksklusjonsavkrysning i skjemaet,
|
||
"Matchspill"-badge i rundens header, fullførte runden → bekreftet
|
||
`EditParticipantPanel` automatisk bytter til restriktert modus (kun
|
||
eksklusjons-toggel, ingen av de andre feltene), lagret en endring →
|
||
bekreftet ekte `PATCH .../participants/{id}` 200 i nettverksfanen.
|
||
Ekte typesjekket produksjonsbuild (alle 24 ruter) kjørt og bekreftet.
|
||
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: migrasjon
|
||
030 kjørt mot ekte `teecup_db` (alle fire nye kolonnesettene bekreftet,
|
||
`test_isolation.sql` fortsatt 12/12 mot ekte database), deretter
|
||
`docker compose up -d --build teecup_api teecup_frontend`. Begge
|
||
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket. Verifisert presist at den nye ruten faktisk når FastAPI:
|
||
anonymt `POST /auth/profile/handicap/apply-computed` over ekte https ga
|
||
korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.
|
||
**Bevisst utenfor omfang, notert i ADR-038:** turnering-/organisasjons-
|
||
scoring teller fortsatt ikke mot faktisk HCP (venter på videre
|
||
ADR-037-arbeid), ingen egen "aging"-bakgrunnsjobb utover at spørringen
|
||
alltid henter kun de 20 nyeste differensialene.
|
||
|
||
- **Ekte spillformer for frittstående runder (match/skins/fourball/
|
||
foursome/greensome/scramble), BACKEND BYGGET OG SCRATCH-VERIFISERT,
|
||
IKKE ENNÅ RULLET UT MOT EKTE SYSTEMER (2026-07-28, ADR-039):** reist av
|
||
brukeren rett etter ADR-038: "Er dette en slagspillsrunde, en match
|
||
mellom to spillere, skins, eller en par- eller lag-konkurranse.
|
||
Avhengig av svaret så må hcp beregnes forskjellig, og også scorekortet
|
||
vil se annerledes ut." Fire load-bærende avklaringer (AskUserQuestion,
|
||
to runder) FØR bygging — bruker valgte det bredeste omfanget i alle
|
||
runder: to sider (Beslutning A), ALLE fire par-/lag-underformater fra
|
||
start inkl. de to delt-ball-krevende (Beslutning B), skins med BEGGE
|
||
akser konfigurerbare av oppsetteren (netto/brutto OG rullerer/deles,
|
||
Beslutning D), og gå rett til migrasjon+motor samme økt (ikke bare
|
||
dokumentere).
|
||
**Kjerneinnsikt som gjorde dette trygt å bygge fort:** hele match-play-
|
||
motoren (`handicap_engine.py` sin `Format`/`AllowanceStrategy`-familie/
|
||
`match_play_strokes`/`compute_match_state`) og hele "delt-ball vs.
|
||
individuell"-mønsteret (`hole_score.match_participant_id` NULLABLE,
|
||
delt-ball-rader identifisert av `team_side` alene) fantes ALLEREDE,
|
||
bygget og produksjonskjørt for org-scopede turnering-matcher. Denne
|
||
runden PORTERER dette mønsteret til frittstående runder (nye
|
||
`round_side`/`round_participant.round_side_id`/`round_participant.
|
||
playing_handicap`/`round_hole.round_side_id`) i stedet for å finne opp
|
||
noe nytt — kun skins (ingen turnering-motstykke) fikk EKTE ny
|
||
motorkode (`compute_skins`, 7 nye tester, 50/50 i `test_handicap_
|
||
engine.py`). `app/handicap.py` sin `_SIDE_IS_UNIT` omdøpt til
|
||
`SIDE_IS_UNIT` (gjort delt for gjenbruk, samme "fjern understrek når
|
||
et andre bruksted dukker opp"-mønster som tidligere runder).
|
||
**Skjema, migrasjon `031_round_play_formats.sql`:** `round.play_format`
|
||
utvidet til 8 verdier, nye `round.skins_scoring`/`skins_tie_handling`,
|
||
ny `round_side`-tabell (nøyaktig to per runde, håndhevet i app-laget
|
||
som ADR-011s to-lags-grense), `round_participant.round_side_id`/
|
||
`playing_handicap` (sistnevnte ALDRI det samme som det eksisterende
|
||
`course_handicap_snapshot` — den absolutte WHS-verdien rørt av INGEN
|
||
av denne rundens kode), `round_hole.round_participant_id` gjort
|
||
NULLABLE + ny `round_hole.round_side_id` + XOR-CHECK + to partielle
|
||
unike indekser (samme mønster som org-scopet `hole_score`, migrasjon
|
||
001).
|
||
**Reelt, bekreftet funn UNDER selve designet (ikke antatt), avklart
|
||
eksplisitt med bruker FØR bygging:** foursome/greensome/scramble har
|
||
ÉN kombinert score per SIDE per hull -- INGEN individuell score
|
||
finnes i det hele tatt å bygge en Score Differential fra. Løst
|
||
(Beslutning E, bekreftet av bruker): disse deltakerne får
|
||
`counts_for_handicap` ALDRI sann for disse rundene -- og viste seg,
|
||
presist verifisert i scratch, å følge HELT AUTOMATISK av den
|
||
eksisterende `complete_round`-logikken uten noen kodeendring i det
|
||
hele tatt (en delt-ball-deltaker har null individuelle `round_hole`-
|
||
rader, så `played_count` blir alltid 0, som `round_counts_for_
|
||
handicap` allerede tolker som "teller ikke" for både 9- og
|
||
18-hulls-intensjon).
|
||
**API (`app/routers/rounds.py`):** `POST/DELETE .../sides` (eier-only,
|
||
maks to, avvist for slagspill/skins), `ParticipantCreate`/
|
||
`ParticipantUpdate` fikk `round_side_id` (eier-only reassignment,
|
||
validerer siden hører til samme runde), `_recompute_side_handicaps`
|
||
(porterer `compute_and_store_side_handicaps` — venter på at siden når
|
||
forventet spillerantall FØR den skriver noe, samme
|
||
"ikke komplett ennå = ikke skriv"-filosofi som originalen),
|
||
`_relative_strokes_for_round` (porterer `relative_strokes_for_match`),
|
||
nye `GET/PATCH .../sides/{id}/holes/{n}` (delt-ball-scoring, kun
|
||
slagtall — ingen av de andre detalj-feltene gir mening for en delt
|
||
ball), og et nytt lese-endepunkt `GET .../format-result` som regner
|
||
løpende matchstatus (gjenbruker `compute_match_state`/`HoleResult`
|
||
uendret) for de to-sidede formatene, eller en skins-tavle
|
||
(`compute_skins`) for skins — aldri lagret, alltid avledet ved lesing.
|
||
**To reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde
|
||
produksjon:** (1) side-tildelings-recompute kjørte FØR responsen ble
|
||
hentet i stedet for ETTER — testen fanget dette presist (forventet
|
||
`playing_handicap` i responsen, fikk `null` fra FØR omregningen); (2)
|
||
individuell-ball-gren i format-result-spørringen nøkkel-forvekslet
|
||
"enhet" (satte `round_side_id` som nøkkel i stedet for
|
||
`round_participant_id`, mens `side_net()`-oppslaget forventet
|
||
deltaker-id) — ga et tomt `hole_results` til tross for gyldige
|
||
registrerte scorer, fanget da `match_holes_played` kom ut som 0 i
|
||
stedet for det forventede 3.
|
||
**Scratch-verifisert grundig, 96/96 sjekker** (isolert
|
||
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
||
container via ekte HTTP, samme mønster som hele prosjektet): en
|
||
hånd-utregnet 3-hulls singles-match (hcp 5 vs. 10, riktig slagmottak
|
||
på de fem vanskeligste hullene) ga eksakt `lead=0`/"AS"/riktig
|
||
hole-for-hole-mønster; fourball bekreftet INDIVIDUELL (ikke kombinert)
|
||
90 %-beregning per spiller, korrekt "ikke komplett ennå" før andre
|
||
spiller på siden var tildelt; foursome bekreftet 18 round_hole-rader
|
||
opprettet på SIDEN ved side-opprettelse (før noen deltaker lagt til),
|
||
korrekt kombinert 50 %-Playing-Handicap først når begge var tildelt,
|
||
ekte delt-ball-scoring via det nye sub-endepunktet, OG at
|
||
`counts_for_handicap` ble `False` for begge etter fullføring; skins
|
||
(netto+carry) ga eksakt `{sk3: 2.0}` for et 2-hulls scenario med et
|
||
bevisst konstruert uavgjort-så-carry-så-outright-vinn-mønster, OG
|
||
bekreftet skins teller NORMALT mot faktisk HCP etter full fullføring
|
||
(ulikt match). `test_isolation.sql` 12/12 uendret (additiv migrasjon).
|
||
Alle 50 handicap_engine-tester (43 eksisterende + 7 nye skins) grønne.
|
||
**IKKE bygget i denne runden, bevisst utsatt (Beslutning F):**
|
||
frontend — ingen skjerm for å opprette sider, tildele deltakere,
|
||
konfigurere skins, eller vise løpende matchstatus/skins-tavle. Backend
|
||
er fullt funksjonelt og testet, men ubrukelig fra selve appen inntil
|
||
frontend bygges i en egen, senere runde (samme lagdelings-mønster som
|
||
ADR-038: motor/skjema/API FØR frontend).
|
||
**Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
|
||
eksplisitt: migrasjon 031 kjørt mot ekte `teecup_db` (nye kolonner/
|
||
`round_side`-tabell bekreftet, `test_isolation.sql` fortsatt 12/12),
|
||
deretter `docker compose up -d --build teecup_api`. Ren boot,
|
||
`/health`/`/dashboard` → 200, ny rute bekreftet nåbar (anonymt
|
||
`POST .../sides` → 401, ikke en rå 404), `teeoff.no` upåvirket.
|
||
|
||
- **Oppfølging samme dag: manglende minimums-spiller-håndhevelse fanget
|
||
og fikset, BYGGET OG SCRATCH-VERIFISERT, RULLET UT LIVE (2026-07-28):**
|
||
brukeren påpekte presist et hull ADR-039 selv ikke fanget opp: "Er det
|
||
match-spill, skins eller lagspill så MÅ det jo være flere spillere.
|
||
Dette fanges ikke opp." Riktig — ingenting hindret å fullføre en
|
||
"match" med kun eieren, eller la flere spillere enn formatet tillater
|
||
havne på samme side. Bekreftet med bruker (AskUserQuestion): skins
|
||
krever minst 3 spillere (2 gjør skins i praksis identisk med en vanlig
|
||
match — 3+ er der en oppsamlet, uavgjort pott faktisk gir mening).
|
||
**Bygget, ren Python-logikk, INGEN migrasjon:** ny
|
||
`_format_setup_status()` — slagspill alltid klar; skins krever minst
|
||
3 deltakere; to-sidede formater (match/fourball/foursome/greensome/
|
||
scramble) krever NØYAKTIG to sider, INGEN uassignerte deltakere, og
|
||
hver side nøyaktig riktig spillerantall for formatet
|
||
(`_SIDE_PLAYER_COUNT`, allerede definert). Ny `setup_complete`/
|
||
`setup_message` på `RoundOut` (alltid synlig, uansett format/status).
|
||
`complete_round` avviser nå (409 `SETUP_INCOMPLETE`) hvis oppsettet
|
||
ikke er komplett — FØR noe regnes ut. Ny `_check_side_capacity()`
|
||
avviser (409 `SIDE_FULL`) proaktivt ved SELVE tildelingen (både
|
||
`POST .../participants` med `round_side_id` og `PATCH .../
|
||
participants/{id}`), i stedet for å først oppdage overtallet ved
|
||
fullføring — med eksplisitt unntak for en no-op-reassignment til
|
||
samme side (en deltaker teller ikke seg selv ut av plassen sin egen
|
||
side har).
|
||
**Scratch-verifisert grundig, 122/122 sjekker** (samme isolerte
|
||
scratch-oppsett som resten av runden — full regresjon av alle
|
||
tidligere 96 sjekker PLUSS 26 nye): 3. spiller avvist på en full
|
||
match-/foursome-side (409 SIDE_FULL), `setup_complete` korrekt False
|
||
ved kun 1 av 2 sider / uassignerte deltakere / for få skins-spillere,
|
||
fullføring korrekt avvist (409 SETUP_INCOMPLETE) i alle disse
|
||
tilstandene, og korrekt True (+ vellykket fullføring) først når
|
||
oppsettet faktisk er komplett for formatet. `test_isolation.sql`
|
||
uendret (ingen skjemaendring).
|
||
**Rullet ut live 2026-07-28**, ingen migrasjon, kun
|
||
`docker compose up -d --build teecup_api`. Ren boot, `/health`/
|
||
`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
- **Frontend for ADR-039 (sider/skins/delt-ball-scoring) BYGGET, BROWSER-
|
||
VERIFISERT OG LIVE (2026-07-28), samme dag:** ingen backend-endring i
|
||
denne runden (alt allerede live) — ren frontend-jobb, testet reelt i
|
||
nettleser mot en isolert scratch-backend (Chrome DevTools), ikke bare
|
||
typesjekk.
|
||
**`new-round.tsx`:** spilleform-velgeren utvidet fra to (Slagspill/
|
||
Match) til alle åtte format (Skins/Fourball/Foursome/Greensome/
|
||
Scramble 2/4 lagt til), med en ny skins-konfigurasjonsseksjon (netto/
|
||
brutto, rullerer/deles) som kun vises for `play_format="skins"` og et
|
||
forklarende "du setter opp sidene inni runden etterpå"-notat for de
|
||
to-sidede formatene (ADR-039 Beslutning A -- sider kan ikke opprettes
|
||
før deltakerne finnes).
|
||
**`round-detail.tsx` (hoveddelen):** ny `SidesPanel` (manage-fanen) --
|
||
opprett/slett de to sidene, tildel/fjern deltakere (kompakte
|
||
"→ Side"-hurtigknapper, deaktivert når siden er full), viser
|
||
`playing_handicap` per side. Ny `FormatResultPanel` -- henter
|
||
`GET .../format-result`, viser løpende matchstatus (oversetter
|
||
motorens bokstavelige "(A)"/"(B)" til faktiske side-navn via
|
||
`round.sides[0]/[1]`, samme sorteringsrekkefølge som backend) eller en
|
||
skins-tavle (sortert synkende). `setup_complete`/`setup_message`
|
||
gater nå "Fullfør runde"-knappen klientside også (server er fortsatt
|
||
autoritativ). For delt-ball-formatene (foursome/greensome/scramble):
|
||
ny `SideScorecardGrid` (rader = sider, ikke spillere) + ny, forenklet
|
||
`SideScoreWizard` (kun slagtall, ingen putt/detalj-steg) mot de
|
||
eksisterende `GET/PATCH .../sides/{id}/holes/{n}`-endepunktene.
|
||
**To reelle stale-state-bugs funnet UNDER selve browserverifiseringen
|
||
(ikke i kodegjennomgang), begge fikset før utrulling:**
|
||
1. `SidesPanel` sin `assign()` oppdaterte kun deltaker-listen lokalt
|
||
(via `onPatchParticipant`) -- `setup_complete`/`setup_message`
|
||
(server-beregnet) ble stående utdatert etter en vellykket
|
||
side-tildeling ("ikke tildelt en side" fortsatte å vises til tross
|
||
for at begge var tildelt). Fikset: `assign()` kaller nå
|
||
`onSidesChanged()` (full runde-refetch) etter en vellykket PATCH.
|
||
2. `FormatResultPanel` sin refetch var kun koblet til hull-registrering
|
||
og WebSocket-signaler, ikke til side-/deltaker-tildeling --
|
||
matchstatus ble stående på "venter..." selv etter at oppsettet var
|
||
komplett og "Fullfør runde" allerede var aktivert. Fikset ved å
|
||
bumpe `formatResultRefreshTick` ved HVER vellykket `loadRound()`
|
||
(enklere og mer robust enn å spore hvert enkelt kallsted som kan
|
||
påvirke handicap-beregningen).
|
||
**Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
|
||
`teecup_scratch`-database, isolert scratch-MinIO, engangs API-
|
||
container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP --
|
||
ekte innlogging via magic-link, ekte profil-fullføring): tre komplette
|
||
runder bygget og spilt gjennom UI-et alene, ende-til-ende:
|
||
- **Match:** opprettet med eksklusjons-avkrysning forhåndshuket,
|
||
opprettet to sider, tildelte eier+gjest, bekreftet `playing_handicap`
|
||
(60/20) vist riktig, scoret hull 1 (4 mot 6) via `ScoringWizard`
|
||
(ubrørt komponent), bekreftet `FormatResultPanel` viste "1 UP
|
||
(Erol)" + riktig fargede hull-merker -- kryssjekket med
|
||
`aria-label`-attributtet direkte via `evaluate_script` for å bekrefte
|
||
semantisk korrekt side-navn bak den rå A/B-bokstaven.
|
||
- **Skins:** konfigurasjons-UI-et (netto/brutto, rullerer/deles) bekreftet
|
||
visuelt, opprettet med kun 1 spiller (satte-message "krever minst 3"),
|
||
la til to gjester til (meldingen forsvant idet den tredje ble lagt til,
|
||
"Fullfør runde" aktivert), scoret hull 1 for alle tre (via direkte
|
||
API-kall for hastighet, samme kontrakt som UI-et bruker), bekreftet
|
||
skins-tavlen -- **hånd-regnet og kryssjekket eksakt**: netto 1/4/3 for
|
||
de tre spillerne (course handicap 60/12/6, alle mottar 1 slag på
|
||
hull 1 unntatt eieren som mottar 4) ga korrekt "1 skin" til laveste
|
||
netto.
|
||
- **Foursome:** bekreftet "Opprett begge sidene..."-meldingen i Score-
|
||
fanen FØR sidene fantes (ingen krasj), opprettet to sider, la til tre
|
||
gjester, scoret hull 1 via `SideScorecardGrid`/`SideScoreWizard`
|
||
(5 mot 5 -- observerte LIVE at gridet oppdaterte seg bak selve
|
||
veiviseren), fant OG fikset de to stale-state-bugene over midt i
|
||
denne runden (glemte først å tildele spillerne til sider -- avdekket
|
||
nettopp fordi UI-et da IKKE viste feil tilstand, men en ekte utdatert
|
||
en), bekreftet til slutt `playing_handicap` kombinert riktig per side
|
||
(38/38 og 17/17) og at `FormatResultPanel` viste "1 UP (Rødt lag)"
|
||
-- kryssjekket for hånd at Rødt lag (høyere kombinert CH) mottar
|
||
slag på det vanskeligste hullet og derfor vinner nettoduellen 5 mot 5.
|
||
Ekte typesjekket + full produksjonsbuild kjørt på nytt ETTER
|
||
bug-fiksene (ikke bare før), alle 24 ruter listet.
|
||
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon (ren frontend), `docker compose up -d --build
|
||
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
|
||
ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
|
||
`/dashboard` → 200, `teeoff.no` upåvirket. **ADR-039 er dermed
|
||
fullstendig ferdig, backend og frontend, live.**
|
||
- **Auto-hopp i scoringsveiviserne, LIVE (2026-07-28), samme dag:**
|
||
brukeren ba om at "i det øyeblikket [scoren] nå registreres" skal
|
||
veiviseren hoppe videre av seg selv, uten å måtte trykke "Neste"/
|
||
"Ferdig". Presiserende avklaring FØR bygging (AskUserQuestion): "Avstand
|
||
første putt" lå tidligere PÅ SAMME steg som selve putt-tallet i
|
||
`ScoringWizard` -- auto-hopp idet putt-tallet velges ville gjort
|
||
avstandsfeltet uoppnåelig (ingen annen inngang finnes). Løst ved å
|
||
splitte putt-steget i to (bekreftet anbefalt løsning): `WizardStep`
|
||
utvidet med et eget `"puttDistance"`-steg, `wizardStepsFor()` gir nå
|
||
`["strokes","putts","puttDistance"]`/`["strokes","putts","puttDistance",
|
||
"details"]` for de to høyere statistikknivåene.
|
||
**Mekanisme (samme mønster i `ScoringWizard` og den enklere
|
||
`SideScoreWizard` for delt-ball-formater):** to refs -- `enteredWithValueRef`
|
||
fanger om steget sitt eget felt ALLEREDE hadde en verdi idet steget ble
|
||
vist (et allerede utfylt hull skal ikke hoppe videre bare fordi
|
||
veiviseren åpnes, og "Forrige" tilbake til et allerede besvart steg skal
|
||
ikke re-trigge et nytt hopp), `firedRef` hindrer dobbelt-triggering.
|
||
Kun steg med ETT entydig felt (Slag, Putter, Avstand, samt hele
|
||
`SideScoreWizard` sitt eneste Slag-felt) auto-hopper -- "flere
|
||
detaljer"-steget (kølle/retning/chip/bunker/straffeslag/anywayslag) har
|
||
ingen enkelt "dette er ferdig"-verdi og beholder derfor "Neste"/
|
||
"Ferdig"-knappen som manuell handling, bevisst uendret.
|
||
**Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
|
||
database/MinIO/API-container, ekte `next dev`, Chrome DevTools):
|
||
full "full"-nivå-runde spilt gjennom Slag→Putter→Avstand (alle tre
|
||
auto-hoppet uten et eneste "Neste"-trykk) →detaljer (korrekt IKKE
|
||
auto-hoppet, krevde et bevisst "Ferdig"-trykk, som deretter gikk videre
|
||
til neste hull av seg selv). "Forrige" fra Putter tilbake til Slag
|
||
bekreftet trygt (viste den allerede valgte verdien, hoppet IKKE
|
||
automatisk fremover igjen). Delt-ball (`SideScoreWizard`, foursome)
|
||
bekreftet separat: valgt slagtall for "Rødt" hoppet umiddelbart til
|
||
"Blått" uten trykk. Ekte typesjekket + full produksjonsbuild kjørt før
|
||
utrulling, alle 24 ruter listet.
|
||
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon (ren frontend), `docker compose up -d --build
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Scorekort for spillformater (match/skins/fourball/foursome/greensome/
|
||
scramble): to reelle bugs bekreftet og fikset, BYGGET, SCRATCH-/
|
||
BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om at scorekortene
|
||
faktisk sjekkes ved å simulere 3-4 spilte hull i hvert spillformat, med
|
||
et konkret forventningsbilde (en match bør vise hvem som vant hvilket
|
||
hull og HVORFOR -- brutto vs. netto, match-hcp). Simulert systematisk mot
|
||
en isolert scratch-backend (Python/urllib-testskript) FØR noe ble antatt
|
||
riktig -- fant to distinkte, bekreftede problemer, presentert til
|
||
brukeren som fikk velge omfang (AskUserQuestion) og valgte full løsning:
|
||
1. **Ekte blokkerende bug:** foursome/greensome/scramble (ADR-039,
|
||
delt-ball -- score lagres PER SIDE, `round_hole.round_participant_id`
|
||
settes ALDRI for disse) viste 0 spilte hull/ingen score på
|
||
`/my-rounds/[id]/scorecard`, `/leaderboard` OG `/stats`, uansett
|
||
faktisk fremdrift -- disse tre sidene spurte alle kun mot
|
||
deltaker-endepunktet (`round_participant_id`), som strukturelt aldri
|
||
kan ha data for disse formatene.
|
||
2. **Reell designmangel:** for match/fourball/skins var rå brutto/netto/
|
||
stableford riktig, men "hvem vant hvilket hull, og hvorfor" fantes
|
||
KUN i `FormatResultPanel` (manage-fanen i `round-detail.tsx`) som et
|
||
rent vinn/tap-merke -- ingen synlige tall (brutto vs. netto, slag
|
||
mottatt) noe sted, og ingenting av dette på de dedikerte Scorekort-/
|
||
Leaderboard-sidene brukeren faktisk testet.
|
||
**Backend:** ny `compute_skins_detail()` i `handicap_engine.py`
|
||
(hull-for-hull-forløp -- verdier/pott-før/tildelt/carried per hull),
|
||
`compute_skins()` omskrevet til en tynn wrapper rundt den (uendret
|
||
signatur/oppførsel, alle 50 eksisterende tester fortsatt grønne + 5 nye).
|
||
`GET /rounds/{id}/format-result` (ADR-039) utvidet med et nytt
|
||
`holes`-felt -- full hull-for-hull-oppløsning (brutto/netto/slag mottatt
|
||
PER enhet PER hull, pluss for fourball hvilken av de to partnernes netto
|
||
som faktisk talte for siden det hullet, R&A-regelen gjort synlig i
|
||
stedet for skjult). **Reell refactor-bug funnet OG fikset UNDER egen
|
||
scratch-verifisering, før noe ble stolt på:** en samlet `side_net()` for
|
||
BEGGE gren-typene (individuell-ball og delt-ball) brukte format-nivåets
|
||
`expected_players` (spiller-ANTALL, f.eks. 2 for foursome) som
|
||
fullstendighetssjekk også for delt-ball, der en side alltid er NØYAKTIG
|
||
ÉN enhet uansett spillerantall -- ga `match_holes_played=0`/tom
|
||
`holes`-liste for ALLE delt-ball-formater til tross for korrekt lagrede
|
||
side-scorer. Rettet med en egen `required_units`-variabel (1 for
|
||
delt-ball, `expected_players` for individuell-ball). `GET .../sides/
|
||
{id}/holes` fikk samtidig et nytt `strokes_received`-felt (samme
|
||
allokeringsalgoritme, nå basert på sidens kombinerte `playing_handicap`).
|
||
**Frontend:** `round-scorecard.tsx` bruker nå SIDER (ikke deltakere) som
|
||
"enhet" for delt-ball-formater -- samme visuelle `ScoreBlock`-tabell,
|
||
bare mot `/sides/{id}/holes`, med en forklarende melding hvis sidene
|
||
ikke er opprettet ennå. Ny `MatchProgressTable`-seksjon (to-sidede
|
||
formater: Hull/Par/Side A/Side B/Resultat, brutto→netto per enhet, ikke-
|
||
tellende fourball-partner tonet ned i stedet for fjernet) og
|
||
`SkinsProgressTable` (skins: hull-for-hull med hvem som vant/hvilket
|
||
hull som rullet videre, netto med brutto i parentes). `round-
|
||
leaderboard.tsx`: to-sidede formater viser nå en `MatchStatusSection`
|
||
(status + hull-merker + lenke til scorekortets fulle oppløsning) i
|
||
stedet for en individuell rangering som uansett ikke gir mening for et
|
||
1v1/lag-format (og alltid var tom for delt-ball) -- `RoundLeaderboardMini`
|
||
returnerer `null` for disse formatene i `round-detail.tsx` sin manage-
|
||
fane, siden `FormatResultPanel` allerede dekker akkurat det der. `round-
|
||
stats.tsx` viser en tydelig forklarende melding for delt-ball-formater
|
||
(individuell slag-for-slag-statistikk er strukturelt umulig der) i
|
||
stedet for en stille tom/misvisende side.
|
||
**Scratch-verifisert grundig, flere lag:** isolert `teecup_scratch`-
|
||
database + `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
|
||
API-container (samme mønster som hele prosjektet), 55/55
|
||
`handicap_engine`-tester, et Python/urllib-simuleringsskript som spilte
|
||
4 hull i alle 6 ikke-trivielle formater og sammenlignet rå API-svar før/
|
||
etter fiksen. **Deretter en FULL, ekte nettleser-gjennomgang** (Chrome
|
||
DevTools MCP, engangs `next dev` mot scratch-backend, ekte innlogging):
|
||
opprettet alle 7 formatene (inkl. `stroke` som regresjonssjekk) via
|
||
ekte API-kall fra en innlogget nettleserøkt, besøkte deretter
|
||
scorekort/leaderboard/stats-sidene for hver -- foursome sitt tidligere
|
||
BLANKE scorekort viste nå korrekte side-tabs + reelle score + riktig
|
||
`Matchforløp`-tabell; fourball sin `Matchforløp` viste presist BEGGE
|
||
partnernes brutto/netto med den ikke-tellende partneren korrekt tonet
|
||
ned; skins sin hull-for-hull-tabell viste riktig vinner-navn og
|
||
"Uavgjort — rullet videre" nøyaktig der forventet; leaderboardets
|
||
matchstatus stemte hull-for-hull med scorekortets egen utregning (bevisst
|
||
kryssjekket for hånd); fanebytte mellom sider (Side A/Side B) bekreftet
|
||
å faktisk refetche og re-rendre riktig data. Ekte typesjekket +
|
||
produksjonsbuild (alle 24 ruter) kjørt både før og etter refactor-bug-
|
||
fiksen. `test_isolation.sql` uendret (ingen migrasjon, ren kode-endring).
|
||
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
|
||
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket.
|
||
- **Offline-kø (ADR-028) utvidet til frittstående runder, BYGGET,
|
||
BROWSERVERIFISERT OG LIVE (2026-07-28):** brukeren ba om det som ble
|
||
identifisert som viktigste gjenstående hull etter forrige runde --
|
||
ADR-028s offline-skrivekø var kun koblet til turnering-scorekortet
|
||
(`session-scorecard.tsx`), IKKE frittstående runder
|
||
(`round-detail.tsx`), nettopp der man oftest står alene ute på banen
|
||
uten dekning, og nå som frittstående runder er bekreftet appens
|
||
hovedfokus var dette et reelt hull i kjerneflyten.
|
||
**Enklere port enn originalen, ikke bare en kopi:** turnering-
|
||
scorekortets PATCH-endepunkter er en append-historie (krever egne
|
||
`pendingStrokes`/`pendingResults`-verdi-overlays for å vise queued
|
||
verdier). Frittstående runders `PATCH .../participants/{id}/holes/{n}`
|
||
og `PATCH .../sides/{id}/holes/{n}` erstatter derimot HELE hull-raden
|
||
per kall, og `statToPatchBody()` sine feltnavn matcher `ApiHole` 1:1 --
|
||
en køet skriving kan dermed speiles direkte inn i
|
||
`holesByParticipant`/`holesBySide` med en enkel `{...h, ...body}`-spread,
|
||
ingen egen verdi-overlay nødvendig. Kun to lette `Set<string>` (nøkkel
|
||
`id:hullnummer`) beholdt, utelukkende til selve "lagret lokalt"-
|
||
indikatoren i veiviseren.
|
||
Samme kø/synk-mønster som originalen ellers: `navigator.onLine`-sjekk
|
||
FØR forsøk, `try/catch` rundt selve fetch-kallet som queuer ved en EKTE
|
||
nettverksfeil (ikke ved et avvist HTTP-svar -- det vises fortsatt som
|
||
vanlig feiltekst), auto-synk ved `window`s `online`-event, manuell
|
||
"Synkroniser nå"-knapp, og en engangs-sjekk for allerede køede
|
||
skrivinger fra en TIDLIGERE økt (f.eks. siden ble lukket mens offline)
|
||
ved mount. `flushPending()` matcher URL-mønsteret på hver synkronisert
|
||
kø-oppføring (`/participants/{id}/holes/{n}` vs. `/sides/{id}/holes/
|
||
{n}`) for å vite hvilke deltakere/sider som trenger en ekte refetch
|
||
etterpå (reconciles bl.a. `strokes_received`, som den optimistiske
|
||
speilingen ikke kan regne ut selv). Banner ("Du er offline"/"N
|
||
endringer venter") lagt til rett under fane-velgeren, synlig uansett
|
||
fane. "Lagret lokalt · venter på synk"-indikator lagt til i BÅDE
|
||
`ScoringWizard` (vanlig scoring) og `SideScoreWizard` (delt-ball).
|
||
**Browserverifisert grundig, IKKE bare kodegjennomgang/build denne
|
||
gangen** (i motsetning til den opprinnelige ADR-028-runden, som
|
||
brukeren selv måtte teste manuelt siden intet nettleserverktøy var
|
||
tilgjengelig da) -- Chrome DevTools MCP sin ekte nettverks-emulering
|
||
(`Offline`, bekreftet at `navigator.onLine` faktisk flippet til
|
||
`false`) mot en isolert scratch-backend: registrerte slag+putter
|
||
offline for en vanlig runde -- bekreftet 2 kø-oppføringer i ekte
|
||
IndexedDB, optimistisk oppdatert scorekort-grid, "1 endring venter"-
|
||
banner, "Lagret lokalt"-indikator i veiviseren; koblet til nett igjen
|
||
-- bekreftet AUTOMATISK synk (ingen manuelt trykk), kø tom etterpå, OG
|
||
et direkte API-kall som bekreftet serveren faktisk hadde de riktige
|
||
verdiene (score=5, putts=1, strokes_received=1). Gjentok hele syklusen
|
||
for en ny foursome-runde via `SideScoreWizard` (delt-ball) -- samme
|
||
resultat, kø tom og server bekreftet score=4 for siden etterpå. Ingen
|
||
konsollfeil i noen av rundene. Ekte typesjekket + full produksjonsbuild
|
||
(alle 24 ruter) kjørt før utrulling.
|
||
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon (ren frontend), `docker compose up -d --build
|
||
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
||
→ 200, `teeoff.no` upåvirket.
|
||
- **Turnering-scorekortets offline-flyt (den OPPRINNELIGE ADR-028, session-
|
||
scorecard.tsx) FAKTISK browserverifisert, samme dag (2026-07-28):**
|
||
eneste reelt gjenstående punkt fra forrige runde — offline-koden i
|
||
turnering-scorekortet er over ett år gammel (funksjonelt), men var ALDRI
|
||
browser-testet, kun kodegjennomgang/build (se opprinnelig ADR-028-notat).
|
||
Bygget en full isolert scratch-turnering fra bunnen via API for å nå
|
||
frem til selve scorekortet (organisasjon → turnering → to lag → to
|
||
spillere rostret som kapteiner → egendefinert bane med 18 hull + tee →
|
||
økt (`format=singles`, `scoring_mode=stroke`) → match → to
|
||
match-deltakere → begge lag låst) -- ingen slik full turnering-scaffold
|
||
fantes fra før i noe testskript denne uken (frittstående runder trenger
|
||
ikke dette apparatet i det hele tatt, ADR-033 Beslutning A), måtte bygges
|
||
fra grunnen ved å lese `tournaments.py`/`matches.py`/`courses.py` sine
|
||
faktiske endepunkt-kontrakter direkte.
|
||
**Verifisert identisk mønster som frittstående runder samme dag:** ekte
|
||
DevTools-nettverksemulering (`navigator.onLine` bekreftet `false`),
|
||
registrerte et slag (5, Spiller B, hull 1) offline -- bekreftet ÉN
|
||
kø-oppføring i ekte IndexedDB (`POST .../hole-scores`), "1 endring
|
||
venter"-banner, "Lagret lokalt · venter på synk"-tekst under
|
||
tallvelgeren, hull-navigasjonens sjekkmerke. Koblet til nett igjen --
|
||
bekreftet AUTOMATISK synk (ingen manuelt trykk på "Synkroniser nå"
|
||
nødvendig), kø tom etterpå, "Bogey"-etiketten dukket opp (bevis på at
|
||
`refetchScorecard()` faktisk hentet det avledede resultatet fra
|
||
serveren), OG et direkte API-kall mot `.../scorecard` som bekreftet
|
||
serveren faktisk hadde `gross_strokes=5` lagret. Ingen konsollfeil.
|
||
**Ingen kodeendring i denne runden** — ren verifisering av allerede
|
||
levert funksjonalitet, ingen utrulling nødvendig.
|
||
- **Undersøkt: grensesnitt for å følge en venns runde live — BEKREFTET AT
|
||
DET IKKE FINNES (2026-07-28), ren undersøkelse, ingen kode skrevet.**
|
||
Brukeren spurte om en spiller kan gå inn på en venns profil og følge en
|
||
pågående frittstående runde live, gitt at rettighetene er gitt. Lest
|
||
direkte i koden (ikke antatt): (1) `frontend/components/friends.tsx` har
|
||
INGEN lenke/rute til en vennprofil-side i det hele tatt — kun
|
||
send/aksepter/avvis-knapper og kategorisering, ingen `/friends/[id]`-
|
||
eller lignende rute finnes noe sted i `frontend/app/`. (2) `round`-
|
||
tabellen har INGEN `visibility`-kolonne (bekreftet med grep over ALLE
|
||
migrasjoner — `visibility` finnes kun på `tournament`, fra
|
||
`009_landing_pages_and_visibility.sql`); `020_personal_rounds.sql` sin
|
||
egen kommentar (linje 39-42) slår eksplisitt fast v1-avgrensningen: en
|
||
runde er kun synlig for `owner_user_id`. (3) `app/routers/rounds.py` sin
|
||
`_get_accessible_round_or_404` (linje 1220-1240, lest direkte) gir
|
||
tilgang KUN til eieren ELLER en lenket `round_participant.user_id`
|
||
(ADR-036 fase 3) — ingen sjekk mot `friendship`-tabellen noe sted,
|
||
bekreftet med `grep -n -i "friend" app/routers/rounds.py` → null treff.
|
||
(4) `GET /ws/rounds/{id}/live` bruker NØYAKTIG samme
|
||
`_get_accessible_round_or_404`-sjekk FØR `websocket.accept()` (samme
|
||
fil, linje ~2763) — en venn som ikke er lenket deltaker får
|
||
websocketen lukket med kode 4403, ulikt turnering-live (ADR-027), som
|
||
bevisst TILLATER anonym tilgang. Ingen forberedt/påbegynt kode for
|
||
"følg venns runde" funnet noe sted (grep etter "follow"/"følg"/"friend"
|
||
i både `rounds.py` og `friends.py` — null relevante treff).
|
||
**Konklusjon: dette er nøyaktig ADR-036 fase 2 (rundevisibilitet
|
||
public/private/friends), som lenge har stått notert som IKKE bygget i
|
||
FEATURE_BACKLOG.md/CLAUDE.md** — bekreftet nå med presis kode-evidens i
|
||
stedet for bare et notat om at det mangler. Ingen ny beslutning tatt,
|
||
ingen kode skrevet — dette var en ren undersøkelse på brukerens
|
||
eksplisitte forespørsel. Se FEATURE_BACKLOG.md for ADR-036 fase 2 sitt
|
||
design (public/private/friends, eksplisitt gruppevalg).
|
||
- **ADR-036 fase 2 (rundevisibilitet) BYGGET, GRUNDIG SCRATCH-/
|
||
BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging
|
||
av undersøkelsen over — brukeren bekreftet eksplisitt retningen fra
|
||
Beslutning B (allerede fullt designet i ADR-036, ikke funnet opp på
|
||
nytt): tre nivåer (`public`/`private`/`friends`), der `friends` krever
|
||
et EKSPLISITT kategori-valg (ikke "alle venner").
|
||
**Migrasjon `032_round_visibility.sql`:** `round.visibility_mode`
|
||
(`public`/`private`/`friends`, default `private`), ny tabell
|
||
`round_visible_category` (samme faste kategori-sett som
|
||
`friend_categorization`, migrasjon 025, bevisst duplisert CHECK fremfor
|
||
delt ENUM — samme pragmatiske mønster som resten av skjemaet).
|
||
**Kjernestykket, `app/routers/rounds.py`:** ny `_can_view_round()` —
|
||
eier ELLER lenket medspiller ser alltid; ellers `public`→alle (også
|
||
anonyme), `private`→ingen, `friends`→krever et AKSEPTERT vennskap MED
|
||
eieren OG at EIERENS kategorisering av viewer (retningen er bevisst
|
||
omvendt av hva man skulle tro — det er eieren som begrenser, basert på
|
||
egen gruppering) treffer minst én av rundens synlige kategorier.
|
||
**Reelt funn under selve designarbeidet:** `_can_view_round()` viste
|
||
seg å være en STRIKT SUPERSETT av den eksisterende
|
||
`_get_accessible_round_or_404()` sin logikk (dens to første grener ER
|
||
nøyaktig eier+medspiller-sjekken) — i stedet for å bygge en helt
|
||
parallell endepunkt-familie fra bunnen, ble fire eksisterende
|
||
autentiserte lese-endepunkters KROPP ekstrahert til delte
|
||
hjelpefunksjoner (`_build_participant_holes`/`_build_side_holes`/
|
||
`_build_leaderboard`/`_build_format_result` — mekanisk gjort med et
|
||
Python-script for presis inndenting fremfor manuell redigering av
|
||
~270+95 linjer, verifisert med `ast.parse` + full regresjonskjøring
|
||
etterpå), gjenbrukt av BÅDE de originale autentiserte endepunktene OG
|
||
syv nye `/public/rounds/*`-endepunkter (samme `get_current_user_
|
||
optional`-mønster som turnering sin offentlige side, ADR-018/026/027)
|
||
pluss et nytt offentlig WS-endepunkt `/ws/public/rounds/{id}/live`
|
||
(ulikt det eksisterende PRIVATE `/ws/rounds/{id}/live`, som fortsatt
|
||
krever ekte autentisert eier/medspiller-sesjon, uendret). Egen, leaner
|
||
`PublicRoundOut` (aldri `guest_email`, som er PII, aldri `my_*`/
|
||
`setup_*`, som kun gir mening for eier/deltaker) i stedet for å
|
||
gjenbruke den fulle `RoundOut` med etterhånds-redigering.
|
||
Ny `GET /people/{id}` (friends.py, samme lavsensitive felt-sett som
|
||
`/people/search`, bevisst OPTIONALT autentisert — en offentlig runde
|
||
må kunne nås via en delt lenke selv av en anonym leser, og
|
||
vennprofil-siden som leder dit må da fungere anonymt også) og
|
||
`GET /public/people/{id}/rounds` (rounds.py, lister eierens runder
|
||
filtrert gjennom `_can_view_round()` per rad — "pågår nå" alltid øverst).
|
||
**Scratch-verifisert grundig, 191 automatiserte sjekker i tre testløp**
|
||
(isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
|
||
API-container, samme mønster som hele prosjektet): et NYTT 34-punkts
|
||
skript som dekket hele synlighetsmatrisen presist (tre brukere A/B/C +
|
||
en helt anonym opener) — B/C/anonym nektet privat OG venner-runde FØR
|
||
vennskap, alle ser offentlig (inkl. anonym), venner-runde fortsatt
|
||
nektet RETT ETTER vennskap men FØR kategorisering, tilgjengelig
|
||
UMIDDELBART etter riktig kategorisering, feil kategori (close_family
|
||
mot golf_friends) fortsatt nektet, PATCH kan endre synlighet i
|
||
etterkant (bekreftet begge retninger), offentlig+autentisert
|
||
leaderboard ga BEVIST IDENTISK resultat (kryssjekket), ukjent
|
||
runde-id ga 404 (ikke 403). PLUSS en full regresjonskjøring av to
|
||
eksisterende testsuiter fra tidligere runder denne uken (35-punkts
|
||
co-player-flyt, 122-punkts spillformat-flyt) — begge 100 % grønne,
|
||
bekrefter at ekstraheringen av de fire delte funksjonene ikke endret
|
||
noen eksisterende oppførsel.
|
||
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
|
||
isolert browser-kontekst for en helt anonym tredje "bruker" ved siden
|
||
av to ekte innloggede faner): (1) synlighetsvelgeren i opprett-runde-
|
||
veiviseren fungerte som designet (tre knapper, kategori-multiselect
|
||
dukker kun opp ved "Venner", advarselstekst ved 0 valgte kategorier) —
|
||
runde opprettet med `friends`+`golf_friends`, bekreftet via API;
|
||
(2) rediger-runde-panelet viste korrekt FORHÅNDSUTFYLT synlighet,
|
||
PATCH til `public` bekreftet lagret; (3) en HELT ANONYM
|
||
nettleserkontekst (ingen cookies i det hele tatt) kunne se den
|
||
offentlige runden direkte på `/watch/{id}` (live-puls, leaderboard,
|
||
riktig eiernavn) OG via `/my-friends/{eier-id}`-profilsiden (navn/
|
||
avatar/HCP/hjemmeklubb + rundeliste med lenke inn); (4) en ekte andre
|
||
bruker sendte venneforespørsel, eieren aksepterte og kategoriserte
|
||
vedkommende som "Golfvenner" VIA DEN FAKTISKE UI-EN (ikke bare API) —
|
||
satte deretter runden til `friends`+`golf_friends`, bekreftet vennen
|
||
fikk tilgang UMIDDELBART via profilsiden; (5) **negativ kontroll,
|
||
samme venn**: byttet runden til `friends`+`close_family` (en kategori
|
||
vennen IKKE var satt i) — profilsiden viste korrekt INGEN runder
|
||
lenger, og et direkte `/watch/{id}`-forsøk ga en tydelig "Du har ikke
|
||
tilgang"-melding (403), atskilt fra en egen "Denne runden finnes
|
||
ikke"-melding for en ukjent id (404) — begge bekreftet med ekte
|
||
skjermbilder. Ekte typesjekket + full produksjonsbuild (26 ruter, inkl.
|
||
de to nye `/my-friends/[id]` og `/watch/[id]`) kjørt før utrulling.
|
||
**Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
|
||
eksplisitt (viste frem full plan for migrasjon FØR den kjørte, per
|
||
CLAUDE.md sin ufravikelige regel): migrasjon 032 kjørt mot ekte
|
||
`teecup_db` (kun additivt — ny kolonne med default, ny tabell, ingen
|
||
eksisterende rader rørt), `test_isolation.sql` fortsatt 12/12,
|
||
deretter `docker compose up -d --build teecup_api teecup_frontend`.
|
||
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||
upåvirket. Verifisert presist at de nye rutene faktisk når FastAPI
|
||
(ikke bare at Next.js svarte): et ukjent person-id mot
|
||
`/public/people/{id}/rounds` over ekte https ga korrekt JSON-formet
|
||
`404 NOT_FOUND` (ikke en rå Next.js-404-side).
|
||
**ADR-036 er dermed HELT ferdig, alle tre faser** (venner-kjernen,
|
||
rundevisibilitet, ekte medspillere) — backend + frontend, live.
|
||
|
||
- **Rundevarsler koblet til det eksisterende varslingssenteret, BYGGET,
|
||
SCRATCH-VERIFISERT OG LIVE (2026-07-28), samme dag:** direkte oppfølging
|
||
av ADR-036 fase 2 — varslingssenteret (2026-07-26) hadde fra start
|
||
reservert `type` for `"round"`/`"result"` (skjema OG frontendens
|
||
`KIND_ICON`/`KIND_LABEL`), men INGENTING skrev noensinne en slik rad —
|
||
kun venneforespørsler trigget et varsel. Bruker bekreftet omfang
|
||
eksplisitt (AskUserQuestion, alle tre valgt): (A) lagt til som ekte
|
||
medspiller på en runde, (B) en venn starter en runde du kan se
|
||
(offentlig, ELLER `friends`-synlig med treffende kategori), (C) en
|
||
runde du er koblet til (eier/medspiller/tredjeparts-venn fra B) blir
|
||
fullført.
|
||
**Ren backend-endring, INGEN frontend-endring nødvendig** — bekreftet
|
||
ved lesing av `notifications.tsx` FØR noe ble bygget: `NotificationKind`/
|
||
`KIND_ICON`/`KIND_LABEL` dekker allerede `"round"`/`"result"` fullt ut,
|
||
siden de ble reservert med akkurat dette for øye i den opprinnelige
|
||
runden.
|
||
**Bygget i `app/routers/rounds.py`:** ny delt `_friends_who_can_see_
|
||
round(conn, round_id, owner_user_id)` — GJENBEREGNER (ikke en lagret
|
||
mottakerliste) nøyaktig samme regel som `_can_view_round` (offentlig =
|
||
alle aksepterte venner, `friends` = kun de hvis EGEN kategorisering av
|
||
venn treffer rundens synlige kategorier), brukt BÅDE ved opprettelse og
|
||
ved fullføring — aldri ute av synk med selve tilgangskontrollen.
|
||
`create_round` sender (B) rett etter transaksjonen (kun for
|
||
`public`/`friends`, aldri `private`), lenke til `/watch/{id}`.
|
||
`add_participant` sender (A) inni transaksjonen når `user_id` er satt
|
||
(aldri for gjester — de har ingen konto å varsle), lenke til
|
||
`/my-rounds/{id}` (full tilgang, ikke tredjeparts-visningen).
|
||
`complete_round` sender (C) til to ATSKILTE mottakergrupper med ulik
|
||
lenke, siden de har ulik tilgang: lenkede medspillere (unntatt den som
|
||
selv fullførte) → `/my-rounds/{id}`; tredjeparts-venner fra (B),
|
||
gjenberegnet på nytt → `/watch/{id}` — ingen dobbel-varsling hvis noen
|
||
skulle være i begge grupper (settoperasjon), og aldri et varsel til
|
||
personen som selv utførte handlingen.
|
||
**Scratch-verifisert grundig, 28/28 nye sjekker** (isolert
|
||
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
||
container, samme mønster som hele prosjektet, alle 32 migrasjoner kjørt
|
||
friskt): venn fikk `round`-varsel ved synlig opprettelse, fremmed fikk
|
||
det IKKE, privat runde ga INGEN varsel til noen, medspiller fikk
|
||
`round`-varsel med riktig `/my-rounds/`-lenke ved tilføyelse, fullføring
|
||
ga `result`-varsel til BÅDE medspiller (`/my-rounds/`) og venn
|
||
(`/watch/`) men ALDRI til den som selv fullførte, en privat solorunde
|
||
sin fullføring ga fortsatt ingen varsler, mark-as-read uendret. PLUSS
|
||
full regresjon av tre eksisterende testsuiter (co-player-flyt 35/35,
|
||
spillformat-flyt 122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle
|
||
fortsatt 100 % grønne, ingen utilsiktet bivirkning av de nye
|
||
`create_notification()`-kallene inni eksisterende transaksjoner.
|
||
`test_isolation.sql` 12/12 uendret (ingen migrasjon, ren Python-logikk).
|
||
**Rullet ut live 2026-07-28**, ingen migrasjon, kun
|
||
`docker compose up -d --build teecup_api`. Ren boot
|
||
(`Application startup complete`), `/health`/`/dashboard` → 200,
|
||
`teeoff.no` upåvirket.
|
||
|
||
- **E-post-fallback for varsler, per type, BYGGET OG SCRATCH-VERIFISERT
|
||
(2026-07-28), samme dag, rett etter rundevarsel-runden:** brukeren ba
|
||
eksplisitt om at mottakeren selv skal kunne velge HVILKE varseltyper som
|
||
skal utløse e-post — ikke en enkelt global av/på-bryter. Ny migrasjon
|
||
`033_notification_email_prefs.sql`: `user_notification_email_pref`
|
||
(`user_id`, `type`, samme CHECK-sett som `notification.type`) — samme
|
||
"tilstedeværelse = valgt"-mønster som `round_visible_category`/
|
||
`friend_categorization`, TRYGG STANDARD ingen rad = ingen e-post for
|
||
noen type (samme "se ingenting til noen har valgt"-filosofi som resten
|
||
av appen).
|
||
**Bevisst ÉN felles innsnevring, ikke ett kallsted per varseltrigger:**
|
||
e-post-utsendingen ligger INNI `create_notification()` selv (etter selve
|
||
INSERT-en) — modul-docstringen kalte den allerede "den eneste
|
||
skrivevegen inn", så alle nåværende OG fremtidige varsel-triggere (i dag
|
||
2 i friends.py, 4 i rounds.py) får e-post-støtte helt uten å røres,
|
||
ingen risiko for at et fremtidig kallsted glemmer det. Samme
|
||
`SMTP_CONFIGURED`-sjekk + try/except + `traceback.print_exc()`-mønster
|
||
som all annen e-postutsending i appen (routers/auth.py,
|
||
organizations.py) — en driftsfeil i selve SMTP-en skal ALDRI hindre at
|
||
in-app-varselet (allerede skrevet FØR e-post-forsøket) består.
|
||
Ny `send_notification_email()` i `app/email.py` — bevisst tospråklig KUN
|
||
i ramme-teksten (emne/hilsen/lenkeforklaring); selve `message`-teksten
|
||
er allerede en ferdig norsk snapshot-tekst (samme prinsipp som
|
||
in-app-varselet), ingen full i18n av selve varselinnholdet i denne
|
||
runden.
|
||
Nye `GET`/`PUT /notifications/email-prefs` — PUT er en FULL erstatning
|
||
(samme kontrakt som `PUT /friends/{id}/categories`), ikke en delvis
|
||
PATCH.
|
||
**Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på:**
|
||
forrige rundes `add_participant`-varsel (medspiller lagt til) lå INNI
|
||
den åpne DB-transaksjonen for selve deltaker-innsettingen — flyttet til
|
||
RETT ETTER (samme mønster som `create_round`/`complete_round` allerede
|
||
fulgte), slik at en fremtidig blokkerende SMTP-utsending aldri skjer
|
||
mens en transaksjon holder låser.
|
||
**Frontend:** ny seksjon "Varsler på e-post" i `/account`
|
||
(`NotificationEmailPrefsSection`, `account-settings.tsx`) — fire
|
||
avkrysningsbokser (Venneforespørsler/Runder/Resultater/Turneringer, sistnevnte
|
||
reservert for fremtidig bruk), samme avkrysningsboks-mønster som
|
||
kølle-bag-listen lenger opp i samme fil, lagrer umiddelbart ved hvert
|
||
klikk (ingen egen "lagre"-knapp).
|
||
**Scratch-verifisert grundig, 19/19 nye sjekker** (isolert
|
||
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
||
container, alle 33 migrasjoner kjørt friskt): trygg standard bekreftet
|
||
(ingen typer valgt fra start), full-erstatning bekreftet (PUT med ett
|
||
sett fjerner det forrige, legger ikke til), e-post-fallback FAKTISK
|
||
logget (dev-log-varianten av `SMTP_CONFIGURED`-grenen) for en bruker med
|
||
`round`/`result` valgt inn — BÅDE ved medspiller-tilføyelse og ved
|
||
fullføring — INGEN e-post til brukeren som selv utførte handlingen,
|
||
INGEN e-post til en bruker (eieren) som ikke har valgt inn NOE, og etter
|
||
å ha slått AV `result` igjen: ingen ny e-post ved neste fullføring MENS
|
||
in-app-varselet fortsatt opprettes helt uendret (de to er reelt
|
||
atskilte, ikke koblet). PLUSS full regresjon av fire eksisterende
|
||
testsuiter (rundevarsler 28/28, co-player-flyt 35/35, spillformat-flyt
|
||
122/122, ADR-036 fase 2-synlighetsmatrise 34/34) — alle fortsatt 100 %
|
||
grønne, ingen bivirkning av at `add_participant`s varsel flyttet utenfor
|
||
transaksjonen. `test_isolation.sql` 12/12 uendret. Ekte typesjekket
|
||
produksjonsbuild (samme `Dockerfile` som deployes) kompilerte rent,
|
||
alle 24 ruter listet uendret.
|
||
|
||
Neste steg:
|
||
0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet
|
||
(2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen
|
||
konsoll-feil. Ikke hvert enkelt interaksjonsdetalj (f.eks. gjeste-
|
||
kjønnsendringens reaktive utslagsfilter) klikket gjennom stykke for
|
||
stykke, men grunnleggende rendring/lasting er bevist, ikke lenger
|
||
bare typesjekket.
|
||
0b. **Rundeleaderboard — nå FAKTISK nettleser-bekreftet (2026-07-27):**
|
||
brutto/netto/poeng-veksling testet direkte i nettleseren, viste
|
||
korrekte tall og riktig form/farge-språk.
|
||
1. **Ferdig, kun for historikk:** dashbord-redesign (ADR-035) og
|
||
venner/kategorisert deling fase 1 (ADR-036) — begge designet
|
||
2026-07-25 og siden BYGGET, SCRATCH-VERIFISERT OG RULLET UT LIVE
|
||
samme dag (se status over). Venner fase 2 (rundevisibilitet) og
|
||
fase 3 (ekte medspillere) er fortsatt ikke bygget.
|
||
3. **Frittstående rundeføring + detaljert statistikk — ADR-033 skrevet
|
||
2026-07-22, IKKE bygget.** Brukeren avklarte 2026-07-22 at dette skal
|
||
bli appens HOVEDFOKUS (turneringsoppsett skal bli ekstremt enkelt
|
||
ETTER dette er på plass) — den største enkeltbeslutningen i prosjektet
|
||
siden ADR-001. Fire load-bærende delbeslutninger avklart eksplisitt
|
||
(AskUserQuestion): nytt parallelt eierskapsmønster keyet på `user_id`
|
||
(gjenbruker det allerede beviste `plain_connection()`-mønsteret fra
|
||
personlig profil/HCP-historikk/sekundær e-post — IKKE en skjult
|
||
personlig organisasjon), fast sett navngitte statistikk-felt per hull
|
||
(ikke fri slag-for-slag-logg), full WHS HCP-indeksberegning bygges NÅ
|
||
(ikke utsatt), shotgun-start blir egen separat ADR-034. De tre
|
||
HCP-PDF-ene lest i sin helhet — Score Differential/Course
|
||
Handicap/Net Double Bogey-formlene er hentet derfra (kilde, ikke
|
||
hukommelse).
|
||
**Oppdatert samme dag:** brukeren bekreftet banedata-spørsmålet (LIVE
|
||
oppslag mot teeoff, ikke import — Beslutning C) og lastet opp en
|
||
FJERDE PDF, den offisielle "WHS Rules of Handicapping 2024"
|
||
(USGA/R&A) — lest i sin helhet, løste presist det som manglet: 9-
|
||
hulls-minimumsregelen (Rule 2.2, IKKE bare "minst 9 hull" — en
|
||
9-hulls-runde krever ALLE 9 av et faktisk ratet sett, en 18-hulls-
|
||
runde krever minst 10 av 18), full HCP-indeks-pipeline (Score
|
||
Differential, Net Double Bogey, expected-score for uspilte hull, beste
|
||
8-av-20, Low Handicap Index, soft/hard cap), OG en ny, presis
|
||
9-hulls-Course-Handicap-formel som HALVERER indeksen først (Rule
|
||
6.1b) — bevisst holdt atskilt fra det eksisterende front_9/back_9-
|
||
øktoppsettet i turnering-flyten (ADR-008), som løser et annet problem
|
||
av andre grunner. ADR-033 er dermed fullt kildebelagt, ingen store
|
||
åpne HCP-regelspørsmål gjenstår.
|
||
**Bygging påbegynt samme dag:** brukeren instruerte at "Expected
|
||
Score" (Rule 3.2b, upublisert WHS-formel) erstattes gjennomgående av
|
||
WHS sin egen "Net Par"-term for uspilte hull — løser samtidig hele
|
||
9-hulls-runde-spørsmålet uten en egen separat formel (Rule 5.1b droppet
|
||
bevisst). Hele HCP-indeks-motor-komponenten (Net Double Bogey/Net Par,
|
||
Adjusted Gross Score, Score Differential, Handicap Index fra beste-
|
||
8-av-20 m/opptrappingstabell for <20 runder, Low Handicap Index, soft/
|
||
hard cap, 9-hulls Course Handicap) er BYGGET og TESTET i
|
||
`handicap_engine.py` — 41/41 tester (`test_handicap_engine.py`, kjørt
|
||
uten pytest da det ikke er installert i miljøet), flere verifisert mot
|
||
regelbokens egne tallregneeksempler (Rule 5.2a, Rule 5.1c, Diagram 5.8,
|
||
Diagram 3.1b). Ren Python, ingen DB/API/frontend rørt ennå — matcher
|
||
ADR-005s "test i isolasjon FØR resten".
|
||
**Deretter, samme dag:** full databasemigrasjon skrevet
|
||
(`020_personal_rounds.sql`, sju nye tabeller: `round`/
|
||
`round_participant`/`round_hole` + fire for en global egendefinert-
|
||
bane-katalog) og scratch-verifisert (9 sjekker som
|
||
`teecup_app_scratch`, ikke superbruker — CHECK-constraints, XOR
|
||
user_id/guest_name, kaskade-sletting, GIR-derivering bekreftet mot
|
||
ekte data). INGEN RLS på disse tabellene (Beslutning A). `test_
|
||
isolation.sql` fortsatt 12/12.
|
||
**Rullet ut mot ekte `teecup_db` 2026-07-22,** bruker bekreftet
|
||
eksplisitt: alle sju tabeller bekreftet opprettet, `test_isolation.sql`
|
||
fortsatt 12/12 mot ekte database.
|
||
**Deretter, samme dag: fullt API-lag bygget** (`app/routers/
|
||
rounds.py` — opprett/liste/hent/slett runde, legg til/fjern gjest-
|
||
deltaker, PATCH hull-for-hull-stats, fullfør-runde som kjører hele
|
||
Adjusted-Gross-Score→Score-Differential-kjeden). Fant og fikset et
|
||
reelt hull underveis: `round_participant` manglet rating-tall-
|
||
kolonner (kun tee-NAVN var snapshotet, ikke selve Course/Slope/Par) —
|
||
ny migrasjon `021_round_participant_rating_snapshot.sql`. 26 scratch-
|
||
sjekker + en egen, ekte teeoff-live-oppslag-test (Beslutning C
|
||
bekreftet: ingen `course`-rad skrives noe sted). Rullet ut mot ekte
|
||
`teecup_db`/`teecup_api` 2026-07-22, `/health`/`/dashboard` → 200,
|
||
`teeoff.no` upåvirket. `frontend/next.config.mjs` fikk `/rounds`/
|
||
`/personal-courses` lagt til i `rewrites()` proaktivt (ikke deployet
|
||
ennå, ingen frontend bruker den før neste runde).
|
||
**Notat fra bruker, IKKE designet:** planer om å måle lengde på slag +
|
||
opplyse avstand til ulike punkter på banen (golf-GPS/rangefinder-type
|
||
funksjonalitet) — krever geografiske/GPS-data ingen kilde har i dag
|
||
(verken teeoff eller `personal_course`). Se FEATURE_BACKLOG.md for
|
||
full detalj.
|
||
**Frontend bygget og rullet ut 2026-07-23, samme rekkefølge-prinsipp
|
||
(engine → skjema → API → frontend) fullført:** tre nye,
|
||
organisasjonsuavhengige endepunkter lagt til i `rounds.py`
|
||
(`GET /rounds/official-search`/`{slug}` — samme mønster som
|
||
`courses.py` sitt org-scopede søk, men uten org-kontekst, siden
|
||
frittstående runder ikke har noen; `GET /personal-courses/{id}` —
|
||
detalj med utslag+kjønn, manglet fra søk-runden) og en fjerde,
|
||
nødvendig tilføyelse oppdaget UNDER frontend-designet: `RoundOut` bar
|
||
aldri hull-nivå-data i det hele tatt — ny
|
||
`GET .../participants/{id}/holes`. Hull-PATCH-endepunktet endret til å
|
||
returnere hele den oppdaterte raden i stedet for `{"ok": true}`.
|
||
**Reelt kontraktsfunn, bekreftet i scratch FØR frontend stolte på det:**
|
||
hull-PATCH er IKKE et ekte delvis-PATCH — den skriver ALLE felt ved
|
||
hvert kall, så et utelatt felt (f.eks. putts) nullstilles stille hvis
|
||
frontend ikke sender det. Løst ved at `round-detail.tsx` alltid slår
|
||
sammen med gjeldende hull-data før hver PATCH, aldri sender et isolert
|
||
feltnavn alene — verifisert eksplisitt med en egen scratch-test som
|
||
FØRST beviste nullstillings-oppførselen, DERETTER beviste at
|
||
merge-mønsteret unngår den.
|
||
Nye sider: `/rounds` (liste), `/rounds/new` (bane-kilde teeoff/egen,
|
||
søk-før-opprett for egen bane samme idé som org-banene, utslag filtrert
|
||
på brukerens registrerte kjønn, dato/starthull/hull-antall), `/rounds/
|
||
[id]` (deltaker-faner, hull-navigasjon fra starthull, slag/putt-
|
||
tallvelgere i samme stil som `session-scorecard.tsx` sin `StrokePicker`,
|
||
kølle/retning/innspill/chip/bunker/straffeslag bak en «flere detaljer»-
|
||
utvidelse, GIR utledet og vist klientside — aldri lagret, kun beregnet
|
||
fra `approach_result`+slag+putt ved lesing, fullfør-runde med HCP-
|
||
differensial-sammendrag). Lenket fra dashbordet som «Egne runder»
|
||
(bevisst adskilt navn fra det eksisterende «Mine runder», som gjelder
|
||
turnering-deltakelse — samme ord, to ulike konsepter).
|
||
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
||
isolert scratch-MinIO + engangs API-container, samme mønster som hele
|
||
økten ellers): 22 sjekker som dekker egendefinert-bane-opprettelse med
|
||
to utslag/kjønn, søk, detalj, PATCH-kontrakten (inkl. det bevisste
|
||
nullstillings-beviset over), GIR-derivering, gjest-fjerning,
|
||
kryss-bruker-autorisasjon (403), og full fullføring med differensial
|
||
— PLUSS en egen, separat test av hele teeoff-baserte opprettelsesløpet
|
||
mot den ekte kjørende `teeoff_api`-containeren (Borregaard Golfklubb),
|
||
som bekreftet course_handicap ble beregnet riktig. Ekte typesjekket
|
||
PRODUKSJONSBUILD (`docker build --target builder`, samme steg som
|
||
`Dockerfile` faktisk bruker) kjørt og bekreftet — alle nye ruter listet.
|
||
**Rullet ut live 2026-07-23**, bruker bekreftet eksplisitt: ingen
|
||
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent, `/health`/`/dashboard`/`/rounds` → 200,
|
||
`teeoff.no` upåvirket. **Frittstående rundeføring har dermed backend
|
||
OG frontend live** — se ADR-033 i ARCHITECTURE_DECISIONS.md for full
|
||
detalj om alle fire lagene (engine/skjema/API/frontend).
|
||
**Frontend ERSTATTET med V0-designet versjon, samme dag:** brukeren
|
||
påpekte berettiget at de tre skjermene over var hånd-kodet av meg i
|
||
stedet for designet i V0, som er prosjektets etablerte mønster for
|
||
ALL øvrig frontend. Bekreftet arbeidsmåten uendret (jeg skriver
|
||
V0-prompten, brukeren kjører den og sender koden tilbake, jeg
|
||
integrerer). Tre prompter skrevet (liste/opprett/hull-registrering,
|
||
inkl. eksplisitt tilgjengelighetskrav i hver), tre zip-eksporter
|
||
mottatt og diffet mot levende tre (samme "full re-eksport"-mønster som
|
||
alltid — kun `round-card.tsx`/`own-rounds.tsx`/`new-round.tsx`/
|
||
`round-detail.tsx` + rutene var reelt nye). Samme kjente V0-feil dukket
|
||
opp igjen og ble hoppet over (`app/clubs/[id]/page.tsx`, feilnavngitt
|
||
slug-parameter — identisk feil som ble rettet under ADR-018). Mine
|
||
hånd-bygde komponenter ERSTATTET (ikke supplert), datalag skrevet om
|
||
fra V0s mock til ekte fetch — samme mønster som enhver annen skjerm.
|
||
Full detalj (inkl. de fire reelle tilpasningene utover ren om-kabling)
|
||
i ADR-033. Ekte typesjekket produksjonsbuild kjørt og bekreftet, ingen
|
||
ny interaktiv nettleser-test (intet slikt verktøy tilgjengelig,
|
||
flagget eksplisitt). **Rullet ut live 2026-07-23**, bruker bekreftet
|
||
eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte
|
||
også `teecup_api` som vanlig bivirkning), begge containere boot-et
|
||
rent, `/health`/`/dashboard`/`/rounds`/`/rounds/new` → 200, `teeoff.no`
|
||
upåvirket. V0-zip-ene slettet fra prosjektroten.
|
||
**Reell produksjonsbug rapportert av bruker (2 skjermbilder) og
|
||
FIKSET samme dag:** `/rounds` var samtidig frontend-listesidens sti OG
|
||
backend-APIets ressursprefiks — en verre, begge-veier-variant av
|
||
ADR-016s medlemsside-felle. Statisk side vant over rewrite for det
|
||
eksakte `/rounds`-treffet (klientens `fetch("/rounds")`/`POST /rounds`
|
||
traff aldri backend, fikk Next sin egen HTML tilbake — derav "Klarte
|
||
ikke å hente rundene dine"/"Klarte ikke å opprette runden"), mens
|
||
rewriten vant over den DYNAMISKE `/rounds/[id]`-siden i motsatt
|
||
retning (selve rundedetalj-siden var dermed fullstendig uoppnåelig,
|
||
bekreftet direkte med `curl` før fiksen). Skjermbildets andre detalj —
|
||
utslagsnavn "55/50/44/32" — ble sjekket direkte mot ekte `teeoff_api`
|
||
og bekreftet Å IKKE VÆRE EN BUG (Tjøme Golfklubb sine faktiske
|
||
utslagsnavn, lengde i hundremeter). Fikset ved å flytte alle tre
|
||
frontend-rutene til et nytt, ikke-overlappende prefiks `/my-rounds/*`
|
||
— API-et uendret på `/rounds`. `next.config.mjs` sin advarsel utvidet
|
||
med denne nye varianten. Verifisert med `curl` mot ekte produksjon
|
||
BÅDE før og etter (anonymt `GET /rounds` → nå korrekt backend-JSON,
|
||
anonymt `GET /my-rounds/<uuid>` → nå korrekt `text/html`, altså
|
||
endelig oppnåelig). Rullet ut live 2026-07-23, bruker bekreftet
|
||
eksplisitt, kun `teecup_frontend` (+ vanlig `teecup_api`-bivirkning),
|
||
`teeoff.no` upåvirket. Full detalj i ADR-033.
|
||
4. **Del 1 (fri, ukrevd sekundær-e-post) er nå BYGGET OG LIVE** (2026-07-21,
|
||
se status over). **Del 2 (ekte konto-sammenslåing) fortsatt IKKE
|
||
designet:** hva skjer hvis den ønskede adressen ALLEREDE tilhører en
|
||
annen, eksisterende konto (i dag avvist tydelig med 409 DUPLICATE i
|
||
stedet for gjettet på)? Trenger egen, separat designrunde — se
|
||
FEATURE_BACKLOG.md for full analyse av hvorfor dette er vesentlig
|
||
vanskeligere enn del 1.
|
||
5. **Deltaker-tilgang til lag-chat/scorekort OG HCP-historikk er nå BEGGE
|
||
BYGGET OG LIVE** (2026-07-21, se status over) — ADR-031s tidligere
|
||
noterte "naturlig neste steg"-punkter er dermed alle tettet, unntatt
|
||
notifikasjons-/aktivitetsfeed og "Mine runder" for rene påmeldinger
|
||
(begge fortsatt IKKE bygget, se FEATURE_BACKLOG.md).
|
||
6. ~~Oppfølgingspunkt fra PWA-runden: offline-scoreregistrering er ALDRI
|
||
browser-testet i praksis~~ — **ferdig 2026-07-28** (se status-punkt
|
||
samme dag, "Turnering-scorekortets offline-flyt (ADR-028) FAKTISK
|
||
browserverifisert"). Beholdt her kun for historikk.
|
||
7. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for
|
||
ADR-020, korrigering-godkjenning fra motpart, video/1-til-1-meldinger
|
||
(bevisst utsatt i ADR-025).
|
||
8. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/
|
||
Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå.
|
||
9. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt),
|
||
FORVENT en full re-eksport hver gang — diff mot live-treet i et
|
||
scratch-område før noe pakkes ut over eksisterende filer, og sjekk om
|
||
V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR
|
||
integrering.
|
||
10. **Resterende hull fra 2026-07-28-gjennomgangen av frittstående runder**
|
||
(det viktigste — faktisk HCP — er tettet, se ADR-038 over; offline-kø,
|
||
punkt (a), er nå OGSÅ tettet, se status 2026-07-28 over — punktet
|
||
beholdes her kun for historikk): (a) ~~offline-kø (ADR-028) er kun
|
||
koblet til turnering-scorekortet~~ — ferdig, browserverifisert og
|
||
live; (b) ingen Stableford-poengberegning for frittstående runder (kun
|
||
rå slag/differensial), inkl. en uløst "plukket opp ballen"-tilstand;
|
||
(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 (se
|
||
FEATURE_BACKLOG.md); (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).
|
||
11. **Ferdig, kun for historikk:** ekte spillformer (match/skins/fourball/
|
||
foursome/greensome/scramble) for frittstående runder — backend
|
||
(ADR-039 + minimums-spiller-håndhevelse) OG frontend (sideoppsett,
|
||
skins-konfig i `/my-rounds/new`, matchstatus-/skins-tavle-visning,
|
||
delt-ball-scorekort/-veiviser, `setup_complete`-gating) er nå BEGGE
|
||
bygget, browserverifisert og live (se status 2026-07-28). Mulig
|
||
fremtidig finpuss (ikke bedt om ennå): redigere et sidenavn i
|
||
etterkant (kun opprett/slett finnes i dag), en tydeligere
|
||
skins-poeng-forklaring i UI-et.
|