2026-08-03 06:24:57 +02:00
|
|
|
|
# TeeCup — CHANGELOG
|
|
|
|
|
|
|
|
|
|
|
|
> Dette er den kronologiske arbeidsloggen for prosjektet — hva som er
|
|
|
|
|
|
> bygget, når, hvorfor, hva som ble funnet og fikset underveis, og hvordan
|
|
|
|
|
|
> hvert steg ble verifisert FØR utrulling. Det er IKKE noe du trenger å
|
|
|
|
|
|
> lese i sin helhet i hver økt.
|
|
|
|
|
|
>
|
|
|
|
|
|
> **Les CLAUDE.md først** — den inneholder de ufravikelige reglene
|
|
|
|
|
|
> (sikkerhet, arkitektur-invarianter, tilgjengelighet, navneformat,
|
|
|
|
|
|
> autoritative kilder) som alltid gjelder, uavhengig av historikk.
|
|
|
|
|
|
>
|
|
|
|
|
|
> **Bruk DENNE filen når:**
|
|
|
|
|
|
> - du feilsøker en regresjon og trenger å vite hvordan/hvorfor noe ble
|
|
|
|
|
|
> bygget som det ble (grep etter filnavn, ADR-nummer, eller feature-navn
|
|
|
|
|
|
> under),
|
|
|
|
|
|
> - du skal gjøre en større endring i et område og vil sjekke om det
|
|
|
|
|
|
> finnes tidligere fallgruver dokumentert her (f.eks. Caddy sin
|
|
|
|
|
|
> stale-inode-oppførsel, MinIO sin understrek-i-hostnavn-feil,
|
|
|
|
|
|
> RLS-tomstreng-bugen, service worker sin nettverk-først-var-egentlig-
|
|
|
|
|
|
> cache-først-bug),
|
|
|
|
|
|
> - brukeren spør "har vi gjort dette før" eller "hvorfor er det sånn her".
|
|
|
|
|
|
>
|
|
|
|
|
|
> For ARKITEKTUR-BESLUTNINGER (hvorfor noe er designet som det er, ADR-
|
|
|
|
|
|
> nummerert), se `ARCHITECTURE_DECISIONS.md`. For GJENSTÅENDE arbeid, se
|
|
|
|
|
|
> `FEATURE_BACKLOG.md` (og "Neste steg" nederst i denne filen for en
|
|
|
|
|
|
> ferskere, mer detaljert punktliste). For dagens visuelle designsystem,
|
|
|
|
|
|
> se `DESIGN_SYSTEM.md`.
|
|
|
|
|
|
|
|
|
|
|
|
## Status (oppdater denne når ting endres)
|
|
|
|
|
|
Ferdig og verifisert:
|
|
|
|
|
|
- Handicap-motor + tester (24/24, R&A-verifisert).
|
|
|
|
|
|
- Skjema `001` + roller `002` + scoring/blind draw `003`. Isolasjon bevist med
|
|
|
|
|
|
`test_isolation.sql` (RLS-oppførsel, ikke bare at skjemaet kjører).
|
|
|
|
|
|
- 002 hadde en reell bug (psql interpolerer ikke `:'var'` inne i `DO $$...$$`)
|
|
|
|
|
|
— permanent fikset, verifisert mot scratch to ganger.
|
|
|
|
|
|
- API-et kjørt for ekte (ikke bare syntaks-sjekket) i en engangs Docker-
|
|
|
|
|
|
container mot en scratch-database, RLS bevist gjennom hele
|
|
|
|
|
|
asyncpg-pool-stacken (ikke bare i rå SQL).
|
|
|
|
|
|
- Oppsett-endepunktene er bygget og verifisert: `app/routers/players.py`
|
|
|
|
|
|
(spillerpool), `tournaments.py` (turnering/lag/roster/økter, ADR-011
|
|
|
|
|
|
to-lags-grense håndhevet med `FOR UPDATE`-lås), `matches.py` (matcher/
|
|
|
|
|
|
deltakere/blind draw-lås, ADR-013-synlighet push-down i SQL). Delt
|
|
|
|
|
|
feiloversettelse i `app/errors.py`, delte synlighetsspørringer i
|
|
|
|
|
|
`app/blind_draw.py`. `main.py` er nå bare app-factory + `include_router`.
|
|
|
|
|
|
- Scoring-runden er bygget og verifisert for ekte mot scratch-db (18-hulls
|
|
|
|
|
|
bane med `tee_rating`, 4 spillere for fourball-testing): `app/handicap.py`
|
|
|
|
|
|
(ADR-014 fire brytere via `parse_allowance_config`, handicap beregnes i
|
|
|
|
|
|
`compute_and_store_side_handicaps` rett etter deltaker-innsetting —
|
|
|
|
|
|
singles/fourball per spiller umiddelbart, foursome/greensome/scramble kun
|
|
|
|
|
|
når siden er komplett), `app/routers/scoring.py` (`hole-scores`/
|
|
|
|
|
|
`hole-results`-upsert, `scorecard`-GET, matchstatus-recompute med
|
|
|
|
|
|
`FOR UPDATE`-lås mot race og SAMMENHENGENDE-prefiks-regel for uferdige
|
|
|
|
|
|
hull). `app/team_authz.py` skilt ut fra `matches.py` (delt med
|
|
|
|
|
|
`scoring.py`). Alle 10 planlagte tester bestått, inkl. fourball
|
|
|
|
|
|
better-ball-aggregering (MIN av to nettoer, venter til begge partnere har
|
|
|
|
|
|
registrert), poeng-caching ved tidlig avgjort match, og ADR-014-bryteren
|
|
|
|
|
|
`use_handicap=false`.
|
|
|
|
|
|
**Fant og fikset underveis:** `tournaments.py` sin `SessionCreate` manglet
|
|
|
|
|
|
`scoring_mode` helt (økter kunne aldri opprettes i `hole_result`-modus via
|
|
|
|
|
|
API-et) — lagt til.
|
|
|
|
|
|
**Bevisst utelatt/kjente begrensninger:** en side som aldri når forventet
|
|
|
|
|
|
deltakerantall (no-show) får aldri beregnet handicap og matchen kan da
|
|
|
|
|
|
aldri avgjøres — ingen manuell overstyring bygget. Score-skriving er
|
|
|
|
|
|
upsert (ingen avvisning ved duplikat) — ingen audit-trail på rettelser.
|
|
|
|
|
|
Kapteins-only autorisasjon fortsatt ikke bygget (FEATURE_BACKLOG ❓); bar
|
|
|
|
|
|
er «rostret på laget».
|
|
|
|
|
|
- **Match-lås ved avgjørelse (2026-07-16):** `submit_hole_score`/
|
|
|
|
|
|
`submit_hole_result` avviser nå 409 hvis `match.points_side_a IS NOT NULL`
|
|
|
|
|
|
(matchen er avgjort) — FØR upserten kjøres, både for nye hull og
|
|
|
|
|
|
korrigering av allerede talte hull. Tetter en reell bug: uten dette kunne
|
|
|
|
|
|
«spøkelses-hull» lagt inn etter avgjørelse endre en allerede cachet margin
|
|
|
|
|
|
ved neste omregning. Automatisk, ingen ny autorisasjon involvert.
|
|
|
|
|
|
- **Ekte autentisering bygget og verifisert (2026-07-16):** `X-Debug-User-Id`-
|
|
|
|
|
|
stubben er HELT fjernet (ingen fallback). Magic-link + JWT-sesjon i
|
|
|
|
|
|
`app/routers/auth.py` + `app/auth.py` (`request-link`/`verify-link`/
|
|
|
|
|
|
`logout`/`me`), ny migrasjon `004_auth.sql` (`magic_link_token`-tabell +
|
|
|
|
|
|
unik e-post-indeks på `app_user`). Token = `secrets.token_urlsafe(32)`, kun
|
|
|
|
|
|
SHA-256-hash lagres, atomisk forbruk (`UPDATE ... RETURNING`, ikke
|
|
|
|
|
|
les-sjekk-skriv), generisk respons uansett om e-posten finnes (unngår
|
|
|
|
|
|
enumerering), gamle uforbrukte lenker ugyldiggjøres når en ny utstedes,
|
|
|
|
|
|
`app_user` opprettes FØRST ved vellykket verifisering (ikke ved
|
|
|
|
|
|
forespørsel). Sesjons-JWT (PyJWT, `algorithms=["HS256"]` eksplisitt) i
|
|
|
|
|
|
HttpOnly/SameSite=Lax/dynamisk-Secure-cookie, 30 dager, med et ekte
|
|
|
|
|
|
eksistens-oppslag mot `app_user` på hver forespørsel (faktisk
|
|
|
|
|
|
tilbakekalling — en slettet bruker kan ikke ri ut sesjonen). Alle 12
|
|
|
|
|
|
planlagte tester bestått.
|
|
|
|
|
|
**Fant og fikset underveis:** `ON CONFLICT (email)` matchet ikke den nye
|
|
|
|
|
|
PARTIELLE unike indeksen uten eksplisitt `WHERE email IS NOT NULL` (samme
|
|
|
|
|
|
klasse feil som `hole_score`s partielle indekser i scoring-runden).
|
|
|
|
|
|
**Fant, IKKE fikset i denne runden (egen runde rett etterpå — se under):**
|
|
|
|
|
|
`organization`-tabellens RLS-policy kastet en 500 i stedet for skjemaets
|
|
|
|
|
|
lovede "trygg standard: se ingenting" ved tomstreng-GUC.
|
|
|
|
|
|
- **RLS-tomstreng-bug FIKSET (2026-07-16):** ny migrasjon
|
|
|
|
|
|
`005_rls_null_guard.sql` — delt `STABLE` SQL-funksjon `app_current_org()`
|
|
|
|
|
|
gjør `NULLIF(current_setting('app.current_org', true), '')::uuid` i stedet
|
|
|
|
|
|
for det rå uttrykket, brukt av alle 15 RLS-policyer (`ALTER POLICY`,
|
|
|
|
|
|
14 `org_isolation` + `org_self`). Verifisert med 3 nye regresjonstester i
|
|
|
|
|
|
`test_isolation.sql` (Test 10-12) OG ved faktisk å gjenskape original-
|
|
|
|
|
|
buggen mot en ekte container (pool-størrelse 1, varm opp med
|
|
|
|
|
|
`org_connection()`, deretter `/auth/me` på samme gjenbrukte tilkobling —
|
|
|
|
|
|
gikk fra 500 til 200).
|
|
|
|
|
|
**Viktig presisering fra denne runden:** fiksen gjør IKKE at `/auth/me` kan
|
|
|
|
|
|
joine `organization` direkte via `plain_connection()` — det var en feilaktig
|
|
|
|
|
|
antakelse i forrige runde. `org_self` krever fortsatt en MATCHENDE
|
|
|
|
|
|
`app.current_org` for å vise en rad (riktig RLS-design, ikke noe fiksen
|
|
|
|
|
|
skulle endre), og en bruker kan tilhøre flere organisasjoner samtidig, så
|
|
|
|
|
|
det finnes ingen ÉN kontekst å sette for en tverr-org-spørring. `/auth/me`
|
|
|
|
|
|
slår derfor opp hvert org-navn ett om gangen via `org_connection()` (N+1,
|
|
|
|
|
|
N = antall org-er brukeren tilhører) — dette er riktig løsning, ikke en
|
|
|
|
|
|
omvei.
|
|
|
|
|
|
- **Organisasjon-bootstrap bygget og verifisert (2026-07-16):** nytt
|
|
|
|
|
|
`POST /orgs` (`app/routers/organizations.py`) — det ENESTE stedet i API-et
|
|
|
|
|
|
som setter inn en `organization`-rad. Fant under statusgjennomgang at dette
|
|
|
|
|
|
manglet helt (alle tidligere org-er var seedet med superbruker-SQL). Ingen
|
|
|
|
|
|
ny migrasjon. Selvrefererende RLS-bootstrap bekreftet å fungere: generer
|
|
|
|
|
|
org-ens uuid i Python, sett `app.current_org` til nøyaktig den via
|
|
|
|
|
|
eksisterende `org_connection()`, sett inn `organization`-raden med samme
|
|
|
|
|
|
id — `org_self`s implisitte `WITH CHECK` blir da trivielt sann, ingen
|
|
|
|
|
|
privilegert tilkobling nødvendig (i motsetning til hva 002s kommentar
|
|
|
|
|
|
antydet). Verifisert med 5 tester inkl. en negativ kontroll (mismatchende
|
|
|
|
|
|
id avvist med `insufficient_privilege`) og full kryss-org-isolasjon mellom
|
|
|
|
|
|
to uavhengig opprettede organisasjoner.
|
|
|
|
|
|
- **Ekte SMTP-utsending bygget og verifisert (2026-07-16):** ny `app/email.py`
|
|
|
|
|
|
(`send_magic_link_email`, `smtplib` via `asyncio.to_thread`, håndterer
|
|
|
|
|
|
både implisitt TLS/port 465 og STARTTLS dynamisk). Brukeren la egne
|
|
|
|
|
|
SMTP-credentials i `.env` (`TEECUP_SMTP_*`, `TEECUP_FROM_EMAIL` — ADR-009,
|
|
|
|
|
|
ikke delt med teeoff); jeg leste kun nøkkelnavnene for å bekrefte de
|
|
|
|
|
|
fantes, aldri verdiene. `app/config.py` sin `SMTP_CONFIGURED` er valgfri
|
|
|
|
|
|
(ikke `_required`) — dev-only logging (`TEECUP_DEV_LOG_MAGIC_LINKS`)
|
|
|
|
|
|
fortsatt fungerer uendret når SMTP ikke er satt opp. Driftsfeil i
|
|
|
|
|
|
utsendingen lekker aldri til klientresponsen (bevarer anti-enumerering).
|
|
|
|
|
|
**Verifisert med faktisk levering:** sendte én ekte test-e-post til en
|
|
|
|
|
|
adresse brukeren oppga — brukeren bekreftet mottak. Første gang noe i
|
|
|
|
|
|
prosjektet er bevist ved ekte levering, ikke bare curl/scratch.
|
|
|
|
|
|
- **Containerisert og LIVE på `teecup.teeoff.no` (2026-07-16):** ekte
|
|
|
|
|
|
`teecup_db` opprettet (migrasjoner 001→005 kjørt permanent, `test_isolation.sql`
|
|
|
|
|
|
består), `Dockerfile` + `docker-compose.yml` (tjeneste `teecup_api`, joiner
|
|
|
|
|
|
det eksisterende `teeoff_default`-nettverket), Caddy-blokk lagt til i
|
|
|
|
|
|
`/opt/teeoff/deploy/Caddyfile`. Ekte innlogging (magic-link → e-post →
|
|
|
|
|
|
JWT-sesjon med `Secure`-cookie) verifisert ende-til-ende mot den live
|
|
|
|
|
|
stacken. `teeoff.no` upåvirket gjennom hele prosessen.
|
|
|
|
|
|
**To reelle hendelser underveis, begge løst:**
|
|
|
|
|
|
1. **Caddy plukket ikke opp filendringen** — `teeoff_caddy` sin
|
|
|
|
|
|
`Caddyfile`-mount er en ENKELTFIL-bind-mount, låst til inoden som fantes
|
|
|
|
|
|
da containeren sist startet. Min fil-redigering (atomisk rename) laget
|
|
|
|
|
|
en ny inode på samme sti, så containeren fortsatte å lese den GAMLE
|
|
|
|
|
|
filen uansett hvor mange ganger `caddy validate`/`caddy reload`/admin-API
|
|
|
|
|
|
`/load` ble kjørt (alle validerte/lastet den uendrede gamle filen, derav
|
|
|
|
|
|
ingen feilmelding). Løst med en full `docker restart teeoff_caddy`
|
|
|
|
|
|
(brukeren bekreftet — avvek fra planens "kun graceful reload, ingen
|
|
|
|
|
|
omstart"-løfte, noen sekunders nedetid for `teeoff.no`).
|
|
|
|
|
|
2. **Alvorlig nettverksalias-kollisjon** (funnet RETT ETTER omstarten, da
|
|
|
|
|
|
ekte teeoff-trafikk som `/api/facilities?...` med ekte klubb-slugs dukket
|
|
|
|
|
|
opp i `teecup_api` sin logg): `docker-compose.yml` sin service-nøkkel var
|
|
|
|
|
|
`api:` — SAMME nøkkel som teeoffs eget `api`-servicenavn
|
|
|
|
|
|
(`docker-compose.prod.yml`). Docker Compose registrerer nettverksalias
|
|
|
|
|
|
basert på service-NAVNET (ikke bare `container_name`) på delte nettverk,
|
|
|
|
|
|
så BEGGE containerne fikk alias `api` på `teeoff_default` — Caddys
|
|
|
|
|
|
`reverse_proxy api:8000` i teeoff sin egen config kunne da tilfeldig
|
|
|
|
|
|
treffe enten ekte `teeoff_api` eller `teecup_api`. **Rettet umiddelbart**
|
|
|
|
|
|
(stoppet `teecup_api` først for å hindre videre feilruting av ekte
|
|
|
|
|
|
teeoff-trafikk, ga service-nøkkelen navnet `teecup_api` i stedet,
|
|
|
|
|
|
gjenopprettet — bekreftet med `docker network inspect` at alias `api` nå
|
|
|
|
|
|
KUN peker på ekte `teeoff_api`).
|
|
|
|
|
|
**Mindre driftslærdom:** (a) jeg eksponerte ved et uhell
|
|
|
|
|
|
`TEECUP_SMTP_PASS`/`TEECUP_FROM_EMAIL` i eget debug-output mens jeg
|
|
|
|
|
|
feilsøkte en `.env`-korrupsjon (manglende linjeskift fra min egen
|
|
|
|
|
|
`>>`-tilføyelse) — brukeren roterte passordet som forsiktighetsregel; (b)
|
|
|
|
|
|
Docker leser IKKE `.env` på nytt for en allerede kjørende container —
|
|
|
|
|
|
`docker compose up -d --force-recreate` kreves etter enhver `.env`-endring
|
|
|
|
|
|
som skal tas i bruk; (c) et `#`-tegn i et upassordet `.env`-passord kuttes
|
|
|
|
|
|
som en kommentar av Compose sin parser — anførselstegn (fortrinnsvis enkle)
|
|
|
|
|
|
løser dette.
|
|
|
|
|
|
- **Dato/klokkeslett + feilkode-kontrakt + i18n-forberedelse (2026-07-17,
|
|
|
|
|
|
ADR-015):** reist av brukeren rett før frontend-arbeidet. Ny migrasjon
|
|
|
|
|
|
`006_scheduling_and_locale.sql` (alt additivt): `tournament.end_date`,
|
|
|
|
|
|
`session.scheduled_at`/`tee_interval_minutes`/`start_hole`,
|
|
|
|
|
|
`match.tee_time_override`, `app_user.preferred_locale`,
|
|
|
|
|
|
`magic_link_token.locale`. `match.tee_time` er UTLEDET i Python
|
|
|
|
|
|
(`scheduled_at + (sequence-1)*tee_interval_minutes`, override vinner hvis
|
|
|
|
|
|
satt) — aldri lagret per match. Full retrofit av ALLE 39 daværende
|
|
|
|
|
|
`HTTPException(..., detail="norsk streng")`-steder på tvers av 6 filer til
|
|
|
|
|
|
en delt `app_error(status_code, code, message)`-factory
|
|
|
|
|
|
(`app/errors.py`) — responsformen er nå konsekvent
|
|
|
|
|
|
`{"detail":{"code":...,"message":...}}` i hele API-et, verifisert med et
|
|
|
|
|
|
siste `grep -rn 'detail="' app/` som ga NULL treff. i18n: `locale`
|
|
|
|
|
|
(`nb`/`en`) sendes av klienten ved `request-link`, styrer e-postmalen
|
|
|
|
|
|
(ekte engelsk mal lagt inn i `app/email.py`, ikke bare rørlegging) OG
|
|
|
|
|
|
settes som en HELT NY brukers `preferred_locale` — en eksisterende bruker
|
|
|
|
|
|
som logger inn på et annet språk får IKKE sin lagrede preferanse
|
|
|
|
|
|
overskrevet.
|
|
|
|
|
|
**Fant og fikset underveis:** `tournament.start_date` har ligget i
|
|
|
|
|
|
skjemaet siden migrasjon 001, men var ALDRI koblet til
|
|
|
|
|
|
`TournamentCreate`/`Tournament`-modellene — funnet som en naturlig
|
|
|
|
|
|
bivirkning av å legge til `end_date`.
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (001→006,
|
|
|
|
|
|
`test_isolation.sql` fortsatt 12/12): feilkode-form bekreftet på tvers av
|
|
|
|
|
|
5 filer (`NOT_FOUND`/`LIMIT_REACHED` tournaments.py, `DUPLICATE` roster,
|
|
|
|
|
|
`NOT_AUTHENTICATED` uten cookie, `NOT_ROSTERED_ON_TEAM` matches.py),
|
|
|
|
|
|
tee_time-beregning bekreftet (10 min intervall → 08:00/08:10/08:20,
|
|
|
|
|
|
override vinner, `scheduled_at=null` gir `tee_time:null` ikke feil), full
|
|
|
|
|
|
i18n-runde bekreftet (ny bruker `locale:"en"` → `preferred_locale:"en"`;
|
|
|
|
|
|
påfølgende `request-link` for samme bruker med `locale:"nb"` skiftet
|
|
|
|
|
|
e-postmalen men IKKE den lagrede preferansen, bekreftet via `/auth/me`).
|
|
|
|
|
|
**Kjørt mot ekte `teecup_db` 2026-07-17** (bruker bekreftet eksplisitt i
|
|
|
|
|
|
samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12
|
|
|
|
|
|
(ruller alltid tilbake, ingen domenerader berørt).
|
|
|
|
|
|
**Mindre driftslærdom, funnet OG rettet samme runde:** et `.env`-filter
|
|
|
|
|
|
(`grep -v -i 'pass|secret|key'`) jeg brukte for å lese ikke-sensitive
|
|
|
|
|
|
nøkler fanget ikke opp `TEECUP_DATABASE_URL`, som bar `teecup_app`-
|
|
|
|
|
|
passordet innebygd i selve URL-en (i tillegg til at det SAMME passordet
|
|
|
|
|
|
allerede lå rent i `TEECUP_APP_PASSWORD` — duplisert, ikke bare skjult) —
|
|
|
|
|
|
passordet ble dermed synlig i et verktøyresultat. Flagget til brukeren
|
|
|
|
|
|
umiddelbart (samme mønster som SMTP-passord-hendelsen over).
|
|
|
|
|
|
- **`.env`/tilkobling ryddet opp (2026-07-17), samme runde:** roten til
|
|
|
|
|
|
hendelsen over var at `TEECUP_DATABASE_URL` var én sammensatt DSN-streng
|
|
|
|
|
|
med passordet URL-kodet inni — usynlig for navnebaserte secret-filtre.
|
|
|
|
|
|
Erstattet med fem separate, rent navngitte felt:
|
|
|
|
|
|
`TEECUP_DB_HOST`/`TEECUP_DB_PORT`/`TEECUP_DB_NAME`/`TEECUP_DB_USER`/
|
|
|
|
|
|
`TEECUP_DB_PASS` (sistnevnte omdøpt fra `TEECUP_APP_PASSWORD`, kun
|
|
|
|
|
|
nøkkelnavnet — verdien aldri lest eller skrevet av meg). `app/config.py`
|
|
|
|
|
|
og `app/db.py` bygger nå `asyncpg`-poolen fra disse fem separate feltene
|
|
|
|
|
|
(`host=`/`port=`/`user=`/`password=`/`database=`) i stedet for én DSN —
|
|
|
|
|
|
fjerner også URL-prosentkoding-problemet fra SMTP-passord-hendelsen sin
|
|
|
|
|
|
klasse av feil. `docker-compose.yml` sin `environment:`-liste oppdatert
|
|
|
|
|
|
tilsvarende. **Krevde et fullt image-rebuild, ikke bare
|
|
|
|
|
|
`--force-recreate`:** `Dockerfile` sin `COPY app/ app/` bakes inn i
|
|
|
|
|
|
imaget ved build-tid (ingen bind-mount i prod, i motsetning til
|
|
|
|
|
|
scratch-verifiseringens engangscontainere) — en ren `--force-recreate`
|
|
|
|
|
|
gjenbrukte det GAMLE imaget og krasjet umiddelbart på den nå fjernede
|
|
|
|
|
|
`TEECUP_DATABASE_URL`. Rettet med `docker compose up -d --build
|
|
|
|
|
|
--force-recreate`. **Verifisert ende-til-ende mot den live stacken:**
|
|
|
|
|
|
containeren boot-et rent (`Application startup complete` i loggen, som
|
|
|
|
|
|
krever en vellykket `init_pool()` — asyncpg ville kastet og forhindret
|
|
|
|
|
|
akkurat den logglinjen ved feil tilkoblingsparametre), `/auth/me` over
|
|
|
|
|
|
ekte https ga et rent `401 NOT_AUTHENTICATED` (ikke 500/502), `teeoff.no`
|
|
|
|
|
|
upåvirket (`200` gjennom hele omstarten, kun `teecup_api` restartet — ikke
|
|
|
|
|
|
delt Caddy-instans, ikke samme risikoklasse som Caddy-hendelsen fra
|
|
|
|
|
|
containeriseringsrunden).
|
|
|
|
|
|
|
|
|
|
|
|
- **Frontend startet, innlogging LIVE (2026-07-17, ADR-016):** første
|
|
|
|
|
|
frontend-skjerm i prosjektet. Designet i V0 (Next.js + Tailwind +
|
|
|
|
|
|
shadcn/ui), hentet inn som `frontend/` — merkevare-form/farge fra
|
|
|
|
|
|
Teeoff-logoen, IKKE navn/logo (egne, separate produkter, se ADR-009).
|
|
|
|
|
|
**Kvalitetsrunde før bruk:** V0s fargetokens var OKLCH-TILNÆRMINGER, ikke
|
|
|
|
|
|
eksakte — regnet ut presise verdier fra `#8bc24a`/`#ff5722` og rettet alle
|
|
|
|
|
|
6 forekomster i `globals.css`. Fjernet `@vercel/analytics` (unødvendig på
|
|
|
|
|
|
egen-hostet infra), fjernet `typescript: { ignoreBuildErrors: true }`
|
|
|
|
|
|
(ekte typesjekk kjører nå), fjernet dødt `pnpm.overrides`-felt.
|
|
|
|
|
|
`frontend/.gitignore` manglet `.pnpm-store/` — årsaken til at brukerens
|
|
|
|
|
|
VSCode viste 10 000 "ukjente" filer ved første commit-forsøk; rettet.
|
|
|
|
|
|
**Kablet mot ekte API:** `next.config.mjs` sin `rewrites()` proxyer
|
|
|
|
|
|
`/auth/*`/`/orgs/*`/`/health` server-side til `teecup_api` — same-origin,
|
|
|
|
|
|
ingen CORS, cookie uendret (full begrunnelse i ADR-016). Login-skjermet
|
|
|
|
|
|
sender ekte `POST /auth/request-link`; ny `/verify`-side mottar
|
|
|
|
|
|
`?token=...` fra e-postlenken (auto-verifiserer) eller viser et manuelt
|
|
|
|
|
|
"lim inn koden"-felt. `app/email.py` fikk en ny `PUBLIC_BASE_URL`-
|
|
|
|
|
|
innstilling og sender nå en EKTE klikkbar lenke (koden beholdes som
|
|
|
|
|
|
fallback).
|
|
|
|
|
|
**Reell fallgruve funnet og fikset ved containerisering:** Next.js sin
|
|
|
|
|
|
`rewrites()` løses ved BUILD-tid for `output: "standalone"`, ikke ved
|
|
|
|
|
|
container-oppstart — en runtime `-e TEECUP_API_ORIGIN=...` ble stille
|
|
|
|
|
|
ignorert (proxy-kall feilet med `ECONNREFUSED` mot `localhost:8000`).
|
|
|
|
|
|
Løst med en Docker build-time `ARG TEECUP_API_ORIGIN` i
|
|
|
|
|
|
`frontend/Dockerfile`, satt via `docker-compose.yml` sin `build.args`.
|
|
|
|
|
|
**Rullet ut live:** ny `teecup_frontend`-tjeneste i `docker-compose.yml`.
|
|
|
|
|
|
Caddy (`teecup.teeoff.no`, i det SEPARATE `teeoff`-repoet,
|
|
|
|
|
|
`/opt/teeoff/deploy/Caddyfile`) endret fra å peke direkte på `teecup_api`
|
|
|
|
|
|
til å peke på `teecup_frontend` — samme stale-inode-oppførsel som
|
|
|
|
|
|
containeriseringsrunden (graceful `reload` plukket IKKE opp endringen,
|
|
|
|
|
|
`/` fortsatte å gi `teecup_api` sin egen 404 i stedet for innloggingssiden
|
|
|
|
|
|
til reload faktisk skjedde). Løst likt: full `docker restart
|
|
|
|
|
|
teeoff_caddy`, brukeren bekreftet eksplisitt på forhånd. `teeoff.no`
|
|
|
|
|
|
upåvirket gjennom hele omstarten.
|
|
|
|
|
|
**Verifisert med FAKTISK e-postlevering:** ekte magic-link sendt til
|
|
|
|
|
|
brukerens egen adresse over `https://teecup.teeoff.no`, ekte e-post
|
|
|
|
|
|
mottatt med en ekte klikkbar lenke, åpnet i nettleser, landet på en
|
|
|
|
|
|
fungerende `/verify`-side, sesjon opprettet — brukeren bekreftet innlogget
|
|
|
|
|
|
status. Første gang en hel bruker-vendt flyt er bevist ende-til-ende i
|
|
|
|
|
|
produksjon, ikke bare API-et isolert.
|
|
|
|
|
|
**Merk for neste økt:** `deploy/Caddyfile`-endringen ligger uncommitted i
|
|
|
|
|
|
det SEPARATE `/opt/teeoff`-repoet, ikke i `teecup`-repoet — lett å glemme
|
|
|
|
|
|
siden denne økten ellers kun har jobbet i `/opt/teecup`.
|
|
|
|
|
|
- **Dashboard-skjerm LIVE (2026-07-18):** andre V0-skjerm — organisasjon-
|
|
|
|
|
|
bytter/-opprettelse + turneringsliste (`/dashboard`), samme mønster som
|
|
|
|
|
|
login-runden. **Reell integrasjonsfelle unngått:** V0s eksport denne gangen
|
|
|
|
|
|
var en FULL re-eksport av hele prosjektet (inkl. `login-form.tsx`,
|
|
|
|
|
|
`next.config.mjs`, `package.json`), ikke bare de nye filene — en naiv
|
|
|
|
|
|
utpakking ville stille reversert `rewrites()`-proxyen, `output:
|
|
|
|
|
|
"standalone"`, den ekte fetch-kablingen i login-skjemaet, og alle
|
|
|
|
|
|
V0-uavhengige opprydninger fra forrige runde. Løst ved å pakke ut til et
|
|
|
|
|
|
scratch-område FØRST, diffe mot live-treet fil for fil, og kun ta inn det
|
|
|
|
|
|
som faktisk var nytt (`dashboard.tsx`, `tournament-card.tsx`,
|
|
|
|
|
|
`tournament-status-badge.tsx`, `wordmark.tsx`, `badge.tsx`,
|
|
|
|
|
|
`dropdown-menu.tsx`, `app/dashboard/page.tsx`) — `next.config.mjs`,
|
|
|
|
|
|
`package.json`, `globals.css`, Docker-filene ble bevisst IKKE overskrevet.
|
|
|
|
|
|
`login-form.tsx` fikk en kirurgisk patch (kun Wordmark flyttet til egen
|
|
|
|
|
|
fil, som V0 selv hadde gjort — all egen fetch-/feilhåndteringslogikk
|
|
|
|
|
|
urørt).
|
|
|
|
|
|
**`dashboard.tsx` sitt datalag skrevet om fra bunnen** (V0 leverte kun
|
|
|
|
|
|
mock `useState`): henter `/auth/me` for organisasjonsmedlemskap,
|
|
|
|
|
|
`/orgs/{id}/tournaments` per valgt org, `POST /orgs`/`POST
|
|
|
|
|
|
/orgs/{id}/tournaments` for opprettelse, `POST /auth/logout` for utlogging
|
|
|
|
|
|
— presentasjonskomponentene (kort, bytter, tomme tilstander) beholdt
|
|
|
|
|
|
uendret fra V0. `/verify`-siden oppdatert til å sende brukeren videre til
|
|
|
|
|
|
`/dashboard` etter vellykket innlogging (fantes ingen dit å gå før nå).
|
|
|
|
|
|
**Verifisert:** ekte typesjekket build, redeploy av kun `teecup_frontend`
|
|
|
|
|
|
(ingen Caddy-endring nødvendig denne gangen — ADR-016s mønster holder),
|
|
|
|
|
|
`teecup.teeoff.no/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
**Skrive-flyten bekreftet med EKTE data samme dag (brukeren testet selv,
|
|
|
|
|
|
ikke meg):** organisasjon "Tjøme Gents" og turnering "De Gamle er Eldst"
|
|
|
|
|
|
opprettet via UI-et mot den ekte `teecup_db` — statusmerket viste riktig
|
|
|
|
|
|
"Utkast", "Ingen datoer satt" håndtert korrekt (ingen krasj på manglende
|
|
|
|
|
|
dato), ett-org-visningen viste riktig uten unødvendig bytter-UI. Første
|
|
|
|
|
|
gang en HEL skrive-flyt (ikke bare lesing) er bevist ende-til-ende fra
|
|
|
|
|
|
frontend mot ekte produksjonsdata.
|
|
|
|
|
|
- **Lag/roster-skjerm LIVE (2026-07-18):** tredje V0-skjerm
|
|
|
|
|
|
(`/tournaments/[id]`), samme re-eksport-mønster som dashboard-runden —
|
|
|
|
|
|
diffet mot live-treet, tok kun inn `tournament-detail.tsx` og et
|
|
|
|
|
|
`Link`-basert `tournament-card.tsx` (navigasjon fra dashbordet).
|
|
|
|
|
|
**URL-design bevisst avvikende fra V0s forslag:** V0s genererte side leste
|
|
|
|
|
|
aldri `params.id` og hadde ingen organization_id i det hele tatt — holdt
|
|
|
|
|
|
derfor V0s flate `/tournaments/[id]`-struktur (i stedet for en nøstet
|
|
|
|
|
|
`/orgs/[orgId]/tournaments/[id]`, som ville krevd manuell ombygging ved
|
|
|
|
|
|
HVER fremtidig V0-reeksport) og la `org`+`name` til som søkeparametre i
|
|
|
|
|
|
`tournament-card.tsx` sin lenke — API-et krever organization_id på alle
|
|
|
|
|
|
team-/roster-kall (RLS).
|
|
|
|
|
|
**Reelt hull funnet FØR integrering, ikke etter:** V0-skjermen bygger inn
|
|
|
|
|
|
"fjern spiller"/"gjør til kaptein"-handlinger, men backend hadde KUN
|
|
|
|
|
|
GET/POST på `team_roster` — ingen DELETE eller PATCH. Spurte bruker
|
|
|
|
|
|
eksplisitt (samme mønster som andre scope-avklaringer denne økten) — svar:
|
|
|
|
|
|
bygg de to endepunktene nå. Lagt til i `app/routers/tournaments.py`:
|
|
|
|
|
|
`PATCH .../roster/{roster_id}` (bevisst enkel — setter/fjerner
|
|
|
|
|
|
`is_captain` på NØYAKTIG denne raden, håndhever IKKE "kun én kaptein per
|
|
|
|
|
|
lag", siden kaptein fortsatt bare er et merke, ikke en egen
|
|
|
|
|
|
autorisasjonsrolle) og `DELETE .../roster/{roster_id}` (204, idempotent
|
|
|
|
|
|
`NOT_FOUND` ved dobbel sletting — ikke krasj).
|
|
|
|
|
|
**`tournament-detail.tsx` sitt datalag skrevet om fra V0s mock:** henter
|
|
|
|
|
|
lag + roster (roster-radene bærer allerede `display_name`/
|
|
|
|
|
|
`handicap_index_snapshot` fra APIet, så V0s separate `poolById`-oppslag
|
|
|
|
|
|
ble fjernet som overflødig) og organisasjonens spillerpool
|
|
|
|
|
|
(`GET /orgs/{id}/players`, brukt til type-ahead ved "legg til spiller").
|
|
|
|
|
|
Alle fem mutasjonene (opprett lag, legg til eksisterende spiller, opprett
|
|
|
|
|
|
ny spiller inline + rostre, endre kaptein, fjern fra roster) kablet mot
|
|
|
|
|
|
ekte endepunkter — presentasjonskomponentene (kort, type-ahead,
|
|
|
|
|
|
fargevelger, bekreft-fjerning) beholdt uendret fra V0.
|
|
|
|
|
|
**Verifisert:** de to nye endepunktene testet mot en fersk
|
|
|
|
|
|
`teecup_scratch` (PATCH setter kaptein + riktig `NOT_FOUND` på ugyldig id,
|
|
|
|
|
|
DELETE gir 204 + idempotent `NOT_FOUND` ved gjentak, `test_isolation.sql`
|
|
|
|
|
|
fortsatt 12/12), ekte typesjekket frontend-build (5 ruter), redeploy av
|
|
|
|
|
|
BEGGE containere (backend-endepunktene er nye), `teecup.teeoff.no/dashboard`
|
|
|
|
|
|
→ 200, `teeoff.no` upåvirket. Selve skrive-flyten på `/tournaments/[id]`
|
|
|
|
|
|
(opprett lag/roster) ikke testet med ekte data i denne runden — venter på
|
|
|
|
|
|
brukeren, samme mønster som dashboard-rundens skrive-test.
|
|
|
|
|
|
- **ADR-017 + migrasjon 007 (2026-07-18):** brukeren reiste selvregistrering
|
|
|
|
|
|
rett etter at roster-skrive-flyten var bekreftet. Full ADR skrevet
|
|
|
|
|
|
(5 beslutninger: offentlig påmelding uten innlogging, e-post som
|
|
|
|
|
|
sammenkoblingsnøkkel mot forhåndsopprettede spillere, egen
|
|
|
|
|
|
`tournament_registration`-tabell atskilt fra `team_roster` med
|
|
|
|
|
|
konfigurerbar godkjenning/kapasitet/venteliste, utvidet spillerprofil +
|
|
|
|
|
|
obligatorisk samtykke, og `public_tournament_org()`). Migrasjon
|
|
|
|
|
|
`007_registration_and_player_fields.sql` skrevet og scratch-verifisert
|
|
|
|
|
|
(001→007 kjører rent, `test_isolation.sql` fortsatt 12/12).
|
|
|
|
|
|
**Reelt arkitekturproblem løst underveis, ikke bare skjema:** et
|
|
|
|
|
|
offentlig (uautentisert) påmeldingskall kjenner en turnering-id, men
|
|
|
|
|
|
ingen org-kontekst — og uten `app.current_org` slipper RLS ingen rader
|
|
|
|
|
|
gjennom, heller ikke oppslaget for å FINNE riktig org. Løst med en snever
|
|
|
|
|
|
`SECURITY DEFINER`-funksjon (`public_tournament_org`) som KUN eksponerer
|
|
|
|
|
|
uuid→uuid-koblingen. **Verifisert presist, ikke bare "kjørte uten feil":**
|
|
|
|
|
|
kalt funksjonen som `teecup_app`-rollen med ingen `app.current_org` satt
|
|
|
|
|
|
— ga korrekt org-id for en kjent turnering, `NULL` (ikke feil) for en
|
|
|
|
|
|
ukjent — OG et RÅTT `SELECT` på `tournament` på SAMME tilkobling/rolle ga
|
|
|
|
|
|
fortsatt 0 rader, som beviser RLS ikke er brutt generelt, bare dette ene
|
|
|
|
|
|
smale unntaket eksisterer.
|
|
|
|
|
|
**Kjørt mot ekte `teecup_db` 2026-07-18** (bruker bekreftet eksplisitt i
|
|
|
|
|
|
samme økt): migrasjonen kjørte rent, `test_isolation.sql` fortsatt 12/12.
|
|
|
|
|
|
- **Registrerings-API LIVE (2026-07-18), samme dag:** ny
|
|
|
|
|
|
`app/routers/registration.py` — `GET /public/tournaments/{id}` og
|
|
|
|
|
|
`POST /public/tournaments/{id}/register`, begge UTEN `get_current_user`
|
|
|
|
|
|
eller `get_authorized_org` (helt uautentisert, egen `/public`-prefiks,
|
|
|
|
|
|
bevisst atskilt fra `/orgs/...` i koden). Bruker `public_tournament_org()`
|
|
|
|
|
|
(migrasjon 007) til å slå opp org-kontekst FØR RLS kan håndheve noe.
|
|
|
|
|
|
`app/routers/players.py` utvidet med alle sju nye ADR-017-feltene
|
|
|
|
|
|
(`mobile`/`email`/`birth_date`/`nickname`/`country`/`club`/
|
|
|
|
|
|
`club_member_number`). `frontend/next.config.mjs` sin `rewrites()`
|
|
|
|
|
|
utvidet med `/public/*` (ADR-016s konsekvens: enhver ny API-prefiks MÅ
|
|
|
|
|
|
inn her).
|
|
|
|
|
|
**Ny brukers-oppdaget notat fanget FØR bygging, ikke etter:** brukeren
|
|
|
|
|
|
krevde eksplisitt at synlighet (offentlig/kun org/kun turnering-
|
|
|
|
|
|
deltakere) må være et VALG for fremtidige landingssider — notert grundig
|
|
|
|
|
|
i `FEATURE_BACKLOG.md` (koblet til samme åpne spørsmål for "Banter
|
|
|
|
|
|
Board"-feeden) FØR dette registrerings-API-et ble bygget, akkurat for at
|
|
|
|
|
|
det ikke skal gå i glemmeboken til landingsside-runden.
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (001→007,
|
|
|
|
|
|
`test_isolation.sql` 12/12): hele registreringsløpet testet reelt —
|
|
|
|
|
|
samtykke-avvisning (400), duplikat-avvisning (409 `DUPLICATE`),
|
|
|
|
|
|
e-post-matching mot en organisator-forhåndsopprettet spiller (BEKREFTET:
|
|
|
|
|
|
ingen duplikatrad, mobil fylt inn via `COALESCE`, `display_name` IKKE
|
|
|
|
|
|
overskrevet), kapasitet+`waitlist`-policy (→ `waitlisted`),
|
|
|
|
|
|
kapasitet+`closed`-policy (→ 409 `LIMIT_REACHED`), `registration_
|
|
|
|
|
|
requires_approval` (→ `pending`), utløpt frist (→ 409
|
|
|
|
|
|
`REGISTRATION_CLOSED`), og `confirmed_count` i `GET`-responsen talt
|
|
|
|
|
|
riktig (kun `confirmed`+`pending`, ikke `waitlisted`/avviste).
|
|
|
|
|
|
**Rullet ut live:** begge containere redeployet, `teecup.teeoff.no/
|
|
|
|
|
|
dashboard` og `/health` fortsatt 200, `teeoff.no` upåvirket, det
|
|
|
|
|
|
offentlige endepunktet bekreftet nåbart over ekte https (ukjent
|
|
|
|
|
|
turnering-id ga korrekt `404`/`NOT_FOUND`, ikke-destruktiv sjekk — selve
|
|
|
|
|
|
påmeldingsflyten med ekte data ikke testet mot prod i denne runden).
|
|
|
|
|
|
**E-post-basert kontosammenkobling LIVE, samme dag:** ny migrasjon
|
|
|
|
|
|
`008_link_player_by_email.sql` — `link_player_by_email(user_id, email)`,
|
|
|
|
|
|
samme `SECURITY DEFINER`-mønster som `public_tournament_org()` (007),
|
|
|
|
|
|
denne gangen for en tverr-org UPDATE i stedet for et lese-oppslag.
|
|
|
|
|
|
`verify_magic_link` (`app/routers/auth.py`) kaller den på HVER
|
|
|
|
|
|
innlogging (idempotent — funksjonens `WHERE user_id IS NULL` gjør
|
|
|
|
|
|
gjentatte kall til en no-op), ikke bare ved førstegangsopprettelse.
|
|
|
|
|
|
**Verifisert presist:** to separate org-er, hver med sin egen
|
|
|
|
|
|
organisator-opprettede "Kari"-rad (samme e-post, ulik store/små
|
|
|
|
|
|
bokstaver for å teste case-insensitivitet også) — ved Karis FØRSTE
|
|
|
|
|
|
innlogging ble BEGGE radene koblet til kontoen hennes, i to org-er hun
|
|
|
|
|
|
aldri har vært medlem av. Eksplisitt bekreftet: `organization_membership`
|
|
|
|
|
|
har NULL rader for henne etterpå — ren identitetskobling, ingen
|
|
|
|
|
|
privilegie-eskalering (å ha `player.user_id` satt gir ingen ny tilgang
|
|
|
|
|
|
gjennom `get_authorized_org`, som fortsatt krever ekte org-medlemskap
|
|
|
|
|
|
uavhengig av dette). Andre innlogging idempotent, ingen feil.
|
|
|
|
|
|
`test_isolation.sql` fortsatt 12/12. **Kjørt mot ekte `teecup_db`
|
|
|
|
|
|
2026-07-18**, bruker bekreftet eksplisitt, backend redeployet, live
|
|
|
|
|
|
sjekker OK, `teeoff.no` upåvirket.
|
|
|
|
|
|
**ADR-017s backend er dermed komplett** (registrering + kontokobling).
|
|
|
|
|
|
Gjenstående: frontend-påmeldingsskjema og landingssider — egen, senere
|
|
|
|
|
|
ADR-runde (se `FEATURE_BACKLOG.md`).
|
|
|
|
|
|
- **ADR-018 + migrasjon 009: landingssider, backend LIVE (2026-07-18),
|
|
|
|
|
|
samme dag:** trenivås synlighet (`tournament.visibility`:
|
|
|
|
|
|
`public`/`org`/`participants`, default `org` — trygg standard) +
|
|
|
|
|
|
`organization.public_profile`. **Reell presisering funnet underveis, ikke
|
|
|
|
|
|
antatt på forhånd:** RLS (`org_isolation`) beskytter kun TENANT-grenser
|
|
|
|
|
|
(org A ser aldri org B), IKKE innholds-synlighet innenfor riktig
|
|
|
|
|
|
org-kontekst — det eksisterende `GET /public/tournaments/{id}` (ADR-017)
|
|
|
|
|
|
leste allerede fullt innhold uten synlighetssjekk, fordi RLS er fornøyd
|
|
|
|
|
|
så snart org-konteksten er satt, uansett hvem som spør. `visibility`
|
|
|
|
|
|
håndheves derfor eksplisitt i `app/routers/registration.py`, på BÅDE
|
|
|
|
|
|
lesing og registrering (ADR-018 Beslutning D: kan du ikke se turneringen,
|
|
|
|
|
|
kan du heller ikke melde deg på den — bekreftet med bruker FØR bygging).
|
|
|
|
|
|
Ny `get_current_user_optional` i `app/auth.py` (som `get_current_user`,
|
|
|
|
|
|
men returnerer `None` i stedet for 401 — offentlige endepunkter skal
|
|
|
|
|
|
fungere for anonyme lesere også). Ny "deltaker"-autorisasjonsvei: en
|
|
|
|
|
|
innlogget bruker med `player.user_id` koblet (ADR-017) OG en
|
|
|
|
|
|
`tournament_registration`- eller `team_roster`-rad for NØYAKTIG den
|
|
|
|
|
|
turneringen får se `participants`-synlige turneringer, uansett
|
|
|
|
|
|
org-medlemskap.
|
|
|
|
|
|
**Ekte migrasjonsfeil funnet OG rettet UNDER scratch-verifisering, ikke
|
|
|
|
|
|
antatt riktig:** migrasjonen feilet først ("column slug already exists")
|
|
|
|
|
|
— `organization.slug` har ligget i skjemaet siden migrasjon 001
|
|
|
|
|
|
("f.eks. subdomene/URL-vennlig", allerede med en plain `UNIQUE`), noe jeg
|
|
|
|
|
|
hadde oversett fullstendig og forsøkt å legge til på nytt. Rettet ved å
|
|
|
|
|
|
fjerne den doble `ADD COLUMN` + den overflødige partielle unik-indeksen
|
|
|
|
|
|
(001 sin plain `UNIQUE` dekker "unik når satt" allerede, siden Postgres
|
|
|
|
|
|
behandler NULL som distinkt), beholde kun de nye `CHECK`-constraintene.
|
|
|
|
|
|
Kjørte rent på ny etter fiksen. **Lærdom:** grep alltid eksisterende
|
|
|
|
|
|
skjema for feltnavn FØR en ny migrasjon skrives, ikke bare stol på
|
|
|
|
|
|
hukommelsen om hva som "sikkert" ikke finnes fra før.
|
|
|
|
|
|
Ny tabell `tournament_sponsor` (navn+lenke aktivt, `logo_key` inert til
|
|
|
|
|
|
MinIO-runden — samme med `tournament.hero_image_key`). Tredje
|
|
|
|
|
|
`SECURITY DEFINER`-bro i prosjektet: `public_org_by_slug()` (etter
|
|
|
|
|
|
`public_tournament_org` 007, `link_player_by_email` 008) — returnerer
|
|
|
|
|
|
`NULL` for BÅDE "finnes ikke" og "finnes, men er privat", samme
|
|
|
|
|
|
anti-enumerering som magic-link.
|
|
|
|
|
|
**Fylte også et implisitt hull oppdaget underveis:** ADR-en beskrev
|
|
|
|
|
|
hvordan synlighet skulle håndheves, men ingen tidligere runde hadde bygget
|
|
|
|
|
|
noen vei for organisator til faktisk å SETTE disse feltene. Lagt til:
|
|
|
|
|
|
`PATCH /orgs/{id}/tournaments/{id}` (visibility/description/
|
|
|
|
|
|
registrerings-innstillinger, ekte PATCH-semantikk via Pydantic sin
|
|
|
|
|
|
`exclude_unset` — et utelatt felt nullstilles IKKE), `PATCH /orgs/{id}`
|
|
|
|
|
|
(slug/public_profile), full sponsor-CRUD. `_fetch_sessions()` trukket ut
|
|
|
|
|
|
som delt hjelpefunksjon i `tournaments.py` (delt mellom den innloggede
|
|
|
|
|
|
og den nye offentlige `GET /public/tournaments/{id}/sessions` — blind
|
|
|
|
|
|
draw-hemmelighold, ADR-013, arves automatisk, ikke reimplementert).
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (001→009,
|
|
|
|
|
|
`test_isolation.sql` 12/12): hele synlighetsmatrisen testet med ekte
|
|
|
|
|
|
HTTP-kall — anonym avvist på `org`-synlig turnering (både lesing OG
|
|
|
|
|
|
registrering), `PATCH` til `public` + beskrivelse + sponsor fungerte,
|
|
|
|
|
|
anonym lesing fungerte deretter, `participants`-synlighet bekreftet
|
|
|
|
|
|
reell chicken-and-egg-konsekvens av Beslutning D (ingen kan selv-
|
|
|
|
|
|
registrere seg til en `participants`-synlig turnering, kun organisator
|
|
|
|
|
|
kan legge til direkte — korrekt, ikke en bug), en organisator-rostret
|
|
|
|
|
|
spiller som logget inn fikk tilgang, en tilfeldig innlogget FREMMED
|
|
|
|
|
|
(ikke deltaker) ble fortsatt avvist, org-landingsside viste KUN
|
|
|
|
|
|
`public`-synlige turneringer, `CHECK`-constraint (`public_profile`
|
|
|
|
|
|
krever `slug`) avvist korrekt, ugyldig slug-format avvist av Pydantic,
|
|
|
|
|
|
sponsor-sletting fungerte.
|
|
|
|
|
|
**Kjørt mot ekte `teecup_db` 2026-07-18**, bruker bekreftet eksplisitt,
|
|
|
|
|
|
backend redeployet, live sjekker OK, `teeoff.no` upåvirket. Ingen
|
|
|
|
|
|
frontend-endring nødvendig for selve API-tilgangen (det brede
|
|
|
|
|
|
`/public/:path*`-mønsteret fra ADR-016 dekker allerede `/public/orgs/*`).
|
|
|
|
|
|
**Gjenstår:** selve landingsside-SKJERMENE i frontend (V0), og
|
|
|
|
|
|
MinIO/bildeopplasting — bevisst utsatt, egen runde.
|
|
|
|
|
|
- **Offentlig turnering-landingsside LIVE (2026-07-18), samme dag:** fjerde
|
|
|
|
|
|
V0-skjerm (`components/public-tournament.tsx`, ny rute `/t/[id]`) —
|
|
|
|
|
|
banner (bevisst permanent farge-/gradient-utseende, ikke en "bilde
|
|
|
|
|
|
kommer"-plassholder), presenterende tekst, status (datoer + "X av Y
|
|
|
|
|
|
plasser"), program, sponsorer (navn+lenke), og et påmeldingsskjema med
|
|
|
|
|
|
navn+e-post synlig først og resten bak en "flere detaljer"-utvidelse —
|
|
|
|
|
|
tre distinkte bekreftelsestilstander (bekreftet/venteliste/godkjenning
|
|
|
|
|
|
venter), ikke én generisk "takk".
|
|
|
|
|
|
**Ingen ny reeksport-kollisjon denne gangen** — kun étt genuint nytt
|
|
|
|
|
|
filnavn (`public-tournament.tsx`), resten var kjent V0-revert av
|
|
|
|
|
|
allerede-tilpassede filer, samme mønster som før.
|
|
|
|
|
|
**V0 opprettet komponenten, men ingen rute** — la selv til `app/t/[id]/
|
|
|
|
|
|
page.tsx` (bevisst en FLAT `/t/[id]`-sti, ikke nøstet under `/orgs/...`
|
|
|
|
|
|
som den innloggede turnering-detalj-siden, siden det offentlige API-et
|
|
|
|
|
|
kun trenger turnering-id, ikke org-id).
|
|
|
|
|
|
**Datalaget skrevet om fra V0s mock:** ekte `fetch` mot
|
|
|
|
|
|
`GET /public/tournaments/{id}` + `/sessions`, ekte `POST .../register`.
|
|
|
|
|
|
Håndterer 403 (`NOT_VISIBLE`, ADR-018) og 404 med en egen tilgang-avvist-
|
|
|
|
|
|
tilstand V0 ikke hadde bedt om (fantes ikke i prompten, men trengs for at
|
|
|
|
|
|
siden faktisk skal fungere for `org`/`participants`-synlige turneringer).
|
|
|
|
|
|
Mappet `status:"waitlisted"` fra API-et til komponentens `"waitlist"`, og
|
|
|
|
|
|
skjemaets norske kjønnsvalg ("kvinne"/"mann"/"annet") til API-ets
|
|
|
|
|
|
`^[mfx]$`-mønster — to reelle navnekollisjoner mellom V0s UI-språk og
|
|
|
|
|
|
API-kontrakten, ikke bare et rett-frem felt-for-felt-uttrekk.
|
|
|
|
|
|
**Reell driftsfeil funnet OG rettet under scratch-test, ikke i
|
|
|
|
|
|
produksjon:** `pnpm install` (uten `--ignore-scripts`) i dev-server-
|
|
|
|
|
|
testoppsettet feilet stille med tom logg og exit 1 — corepack hadde
|
|
|
|
|
|
hentet en NY pnpm-versjon (11.13.1 → 11.14.0) siden sist, som gjør
|
|
|
|
|
|
"ignored builds"-varselet til en hard feil i stedet for bare en
|
|
|
|
|
|
advarsel. Rettet ved å bruke samme `--ignore-scripts`-flagg som
|
|
|
|
|
|
`frontend/Dockerfile` allerede bruker (upåvirket av denne — bekreftet
|
|
|
|
|
|
ved at selve prod-buildet fortsatt gikk rent). **Lærdom:** `Dockerfile`
|
|
|
|
|
|
sin pinning av *kode* er ikke det samme som å pinne *verktøyene rundt*
|
|
|
|
|
|
(corepack henter alltid siste pnpm) — verdt å huske neste gang et
|
|
|
|
|
|
scratch-dev-server-oppsett plutselig feiler uten åpenbar grunn.
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch` + en ekte kjørende
|
|
|
|
|
|
frontend-dev-server** (ikke bare `next build`): ekte turnering med
|
|
|
|
|
|
beskrivelse, kapasitet, sponsor og økt opprettet via API-et, hentet
|
|
|
|
|
|
gjennom frontend-proxyen (ikke direkte mot backend) og bekreftet
|
|
|
|
|
|
byte-for-byte riktig — inkludert en ekte `POST`-registrering som økte
|
|
|
|
|
|
`confirmed_count` fra 0 til 1.
|
|
|
|
|
|
**Rullet ut live**, kun `teecup_frontend` (ingen backend-endring denne
|
|
|
|
|
|
runden), `teeoff.no` upåvirket.
|
|
|
|
|
|
**Gjenstår:** org-landingssiden (egen V0-prompt), Open Graph-metadata for
|
|
|
|
|
|
deling, MinIO/bilder.
|
|
|
|
|
|
- **Offentlig klubb-landingsside LIVE (2026-07-18), samme dag:** femte og
|
|
|
|
|
|
siste V0-skjerm i ADR-018 (`components/public-club.tsx`, ny rute
|
|
|
|
|
|
`/clubs/[slug]`). Samme banner-språk som turnering-siden, liste over
|
|
|
|
|
|
klubbens turneringer (gjenbrukte eksisterende `TournamentCard`), egen tom-
|
|
|
|
|
|
tilstand.
|
|
|
|
|
|
**Reell delt-komponent-kollisjon løst, ikke duplisert bort:**
|
|
|
|
|
|
`TournamentCard` var bygget for KUN den innloggede konteksten (krevde
|
|
|
|
|
|
`orgId`, lenket til `/tournaments/{id}?org=...`). I stedet for en egen
|
|
|
|
|
|
kopi av kortet for den offentlige siden, gjort `orgId` valgfri —
|
|
|
|
|
|
satt (dashbordet) gir innlogget lenke, utelatt (klubbsiden) gir
|
|
|
|
|
|
`/t/{id}` i stedet. Samme kort, to kontekster, ingen duplisering.
|
|
|
|
|
|
Bekreftet dashbordets egen bruk uendret/upåvirket etterpå.
|
|
|
|
|
|
**V0 opprettet denne gangen selv en rute** (`app/clubs/[id]/page.tsx`,
|
|
|
|
|
|
uten noen props sendt inn i det hele tatt) — men navnga parameteren
|
|
|
|
|
|
`[id]` selv om den faktisk er en SLUG (`public_org_by_slug()`, ADR-018).
|
|
|
|
|
|
Skrev selv en ny, riktig `app/clubs/[slug]/page.tsx` i stedet for å bruke
|
|
|
|
|
|
V0s (feilnavngitte og prop-løse) versjon.
|
|
|
|
|
|
**Verifisert mot fersk `teecup_scratch` + ekte kjørende frontend-dev-
|
|
|
|
|
|
server:** org med slug+`public_profile`, én turnering satt `public`, én
|
|
|
|
|
|
latt stå på default `org` — klubbsiden viste GJENNOM frontend-proxyen
|
|
|
|
|
|
kun den ene offentlige turneringen, den org-private var korrekt
|
|
|
|
|
|
usynlig (samme filtermønster som API-et selv, bekreftet fra
|
|
|
|
|
|
klientsiden også). Ukjent slug ga korrekt 404.
|
|
|
|
|
|
**Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket.
|
|
|
|
|
|
**ADR-018s planlagte skjermer er dermed komplette.** Gjenstår: Open
|
|
|
|
|
|
Graph-metadata for deling, MinIO/bilder — begge bevisst egne, senere
|
|
|
|
|
|
runder.
|
|
|
|
|
|
- **Open Graph-metadata LIVE (2026-07-18), samme dag — ADR-018 dermed
|
|
|
|
|
|
helt ferdig:** `generateMetadata()` lagt til på `/t/[id]` og
|
|
|
|
|
|
`/clubs/[slug]` (ekte tittel/beskrivelse fra API-et, `og:site_name`,
|
|
|
|
|
|
trygg fallback-tittel ved ukjent id/slug — feil her skal ALDRI hindre
|
|
|
|
|
|
selve siden i å laste).
|
|
|
|
|
|
**Reell driftsfeil funnet FØR den nådde produksjon, ikke etter:**
|
|
|
|
|
|
`generateMetadata()` kjører server-side ved REQUEST-tid, ikke i
|
|
|
|
|
|
nettleseren — går derfor IKKE gjennom `next.config.mjs` sin
|
|
|
|
|
|
`rewrites()` (som kun gjelder nettleser-trafikk inn til Next.js-
|
|
|
|
|
|
serveren). Måtte derfor lese `TEECUP_API_ORIGIN` direkte, men den
|
|
|
|
|
|
variabelen fantes KUN i `Dockerfile` sitt builder-steg — `ENV` satt i
|
|
|
|
|
|
ett `FROM`-steg arves ikke til et senere. Rettet ved å sette samme
|
|
|
|
|
|
`ARG`/`ENV` på nytt i runner-steget også.
|
|
|
|
|
|
**Verifisert presist at fiksen faktisk virker, ikke bare at bygget gikk
|
|
|
|
|
|
gjennom:** bygget det EKTE produksjonsimaget (ikke dev-server) pekt mot
|
|
|
|
|
|
en scratch-backend med en ekte offentlig turnering+org, hentet den
|
|
|
|
|
|
faktiske server-rendrede HTML-en og bekreftet ekte `<title>`/`og:title`/
|
|
|
|
|
|
`og:description` — ikke bare at TypeScript kompilerte. Ukjent
|
|
|
|
|
|
turnering-id ga korrekt trygg fallback-tittel.
|
|
|
|
|
|
**Bevisst utenfor omfang:** `og:image` — ingen ekte bilde finnes ennå
|
|
|
|
|
|
(MinIO-runden). La til `metadataBase` i `app/layout.tsx` nå likevel, som
|
|
|
|
|
|
forarbeid slik at et fremtidig relativt bilde-URL løses riktig uten en
|
|
|
|
|
|
egen fiks da.
|
|
|
|
|
|
**Rullet ut live**, kun `teecup_frontend`, `teeoff.no` upåvirket.
|
|
|
|
|
|
- **MinIO-runden LIVE (2026-07-18), samme dag — ADR-018 dermed HELT
|
|
|
|
|
|
ferdig, ingenting utsatt igjen:** ny `teecup-minio`-tjeneste (persistent
|
|
|
|
|
|
volum, genererte credentials i `.env`), backend-endepunkter for ekte
|
|
|
|
|
|
bildeopplasting til `tournament.hero_image_key`/`tournament_sponsor.
|
|
|
|
|
|
logo_key` (som lå inerte siden migrasjon 009), offentlige API-svar bygger
|
|
|
|
|
|
nå fulle URL-er, `og:image` koblet på i `/t/[id]` sin `generateMetadata`.
|
|
|
|
|
|
**Sent, men viktig presisert krav underveis:** brukeren avbrøt en
|
|
|
|
|
|
verktøyskall midtveis for å presisere at ALLE bilder skal konverteres
|
|
|
|
|
|
til AVIF for plassbesparelse. Snudde HELE opplastingsarkitekturen på
|
|
|
|
|
|
dette: droppet den opprinnelige planen om presignerte URL-er (nettleser
|
|
|
|
|
|
laster opp DIREKTE til MinIO) til fordel for ekte multipart-opplasting
|
|
|
|
|
|
GJENNOM API-et, som konverterer til AVIF (Pillow + pillow-avif-plugin)
|
|
|
|
|
|
FØR lagring. Dette forenklet arkitekturen betydelig: kun ÉN MinIO-klient
|
|
|
|
|
|
trengs nå (før: to separate klient-oppsett for henholdsvis internt
|
|
|
|
|
|
admin-arbeid og offentlig presignering), og Caddy sin nye rute trenger
|
|
|
|
|
|
ikke lenger bevare Host-headeren presist (relevant kun for SigV4-
|
|
|
|
|
|
signaturverifisering av presignerte URL-er, ikke for anonym public-read).
|
|
|
|
|
|
**To reelle feil funnet UNDER scratch-verifisering, aldri i produksjon:**
|
|
|
|
|
|
(1) `pillow-avif-plugin` krever ingen ekstra systempakker i
|
|
|
|
|
|
`python:3.12-slim` -- verifisert med et frittstående encode/decode-
|
|
|
|
|
|
rundtrip FØR det ble tatt i bruk i selve API-et. (2) MinIO validerer
|
|
|
|
|
|
`Host`-headeren STRENGT og avviser understrek som ugyldig vertsnavn --
|
|
|
|
|
|
et tjenestenavn med understrek (`teecup_minio`, konsistent med
|
|
|
|
|
|
`teecup_api`/`teecup_frontend`) feilet umiddelbart ved oppstart
|
|
|
|
|
|
("Invalid Request (invalid hostname)"). Bekreftet presist ved å teste
|
|
|
|
|
|
samme oppsett med bindestrek i stedet (`teecup-minio`) -- fungerte
|
|
|
|
|
|
umiddelbart. Alle referanser rettet til bindestrek FØR noe ble forsøkt
|
|
|
|
|
|
mot ekte infrastruktur.
|
|
|
|
|
|
**Caddy:** ny `/teecup-media/*`-rute på det EKSISTERENDE
|
|
|
|
|
|
`teecup.teeoff.no`-blokket (IKKE et nytt subdomene -- en tidlig sjekk
|
|
|
|
|
|
avdekket at `media.teecup.teeoff.no` fantes som en wildcard DNS-post,
|
|
|
|
|
|
men pekte til en IPv6-adresse denne serveren ikke har i det hele tatt;
|
|
|
|
|
|
path-prefiks på et allerede fungerende domene unngikk hele den DNS-
|
|
|
|
|
|
avhengigheten). Samme stale-inode-oppførsel som alle tidligere Caddy-
|
|
|
|
|
|
runder -- full `docker restart teeoff_caddy`, brukeren bekreftet
|
|
|
|
|
|
eksplisitt. `teeoff.no` upåvirket.
|
|
|
|
|
|
**Verifisert grundig, i flere lag:** frittstående AVIF-encode/decode-
|
|
|
|
|
|
test, full scratch-kjede (fersk Postgres + en ISOLERT scratch-MinIO-
|
|
|
|
|
|
container) med et ekte opplastet bilde -- bekreftet konvertert til
|
|
|
|
|
|
gyldig AVIF (800×400, 406 bytes for et helfarget testbilde), bekreftet
|
|
|
|
|
|
lagret med riktig nøkkel, bekreftet lesbart ANONYMT direkte mot MinIO
|
|
|
|
|
|
(uten Caddy, isolerer bucket-policyen), bekreftet `hero_image_url`/
|
|
|
|
|
|
`logo_url` bygget riktig i det offentlige API-svaret. Alle tre
|
|
|
|
|
|
valideringsveier testet (ugyldig content-type, korrupt bildeinnhold,
|
|
|
|
|
|
for stor fil >8MB) -- alle ga korrekt `VALIDATION_FAILED`. Etter
|
|
|
|
|
|
Caddy-omstart: et ekte anonymt kall mot `/teecup-media/...` på
|
|
|
|
|
|
produksjonsdomenet ga en ekte MinIO S3-XML-feilrespons (`NoSuchKey` for
|
|
|
|
|
|
en scratch-nøkkel som naturligvis ikke finnes i prod) -- beviser ruten
|
|
|
|
|
|
treffer MinIO selv, ikke frontend sin egen 404-side.
|
|
|
|
|
|
`test_isolation.sql` fortsatt 12/12 gjennom hele runden.
|
|
|
|
|
|
**Bevisst utenfor omfang:** ingen faktisk opplasting av et EKTE bilde
|
|
|
|
|
|
til en EKTE, live turnering i denne runden (ville krevd å skrive
|
|
|
|
|
|
test-data i brukerens ekte konto uten å bli spurt) -- tilbudt, ikke
|
|
|
|
|
|
utført. Selve dra-og-slipp-opplastingsskjermen i frontend (V0) er
|
|
|
|
|
|
fortsatt ikke bygget, som avtalt fra starten av runden.
|
|
|
|
|
|
|
|
|
|
|
|
- **Program-skjerm (økter/tidsplan), bygget og SCRATCH-verifisert, IKKE
|
|
|
|
|
|
live ennå (2026-07-18):** sjette V0-skjerm, første i "bygg i rekkefølgen
|
|
|
|
|
|
ting brukes"-serien (etter lag/roster: program → blind draw → scorekort →
|
|
|
|
|
|
leaderboard). `components/tournament-program.tsx`, ny rute
|
|
|
|
|
|
`/tournaments/[id]/program`. Tidslinje over økter + opprett-skjema
|
|
|
|
|
|
(format, hullomfang, scoringsmodus, poeng, klokkeslett+intervall,
|
|
|
|
|
|
starthull, kollapsbar "avansert"-seksjon med ADR-014s fire brytere).
|
|
|
|
|
|
**Reelt blokkerende hull funnet FØR integrering:** `SessionCreate.
|
|
|
|
|
|
course_id` er påkrevd, men INGEN endepunkt kunne noensinne produsere en —
|
|
|
|
|
|
ADR-004s teeoff-integrasjon er fortsatt bare vedtatt (ingen utgående
|
|
|
|
|
|
HTTP-klient finnes). Spurte bruker eksplisitt — svar: bygg enkel
|
|
|
|
|
|
course-CRUD nå. Ny `app/routers/courses.py` (`GET`/`POST /orgs/{id}/
|
|
|
|
|
|
courses`, kun `source='custom'`). Program-skjemaet fikk et
|
|
|
|
|
|
type-ahead-felt for bane (samme mønster som spiller-type-ahead i
|
|
|
|
|
|
roster-skjermen).
|
|
|
|
|
|
**Reell korrekthetsfeil rettet FØR integrering:** V0-promptet mitt ba om
|
|
|
|
|
|
ett generisk "Scramble"-valg, men skjemaets `CHECK`-constraint og
|
|
|
|
|
|
`handicap_engine.py` sin `Format`-enum krever `scramble_2`/`scramble_4`
|
|
|
|
|
|
som distinkte verdier -- ren `"scramble"` avvises med 400. Rettet i
|
|
|
|
|
|
frontend-mappingen til to segment-knapper.
|
|
|
|
|
|
**`allowance_override`-JSON-formen verifisert eksakt** mot
|
|
|
|
|
|
`app/handicap.py` sin `parse_allowance_config`/`_strategy_from_json`
|
|
|
|
|
|
(`{type: "combined"|"per_player", percentage: 0..1}`, ikke en flat
|
|
|
|
|
|
prosent) -- frontend velger riktig `type` ut fra om formatet er
|
|
|
|
|
|
side-enhet eller spiller-enhet, konverterer 0–100-skjemafelt til 0–1.
|
|
|
|
|
|
**Scratch-infrastruktur denne runden, bevisst forskjellig fra tidligere:**
|
|
|
|
|
|
brukte en ISOLERT `teecup_app_scratch`-rolle (`GRANT teecup_app TO
|
|
|
|
|
|
teecup_app_scratch`) i stedet for den ekte `teecup_app`-rollen -- den er
|
|
|
|
|
|
nå cluster-global og produksjonskritisk (delt Postgres-instans med
|
|
|
|
|
|
`teecup_db`), så et eldre plandokuments "drop teecup_app-rolle"-
|
|
|
|
|
|
opprydning (skrevet FØR go-live) er utdatert og ble bevisst IKKE fulgt.
|
|
|
|
|
|
Egen isolert scratch-MinIO-container også (app-oppstart krever en
|
|
|
|
|
|
nåbar MinIO for `ensure_bucket()`).
|
|
|
|
|
|
**Verifisert:** courses opprettet+listet, kryss-org-isolasjon bekreftet,
|
|
|
|
|
|
økt med klokkeslett, økt med `scramble_4`+full `allowance_override`-
|
|
|
|
|
|
rundtur, gammel `"scramble"`-verdi avvist (400), `test_isolation.sql`
|
|
|
|
|
|
12/12, ekte typesjekket PRODUKSJONSBUILD (samme `Dockerfile` som faktisk
|
|
|
|
|
|
deployes) kjørt og bekreftet.
|
|
|
|
|
|
**Diffet V0-eksporten mot live-treet FØR noe ble tatt inn** (samme mønster
|
|
|
|
|
|
som alle tidligere runder): kun tre reelt nye filer, resten forventede
|
|
|
|
|
|
full-reverts, ikke rørt. Fjernet V0s dev-only forhåndsvisnings-toggle;
|
|
|
|
|
|
lagt til fanerad ("Lag og spillere" / "Program") i BEGGE skjermene siden
|
|
|
|
|
|
V0 ikke visste om den andre når den ble generert i egen prompt.
|
|
|
|
|
|
**Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: `docker
|
|
|
|
|
|
compose up -d --build teecup_api teecup_frontend` (kun disse to, `teecup-
|
|
|
|
|
|
minio` urørt). Verifisert: begge containere boot-et rent (`Application
|
|
|
|
|
|
startup complete`, Next.js `Ready`), `teecup.teeoff.no/health` og
|
|
|
|
|
|
`/dashboard` → 200, `teeoff.no` upåvirket (200).
|
|
|
|
|
|
- **ADR-004 (teeoff-banedata) kartlagt, IKKE bygget (2026-07-18):** brukeren
|
|
|
|
|
|
påpekte rett etter program-skjerm-rullingen at ADR-004s teeoff-
|
|
|
|
|
|
integrasjon fortsatt bare er vedtatt, ikke bygget (kun `source='custom'`
|
|
|
|
|
|
finnes). Kartlagt `/opt/teeoff/backend/main.py` (annet repo, kun lest):
|
|
|
|
|
|
`GET /api/facilities/{slug}` (offentlig, INGEN auth/API-nøkkel) returnerer
|
|
|
|
|
|
allerede alt teecup trenger — `courses[].holes[]` (`par`, `hcp_index`
|
|
|
|
|
|
= stroke index), `courses[].tees[]` (`name`, `cr_men`/`slope_men`,
|
|
|
|
|
|
`cr_women`/`slope_women` — kjønnsdelt WHS-rating). CORS-lista på teeoff-
|
|
|
|
|
|
siden ekskluderer teecups origin, men er IRRELEVANT for et server-til-
|
|
|
|
|
|
server-kall (kun nettleser-fetch rammes av CORS). Ingen dokumentasjon
|
|
|
|
|
|
(README/OpenAPI) finnes for dette API-et i teeoff-repoet — kartleggingen
|
|
|
|
|
|
over er basert på å lese koden direkte. Stabil identifikator: `facilities.
|
|
|
|
|
|
slug` (f.eks. `borregaard-golfklubb`) — selve banen har kun en intern
|
|
|
|
|
|
serial-id, ingen egen slug, så `course.external_course_ref` må bære
|
|
|
|
|
|
facility-slug + bane-id sammen.
|
|
|
|
|
|
**Oppdatering samme dag: brukeren ba meg ta fatt på dette NÅ** (bevisst
|
|
|
|
|
|
sidesprang fra "bygg i rekkefølgen ting brukes"-planen — blind draw-
|
|
|
|
|
|
skjermen er fortsatt neste steg i den planen ETTERPÅ, ikke droppet).
|
|
|
|
|
|
Design besluttet og skrevet som **ADR-019** (se ARCHITECTURE_DECISIONS.md):
|
|
|
|
|
|
import (kopi) ved organisators eksplisitte valg, ikke live oppslag ved
|
|
|
|
|
|
hver bruk — fryser data på importtidspunktet, samme reproduserbarhets-
|
|
|
|
|
|
prinsipp som handicap-snapshotten (ADR-007). Server-til-server-kall
|
|
|
|
|
|
(`teecup_api` → `http://teeoff_api:8000`, internt Docker-nettverk, ingen
|
|
|
|
|
|
auth trengs). Ny migrasjon `010` (unik `external_course_ref` per org,
|
|
|
|
|
|
hindrer dupliserte importer). Se ADR-019 for alle fem delbeslutningene.
|
|
|
|
|
|
**Bygget, scratch-verifisert MOT EKTE `teeoff_api`** (ikke en simulert
|
|
|
|
|
|
respons — ekte HTTP-kall til den kjørende produksjonscontaineren, kun
|
|
|
|
|
|
lesing): søkte opp "Borregaard" i teeoff sine 174 publiserte anlegg,
|
|
|
|
|
|
hentet Borregaard Golfklubb sin hovedbane (18 hull, 4 tee-farger ×
|
|
|
|
|
|
kjønn), importerte den til en scratch-org — alle 18 hull med riktig
|
|
|
|
|
|
par/stroke-index (1-18, unik), alle 8 tee/tee_rating-rader med riktig
|
|
|
|
|
|
full_18 course/slope-rating verifisert direkte i databasen, opprettet
|
|
|
|
|
|
deretter en ekte økt med den importerte banen som `course_id` (beviser
|
|
|
|
|
|
hele veien til handicap-motoren fungerer, ikke bare selve importen).
|
|
|
|
|
|
Reimport av samme bane korrekt avvist (409 DUPLICATE, migrasjon 010 sin
|
|
|
|
|
|
indeks). Kryss-org-isolasjon bekreftet, ukjent teeoff-slug ga korrekt 404,
|
|
|
|
|
|
`test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild av
|
|
|
|
|
|
frontend-utvidelsen (bane-søk i program-skjemaet: søk anlegg → velg bane →
|
|
|
|
|
|
importer, samme UI-mønster som spiller-/bane-type-ahead ellers i appen).
|
|
|
|
|
|
**Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: migrasjon 010
|
|
|
|
|
|
kjørt mot ekte `teecup_db` (kun én ny partiell unik-indeks, ingen
|
|
|
|
|
|
eksisterende rader rørt), begge containere bygget+redeployet, live
|
|
|
|
|
|
sjekker OK, `teeoff.no` upåvirket. **"Bygg i rekkefølgen ting brukes"-
|
|
|
|
|
|
planen gjenopptas nå** — blind draw-skjermen er neste steg.
|
|
|
|
|
|
**Reell produksjonsbug funnet OG fikset samme dag, rapportert av bruker
|
|
|
|
|
|
som faktisk brukte funksjonen:** brukeren klikket seg korrekt via
|
|
|
|
|
|
dashbord → turnering → Program-fane (bekreftet med skjermbilde + full
|
|
|
|
|
|
klikk-sti, ikke gjettet), åpnet "Hent bane fra teeoff", søkte "Tjøme", og
|
|
|
|
|
|
fikk feilmeldingen "Mangler organisasjon i lenken" — URL-en hadde da
|
|
|
|
|
|
MISTET `?org=...&name=...`. Root-cause: `OfficialCourseSearch` sitt eget
|
|
|
|
|
|
søke-`<form onSubmit={runSearch}>` var rendret INNI `CreateSessionCard`
|
|
|
|
|
|
sitt eksisterende `<form onSubmit={handleSubmit}>` -- nestede
|
|
|
|
|
|
`<form>`-elementer er ugyldig HTML. Nettleseren slår sammen de to
|
|
|
|
|
|
skjemaene i den faktiske DOM-en, så "Søk"-knappen submittet i praksis det
|
|
|
|
|
|
YTRE økt-skjemaet som en ekte native side-navigasjon (GET til gjeldende
|
|
|
|
|
|
sti, ingen navngitte felt => tom spørrestreng) -- dette vasket bort
|
|
|
|
|
|
org-parameteren og landet brukeren på siden sin egen org-guard. **Fikset**
|
|
|
|
|
|
ved å fjerne det indre `<form>`-elementet helt (vanlig `<div>` +
|
|
|
|
|
|
Enter-tast-håndtering på inputet + `type="button"` i stedet for
|
|
|
|
|
|
`type="submit"` på søkeknappen) -- gjør nestede skjemaer strukturelt
|
|
|
|
|
|
umulig fremover for denne komponenten. Ekte typesjekket
|
|
|
|
|
|
produksjonsbuild kjørt på nytt, kun `teecup_frontend` redeployet (ingen
|
|
|
|
|
|
backend-endring). **Lærdom for fremtidige skjermer:** en ny
|
|
|
|
|
|
søk-/underskjema-widget som skal plasseres INNI et eksisterende skjema
|
|
|
|
|
|
(slik som denne bane-søk-widgeten ligger inni økt-opprett-skjemaet) må
|
|
|
|
|
|
ALDRI være et eget `<form>` -- bruk `<div>` + eksplisitt
|
|
|
|
|
|
klikk-/Enter-håndtering.
|
|
|
|
|
|
|
|
|
|
|
|
- **Organisator-overstyring i `team_authz.py`, LIVE (2026-07-18):** reist av
|
|
|
|
|
|
brukeren rett før blind draw-skjermen skulle designes: `app/team_authz.py`
|
|
|
|
|
|
sin `user_may_act_for_team` krevde tidligere en `team_roster`-rad med
|
|
|
|
|
|
lenket bruker-konto — ingen vei for organisatoren til å låse/legge til
|
|
|
|
|
|
deltakere/føre score hvis ingen spiller hadde logget inn ennå (vanlig
|
|
|
|
|
|
tidlig i en turnering). Utvidet til også å godta org-eier/admin
|
|
|
|
|
|
(`organization_membership.role IN ('owner','admin')`), på ETHVERT lag,
|
|
|
|
|
|
ingen unntak for at organisatoren selv er rostret på motstanderlaget.
|
|
|
|
|
|
**Det unntaket ble bevisst vurdert og avvist** (brukeren spurte selv om
|
|
|
|
|
|
det, jeg anbefalte det opprinnelig, men vi kom sammen frem til at det ville
|
|
|
|
|
|
skapt en verre låsning: er organisatoren spillende på Lag A og
|
|
|
|
|
|
Lag B heller ikke har noen innlogget spiller, ville Lag B blitt helt
|
|
|
|
|
|
låst ute) — TeeCup er et tillitsbasert klubb-/vennegjeng-verktøy, ikke en
|
|
|
|
|
|
sikkerhetsgrense mot en fiendtlig organisator som uansett allerede ser
|
|
|
|
|
|
begge lags fulle troppe-liste (blind draw skjuler kun selve
|
|
|
|
|
|
kamp-paringen). Alle 5 kallsteder oppdatert (matches.py sin
|
|
|
|
|
|
add_participant/lock_lineup, scoring.py sin submit_hole_score/
|
|
|
|
|
|
submit_hole_result). **Verifisert grundig i scratch:** org-eier uten
|
|
|
|
|
|
roster kan nå låse BEGGE lag + føre score, en vanlig 'member'-rolle
|
|
|
|
|
|
fortsatt blokkert (uendret), en rostret spiller fungerer uendret
|
|
|
|
|
|
uavhengig av org-rolle. `test_isolation.sql` 12/12. Ingen migrasjon (ren
|
|
|
|
|
|
Python-endring) — kun `teecup_api` redeployet, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Tee-endepunkter bygget og LIVE (2026-07-18), rett før blind draw-skjermen:**
|
|
|
|
|
|
fant et hull som blokkerte selve blind draw-flyten: `match_participant.
|
|
|
|
|
|
tee_id` er påkrevd, men det fantes INGEN `GET`-vei for å liste en banes
|
|
|
|
|
|
tee-er (selv offisielt importerte baner har tee-rader, men ingenting
|
|
|
|
|
|
eksponerte dem) OG egendefinerte («custom») baner har ALDRI hatt noen
|
|
|
|
|
|
vei til å FÅ tee-er i det hele tatt — dette er ikke noe ADR-019 innførte,
|
|
|
|
|
|
det var et hull som fantes fra før custom-baner ble lagt til i første
|
|
|
|
|
|
omgang, bare usynlig til nå. Konsekvens før fiksen: en turnering satt opp
|
|
|
|
|
|
på en manuelt navngitt bane kunne ALDRI få en ekte deltaker lagt til på
|
|
|
|
|
|
noen match. **Presisering (brukeren spurte eksplisitt):** dette er IKKE
|
|
|
|
|
|
noe som må løses i teeoff.no sin kode — egendefinerte baner er per
|
|
|
|
|
|
definisjon baner teeoff ikke kjenner til, så dette er en ren
|
|
|
|
|
|
teecup-intern funksjon, uavhengig av ADR-019-integrasjonen.
|
|
|
|
|
|
Ny `GET/POST /orgs/{id}/courses/{id}/tees` i `app/routers/courses.py`.
|
|
|
|
|
|
`POST` avviser eksplisitt forsøk på offisielle baner (400
|
|
|
|
|
|
`VALIDATION_FAILED` — de får tee-ene sine fra teeoff-importen, ikke
|
|
|
|
|
|
manuelt). Kun full_18-rating dekket (samme begrunnelse som ADR-019
|
|
|
|
|
|
Beslutning D). **Bevisst UTENFOR omfang denne runden, egen senere sak:**
|
|
|
|
|
|
hull-/stroke-index-data for egendefinerte baner (`hole`-tabellen forblir
|
|
|
|
|
|
tom for custom-baner) — trengs for korrekt slagfordeling i SCORING-fasen
|
|
|
|
|
|
(ADR-008), ikke i blind draw, så det løses naturlig når scorekort-
|
|
|
|
|
|
skjermen bygges (samme "bygg i rekkefølgen ting brukes"-logikk).
|
|
|
|
|
|
**Verifisert grundig i scratch:** tom tee-liste på fersk custom-bane,
|
|
|
|
|
|
opprett+list tee på custom-bane, offisiell bane viser alle 8 importerte
|
|
|
|
|
|
tee-er, manuell tee-opprettelse avvist på offisiell bane, og — den
|
|
|
|
|
|
faktiske payoff-en — en deltaker lagt til en match på en custom-bane-økt
|
|
|
|
|
|
for FØRSTE gang noensinne (`POST .../matches/{id}/participants` lykkes
|
|
|
|
|
|
nå med en tee_id fra en nyopprettet custom-tee). `test_isolation.sql`
|
|
|
|
|
|
12/12. Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Blind draw-skjermen LIVE (2026-07-18):** syvende V0-skjerm,
|
|
|
|
|
|
`components/session-blind-draw.tsx`, ny rute
|
|
|
|
|
|
`/tournaments/[id]/sessions/[sessionId]`. To lag-kolonner, hver med sine
|
|
|
|
|
|
matcher ("flights"), legg til/fjern spiller+tee per plass (antall plasser
|
|
|
|
|
|
avhenger av format), lås-knapp med bekreftelse, avslørings-visning når
|
|
|
|
|
|
begge lag har låst. Program-skjermens økt-kort er nå klikkbare inn hit.
|
|
|
|
|
|
**Datamodell tilpasset fra V0s mock til API-ets faktiske sett-modell:**
|
|
|
|
|
|
V0 designet faste, indekserte "Slot"-arrays (kontrollert skjema-state);
|
|
|
|
|
|
API-et har verken PATCH på `match_participant` eller noen slot-indeks —
|
|
|
|
|
|
kun opprett/slett av en løs deltaker-mengde per side. Løst med en
|
|
|
|
|
|
"legg til spiller"-inline-form (samme mønster som spiller-/bane-type-ahead
|
|
|
|
|
|
ellers i appen) i stedet for faste dropdown-rader; funksjonelt likeverdig,
|
|
|
|
|
|
strukturelt riktigere for API-ets faktiske form.
|
|
|
|
|
|
**To reelle hull funnet og fikset FØR/UNDER integrering:**
|
|
|
|
|
|
1. Ingen `DELETE` fantes for `match_participant` — en kaptein kunne aldri
|
|
|
|
|
|
angre et valg før låsing uten å etterlate en foreldreløs rad. Ny
|
|
|
|
|
|
`DELETE /orgs/{id}/matches/{match_id}/participants/{participant_id}`
|
|
|
|
|
|
(samme ALREADY_LOCKED/NOT_ROSTERED_ON_TEAM-sjekker som opprett).
|
|
|
|
|
|
2. **"Skriv blindt"-hull, funnet under selve scratch-testingen (ikke bare
|
|
|
|
|
|
tenkt ut på forhånd):** forrige rundes org-admin-overstyring i
|
|
|
|
|
|
`team_authz.py` lot en organisator LEGGE TIL deltakere på et lag de
|
|
|
|
|
|
ikke selv er rostret på, men `app/blind_draw.py` sin `own_team_ids()`
|
|
|
|
|
|
sjekket KUN rostret spiller for SYNLIGHET — så organisatoren så aldri
|
|
|
|
|
|
sine egne tilføyelser igjen før begge lag hadde låst. Fikset ved å gi
|
|
|
|
|
|
`own_team_ids()` samme owner/admin-utvidelse som `user_may_act_for_
|
|
|
|
|
|
team()` — en org-admin ser nå BEGGE lag umiddelbart (konsistent med at
|
|
|
|
|
|
de uansett allerede har full tilgang, se forrige rundes resonnement),
|
|
|
|
|
|
mens en faktisk rostret kaptein fortsatt kun ser sitt eget lag før
|
|
|
|
|
|
reveal. Kun ett reelt kallsted (`matches.py` sin `list_matches`).
|
|
|
|
|
|
**Tredje, urelatert bug fanget under samme scratch-økt og fikset på
|
|
|
|
|
|
brukerens eksplisitte forespørsel:** `POST .../matches/{id}/participants`
|
|
|
|
|
|
krasjet med en rå 500 (`TypeError` i `handicap_engine.py` sin
|
|
|
|
|
|
`course_handicap_raw`) hvis spilleren manglet `handicap_index` OG
|
|
|
|
|
|
`use_handicap` var på (standard) — preeksisterende, ikke noe denne
|
|
|
|
|
|
runden introduserte. Fikset i `app/routers/matches.py` sin
|
|
|
|
|
|
`add_participant`: sjekker nå `handicap_index_snapshot IS NULL` EKSPLISITT
|
|
|
|
|
|
FØR innsetting når `use_handicap` er sann, avviser med en klar
|
|
|
|
|
|
`VALIDATION_FAILED` (400) i stedet for å krasje. Bekreftet at
|
|
|
|
|
|
`use_handicap=false` fortsatt tillater en spiller uten handicap
|
|
|
|
|
|
(scratch-spill), og at en spiller MED handicap fortsatt fungerer uendret.
|
|
|
|
|
|
**Verifisert grundig i scratch, flere runder:** DELETE-endepunktet
|
|
|
|
|
|
(fjern+idempotent 404 ved gjentak+409 etter lås), synlighetsfikset (org-
|
|
|
|
|
|
admin ser begge sider, rostret kaptein ser fortsatt kun eget lag),
|
|
|
|
|
|
null-handicap-fikset (avvist rent, scratch-modus upåvirket, normal
|
|
|
|
|
|
spiller upåvirket), full opprett-match→legg-til-deltaker→lås→avslør-
|
|
|
|
|
|
syklus ende-til-ende. `test_isolation.sql` 12/12 etter hver runde. Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild.
|
|
|
|
|
|
**Rullet ut live**, bruker bekreftet eksplisitt: begge containere
|
|
|
|
|
|
bygget+redeployet (ingen migrasjon), `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Backend-forarbeid for scorekort-skjermen, LIVE (2026-07-18):** to hull
|
|
|
|
|
|
funnet ved gjennomlesing av `scoring.py` FØR V0-prompten ble skrevet,
|
|
|
|
|
|
samme føre-var-mønster som resten av økten.
|
|
|
|
|
|
1. **Hull-/stroke-index-data for egendefinerte baner** (bevisst utsatt fra
|
|
|
|
|
|
tee-rundens status-notat) — `hole`-tabellen har ALDRI hatt noen vei inn
|
|
|
|
|
|
for custom-baner, som blokkerte `stroke`-scoringsmodus helt (`hole_
|
|
|
|
|
|
result`-modus trenger den ikke). Ny `GET/POST /orgs/{id}/courses/{id}/
|
|
|
|
|
|
holes` i `app/routers/courses.py` — engangs alle-18-på-en-gang
|
|
|
|
|
|
(avviser feil antall, avviser hvis banen allerede har hull, avviser på
|
|
|
|
|
|
offisielle baner som får hullene sine fra teeoff-import).
|
|
|
|
|
|
2. **`GET scorecard` viste kun UTLEDET vinn/tap/delt per hull, aldri de
|
|
|
|
|
|
faktiske tallene som var registrert** — umulig å bygge en skjerm som
|
|
|
|
|
|
viser gjeldende tilstand ved gjenlasting. Utvidet `Scorecard`-modellen
|
|
|
|
|
|
i `app/routers/scoring.py` med `stroke_entries`/`hole_result_entries`
|
|
|
|
|
|
(rå `hole_score`/`match_hole_result`-rader, nøyaktig ett av de to fylt
|
|
|
|
|
|
ut avhengig av øktens scoring_mode).
|
|
|
|
|
|
**Verifisert grundig i scratch:** hull-validering (feil antall avvist,
|
|
|
|
|
|
gjentatt oppsett avvist), og en FULL `stroke`-modus scoringsrunde med
|
|
|
|
|
|
ekte handicap-justert nettoberegning på en egendefinert bane for FØRSTE
|
|
|
|
|
|
gang noensinne (ulikt slagtall — 4 mot 5 — ga korrekt "halved" etter
|
|
|
|
|
|
handicap-utjevning), scorecard sin nye `stroke_entries` bekreftet å
|
|
|
|
|
|
returnere nøyaktig det som ble registrert. `test_isolation.sql` 12/12.
|
|
|
|
|
|
**Rullet ut live**, ingen migrasjon, kun `teecup_api` redeployet,
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Scorekort-skjermen LIVE (2026-07-18):** åttende V0-skjerm,
|
|
|
|
|
|
`components/session-scorecard.tsx`, ny rute `/tournaments/[id]/sessions/
|
|
|
|
|
|
[sessionId]/matches/[matchId]`. Ett hull i fokus om gangen (banebruk,
|
|
|
|
|
|
ikke et regneark) — store slag-steppere for `stroke`-modus, tre-valgs
|
|
|
|
|
|
vinner-knapper for `hole_result`-modus, hull-chip-navigasjon, kollapsbar
|
|
|
|
|
|
full oversikt, feiret "avgjort"-banner som låser alt til read-only.
|
|
|
|
|
|
Blind draw sin avslørte visning lenker nå til hver match sitt scorekort.
|
|
|
|
|
|
**Ingen nye backend-hull denne runden** — forrige rundes forarbeid
|
|
|
|
|
|
(hull-endepunkter + `stroke_entries`/`hole_result_entries` i scorecard)
|
|
|
|
|
|
dekket akkurat det skjermen trengte.
|
|
|
|
|
|
**Verifisert grundig i scratch, inkludert et fullt oppsett som speiler
|
|
|
|
|
|
NØYAKTIG frontend-ens egen last-sekvens** (sessions→teams→matches→
|
|
|
|
|
|
holes→scorecard, deretter en ekte `POST hole-scores` for begge spillere
|
|
|
|
|
|
på hull 1 og en refetch): status_text/derivert resultat oppdaterte seg
|
|
|
|
|
|
korrekt ("1 UP (A)", hull 1 → "a"), alle feltnavn stemte eksakt med
|
|
|
|
|
|
TypeScript-typene uten justering. `test_isolation.sql` 12/12, ekte
|
|
|
|
|
|
typesjekket build.
|
|
|
|
|
|
**Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Leaderboard-backend LIVE (2026-07-18):** ny `GET /orgs/{id}/
|
|
|
|
|
|
tournaments/{id}/leaderboard` i `app/routers/tournaments.py` — summerer
|
|
|
|
|
|
poeng på tvers av ALLE økter/matcher i turneringen, både total og
|
|
|
|
|
|
per-økt-delsum.
|
|
|
|
|
|
**Reelt korrekthetshull funnet OG designet rundt FØR koden ble skrevet,
|
|
|
|
|
|
ikke oppdaget i ettertid:** `match.team_a_id`/`team_b_id` settes PER
|
|
|
|
|
|
MATCH ved opprettelse, ikke garantert konsistent på tvers av matcher —
|
|
|
|
|
|
en organisator kunne i prinsippet opprettet match 1 med team_a=Rød og
|
|
|
|
|
|
match 2 med team_a=Blå. En naiv summering av `points_side_a`/`points_
|
|
|
|
|
|
side_b` ville da blandet sammen poeng fra to ULIKE fysiske lag. Løst ved
|
|
|
|
|
|
å ALDRI summere på "a"/"b"-labelen — kun på det ekte lag-id-et
|
|
|
|
|
|
(`points_by_team: dict[team_id, float]`, både i totalen og per økt).
|
|
|
|
|
|
**Verifisert presist, ikke bare "kjørte uten feil":** bygget et scratch-
|
|
|
|
|
|
scenario med 3 matcher over 2 økter der match 2 sin team_a/team_b
|
|
|
|
|
|
BEVISST var byttet om i forhold til de to andre — satte poeng direkte
|
|
|
|
|
|
(Rød vinner alle tre, ett delt) og bekreftet at leaderboardet likevel ga
|
|
|
|
|
|
riktig total (Rød 2.5, Blå 0.5) og riktig per-økt-delsum, til tross for
|
|
|
|
|
|
swap-en. `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api`
|
|
|
|
|
|
redeployet, `teeoff.no` upåvirket. Frontend-skjermen gjenstår.
|
|
|
|
|
|
|
|
|
|
|
|
- **Leaderboard-skjermen LIVE (2026-07-18), samme dag:** niende og siste
|
|
|
|
|
|
V0-skjerm i "bygg i rekkefølgen ting brukes"-serien for kamp-play-flyten.
|
|
|
|
|
|
`components/tournament-leaderboard.tsx`, ny rute `/tournaments/[id]/
|
|
|
|
|
|
leaderboard`. Stort scoreboard-kort med de to lagenes totalpoeng
|
|
|
|
|
|
(lederen fremhevet), pluss en per-økt poeng-fordelingsliste med
|
|
|
|
|
|
fullført/ikke-startet-status. V0 la selv til et tredje faneelement
|
|
|
|
|
|
("Leaderboard") i BÅDE program- og roster-skjermens fanerad denne
|
|
|
|
|
|
runden — portert inn i de LIVE versjonene av begge (med riktig `org`-
|
|
|
|
|
|
parameter, som V0s eksport som vanlig manglet).
|
|
|
|
|
|
**Ingen nye backend-hull** — forrige rundes leaderboard-endepunkt dekket
|
|
|
|
|
|
akkurat det skjermen trengte.
|
|
|
|
|
|
**Verifisert i scratch:** full datamodell-runde (samme felt-for-felt
|
|
|
|
|
|
som backend-verifiseringen dagen før) OG et eget tomtilstand-scenario
|
|
|
|
|
|
(fersk turnering med 2 lag, 0 økter — `sessions: []`, `matches_total: 0`)
|
|
|
|
|
|
for å bekrefte at "ingen økter opprettet ennå"-meldingen vises riktig
|
|
|
|
|
|
i stedet for å krasje på et tomt array. `test_isolation.sql` 12/12, ekte
|
|
|
|
|
|
typesjekket build.
|
|
|
|
|
|
**Rullet ut live**, ren frontend-endring, `teeoff.no` upåvirket.
|
|
|
|
|
|
**Hele "bygg i rekkefølgen ting brukes"-serien for match-play-flyten er
|
|
|
|
|
|
dermed komplett:** oppsett (lag/roster) → program → blind draw →
|
|
|
|
|
|
scorekort → leaderboard.
|
|
|
|
|
|
- **Offisiell bane-import: idempotent + tydeligere navn, LIVE (2026-07-18),
|
|
|
|
|
|
rapportert av brukeren som faktisk brukte funksjonen på ekte
|
|
|
|
|
|
produksjonsdata:** to reelle problemer, begge funnet ved å lese (kun
|
|
|
|
|
|
lesing) de faktiske dataene for "De Gamle er Eldst" FØR noe ble antatt.
|
|
|
|
|
|
1. **`POST .../courses/official-import` var IKKE idempotent** — å
|
|
|
|
|
|
importere samme bane på nytt (helt vanlig: flere økter spilles ofte
|
|
|
|
|
|
på samme bane) ga en 409 `DUPLICATE`-feil (migrasjon 010 sin sperre)
|
|
|
|
|
|
i stedet for å bare gi tilbake den allerede importerte banen. Fikset:
|
|
|
|
|
|
sjekker nå `external_course_ref` FØR noe teeoff-kall gjøres — finnes
|
|
|
|
|
|
banen fra før, returneres den eksisterende raden direkte (også
|
|
|
|
|
|
raskere, og robust mot at teeoff er nede akkurat da).
|
|
|
|
|
|
2. **Lagret navn var kun selve banens navn ("Hovedbanen"), ikke hvilken
|
|
|
|
|
|
klubb** — ubrukelig til å skille baner fra hverandre, siden mange
|
|
|
|
|
|
klubber navngir hovedbanen sin identisk. Fikset: navnet kombineres nå
|
|
|
|
|
|
til "{anlegg} – {bane}" (f.eks. "Tjøme Golfklubb – Hovedbanen") ved
|
|
|
|
|
|
import.
|
|
|
|
|
|
**Reell konsekvens av hull #1 funnet i produksjonsdata:** brukeren hadde,
|
|
|
|
|
|
mens hen forsøkte å søke opp Tjøme via det ØVERSTE banefeltet (som søker
|
|
|
|
|
|
organisasjonens EGNE baner, ikke teeoff), ved et uhell trigget «Opprett
|
|
|
|
|
|
ny bane: «Tj»»-snarveien og fått en tom, søppel `custom`-bane hengende på
|
|
|
|
|
|
Foursome-økten -- en ekte forvekslingsfelle mellom de to adskilte
|
|
|
|
|
|
bane-søkeflatene (eget vs. teeoff), ikke en kodefeil i seg selv.
|
|
|
|
|
|
**Data ryddet opp i EKTE `teecup_db`, bruker bekreftet eksplisitt:**
|
|
|
|
|
|
Foursome-økten pekt om til den allerede importerte, ekte Tjøme-banen
|
|
|
|
|
|
(samme bane som Fourball-økten allerede brukte -- nøyaktig det
|
|
|
|
|
|
organisatoren egentlig ønsket), søppel-«Tj»-banen slettet (bekreftet
|
|
|
|
|
|
ingen matcher/tee-er/hull hang på den FØR sletting), og den ekte banens
|
|
|
|
|
|
navn oppdatert til "Tjøme Golfklubb – Hovedbanen". Verifisert i etterkant
|
|
|
|
|
|
at begge økter nå peker til samme, korrekt navngitte bane.
|
|
|
|
|
|
**Verifisert mot ekte teeoff_api i scratch FØR utrulling:** importerte
|
|
|
|
|
|
Borregaard på nytt to ganger — andre kallet ga nøyaktig samme course-id
|
|
|
|
|
|
(ikke en duplikat-rad), navnet kom ut som "Borregaard Golfklubb –
|
|
|
|
|
|
Hovedbanen". `test_isolation.sql` 12/12. Ingen migrasjon, kun `teecup_api`
|
|
|
|
|
|
redeployet, `teeoff.no` upåvirket.
|
|
|
|
|
|
- **`PATCH`/`DELETE` for økter, LIVE (2026-07-18), samme dag:** brukeren
|
|
|
|
|
|
spurte rett etter opprydningen om det i det hele tatt var mulig å
|
|
|
|
|
|
rette/slette en feiloppsatt økt — det var det ikke (kun `POST`/`GET`
|
|
|
|
|
|
fantes). Ny `PATCH /orgs/{id}/sessions/{id}` (`app/routers/
|
|
|
|
|
|
tournaments.py`) for enkle felt (navn, klokkeslett/intervall, starthull,
|
|
|
|
|
|
poeng, handicap-brytere) via vanlig `exclude_unset`-mønster, PLUSS en egen
|
|
|
|
|
|
gren for bane-bytte. Ny `DELETE /orgs/{id}/sessions/{id}` — kun tomme
|
|
|
|
|
|
økter (ingen matcher), avviser med 409 ellers (bruk PATCH til å korrigere
|
|
|
|
|
|
i stedet).
|
|
|
|
|
|
**Banebytte-scenarioet brukeren selv reiste** ("5 hull spilt, oppdager
|
|
|
|
|
|
feil bane — slagene er ekte, utregningen er trolig feil") krevde egen
|
|
|
|
|
|
design: `match_participant.tee_id` peker til en tee som HØRER til den
|
|
|
|
|
|
gamle banen. Løst med `_remap_course()` — finner en tee med samme
|
|
|
|
|
|
navn+kjønn på den nye banen for hver allerede tillagte deltaker, flytter
|
|
|
|
|
|
dem dit, og avviser HELE bane-byttet tydelig (400, ingenting skrevet,
|
|
|
|
|
|
bekreftet transaksjonell rollback) hvis den nye banen mangler en
|
|
|
|
|
|
tilsvarende tee. Etter et vellykket bytte: handicap regnes om for alle
|
|
|
|
|
|
berørte deltakere, og matchstatus/poeng regnes om for HVER match i
|
|
|
|
|
|
økten — **bevisst uavhengig av om matchen allerede er avgjort** (brukeren
|
|
|
|
|
|
bekreftet eksplisitt at en bane-korrigering skal kunne endre et allerede
|
|
|
|
|
|
cachet resultat). `recompute_and_cache_match_state` i `app/routers/
|
|
|
|
|
|
scoring.py` gjort delt (fjernet ledende understrek) for gjenbruk fra
|
|
|
|
|
|
tournaments.py.
|
|
|
|
|
|
**Reelt, urelatert funn underveis i scratch-testingen, IKKE fikset:**
|
|
|
|
|
|
`tee_rating` lages i dag ALLTID kun med `full_18`-omfang (både ved
|
|
|
|
|
|
teeoff-import og manuell tee-opprettelse) — en økt satt til `front_9`/
|
|
|
|
|
|
`back_9` i `stroke`-modus kan derfor ALDRI få handicap beregnet
|
|
|
|
|
|
(`compute_and_store_side_handicaps` sin `tee_rating`-join finner aldri
|
|
|
|
|
|
noen rad), og dermed aldri avgjøre noen hull. `hole_result`-modus
|
|
|
|
|
|
upåvirket. Flagget til bruker, bevisst latt urørt denne runden.
|
|
|
|
|
|
**Verifisert grundig i scratch:** enkelt feltbytte (kun navn), fullt
|
|
|
|
|
|
banebytte-scenario bygget nøyaktig som brukerens eksempel (10 hull spilt
|
|
|
|
|
|
under feil bane, matchen allerede avgjort 10&8, PATCH til riktig bane →
|
|
|
|
|
|
tee-er ombyttet korrekt, handicap endret fra 7/9 til 10/13 under den nye
|
|
|
|
|
|
banens rating, matchstatus regnet på nytt med UENDREDE rå slagtall),
|
|
|
|
|
|
avvist bane-bytte ved manglende tee-match (bekreftet full rollback,
|
|
|
|
|
|
også av det urelaterte navnefeltet i samme kall), DELETE avvist på økt
|
|
|
|
|
|
med match (409) og godtatt på tom økt (204). `test_isolation.sql` 12/12.
|
|
|
|
|
|
Ingen migrasjon, kun `teecup_api` redeployet, `teeoff.no` upåvirket.
|
|
|
|
|
|
Frontend (rediger-/slett-knapper i program-skjermen) ikke bygget ennå —
|
|
|
|
|
|
kun backend-kapasiteten denne runden.
|
|
|
|
|
|
- **Rediger/slett-UI for økter LIVE (2026-07-19):** V0-utvidelse av den
|
|
|
|
|
|
eksisterende Program-skjermen (ingen ny rute) — "..."-meny (samme
|
|
|
|
|
|
DropdownMenu-mønster som roster-skjermens per-spiller-handlinger) med
|
|
|
|
|
|
"Rediger"/"Slett" per øktkort. Rediger bytter kortet til et inline-skjema
|
|
|
|
|
|
(samme felt som PATCH støtter: navn, poeng, starthull, klokkeslett/
|
|
|
|
|
|
intervall, bane). Slett viser en bekreftelse; treffer den ekte 409-en
|
|
|
|
|
|
(økt har matcher), vises backend sin egen feiltekst i stedet for en
|
|
|
|
|
|
forhåndsberegnet klient-tilstand.
|
|
|
|
|
|
**Reell regresjon funnet OG UNNGÅTT i selve V0-eksporten, ikke i
|
|
|
|
|
|
etterkant:** denne rundens V0-prompt handlet kun om rediger/slett, men
|
|
|
|
|
|
eksporten hadde samtidig (utilsiktet) FJERNET bane-feltet fra "Legg til
|
|
|
|
|
|
økt"-skjemaet helt — nye økter ville stille blitt satt til en hardkodet
|
|
|
|
|
|
mock-bane. Fanget under diff-mot-live-treet FØR noe ble tatt inn (samme
|
|
|
|
|
|
rutine som alltid) — `CreateSessionCard` (med sitt ekte bane-søk/
|
|
|
|
|
|
teeoff-import) beholdt fullstendig urørt; kun de nye rediger/slett-
|
|
|
|
|
|
delene ble hentet inn.
|
|
|
|
|
|
**Bane-bytte i redigeringsskjemaet er bevisst ENKLERE enn opprett-
|
|
|
|
|
|
skjemaets bane-felt** — kun velg blant organisasjonens eksisterende
|
|
|
|
|
|
baner eller hent fra teeoff, INGEN "opprett ny bane: X"-snarvei her
|
|
|
|
|
|
(PATCH sin `course_id` må være en ekte, allerede eksisterende bane, ikke
|
|
|
|
|
|
en tekststreng) — unngår at samme "Tj"-forvekslingsfelle fra forrige
|
|
|
|
|
|
banebytte-hendelse kan gjenta seg via redigeringsveien.
|
|
|
|
|
|
**Verifisert:** ekte PATCH med nøyaktig skjemaets feltform, ekte DELETE
|
|
|
|
|
|
(204 tom økt, 409 med matcher — bekreftet at `{"detail":{"code",
|
|
|
|
|
|
"message"}}`-formen leses riktig av UI-et), `test_isolation.sql` 12/12,
|
|
|
|
|
|
ekte typesjekket build. Rullet ut live, ren frontend-endring, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Invitasjonskode + ledende side + projisert stilling, BACKEND LIVE
|
|
|
|
|
|
(2026-07-19, ADR-020):** brukeren reiste tre relaterte hull rett etter at
|
|
|
|
|
|
"bygg i rekkefølgen ting brukes"-serien var ferdig: (1) ingen vei inn til
|
|
|
|
|
|
en turnering for en spiller som bare har fått muntlig beskjed, (2)
|
|
|
|
|
|
leaderboardet viser kun faktisk opptjente poeng, ikke hva stillingen ville
|
|
|
|
|
|
blitt om pågående matcher holder seg, (3) ingen fargekoding i matchlister
|
|
|
|
|
|
for hvem som leder. Full ADR-020 skrevet (4 delbeslutninger, se
|
|
|
|
|
|
ARCHITECTURE_DECISIONS.md) — nøkkelbeslutning bekreftet eksplisitt av
|
|
|
|
|
|
bruker FØR bygging: en invitasjonskode OVERSTYRER `tournament.visibility`
|
|
|
|
|
|
helt (koden ER selve invitasjonen, ikke en snarvei som fortsatt krever
|
|
|
|
|
|
eksisterende tilgang).
|
|
|
|
|
|
Ny migrasjon `011_join_code_and_leading_side.sql`: `tournament.join_code`
|
|
|
|
|
|
(6 tegn, alfabet uten 0/O/1/I, globalt unikt, backfylt for eksisterende
|
|
|
|
|
|
rader), `match.leading_side` (cachet fortegn av `MatchState.lead`, samme
|
|
|
|
|
|
mønster som `status_text`/`points_side_a/b`), fjerde
|
|
|
|
|
|
`SECURITY DEFINER`-bro `public_tournament_by_code()` (etter
|
|
|
|
|
|
`public_tournament_org` 007, `link_player_by_email` 008,
|
|
|
|
|
|
`public_org_by_slug` 009).
|
|
|
|
|
|
**Backend bygget:** `create_tournament` genererer koden (retry-løkke ved
|
|
|
|
|
|
kollisjon, astronomisk usannsynlig med 33^6 kombinasjoner). Ny
|
|
|
|
|
|
`GET /public/tournaments/by-code/{code}` (MÅ registreres FØR
|
|
|
|
|
|
`/{tournament_id}` i routeren, ellers tolkes "by-code" som en ugyldig
|
|
|
|
|
|
UUID). `GET /public/tournaments/{id}`, `GET .../sessions` og
|
|
|
|
|
|
`POST .../register` godtar alle en valgfri `code`-parameter som — når den
|
|
|
|
|
|
matcher — hopper `_check_visibility()` helt over.
|
|
|
|
|
|
`recompute_and_cache_match_state` (scoring.py) cacher nå `leading_side`
|
|
|
|
|
|
ved HVER hull-innsending, ikke bare ved avgjørelse. Leaderboard-
|
|
|
|
|
|
endepunktet fikk `projected_points`/`projected_points_by_team`:
|
|
|
|
|
|
avgjorte matcher bidrar likt til faktisk og projisert, ikke-avgjorte gir
|
|
|
|
|
|
hele `points_per_match` til `leading_side` (delt 0,5/0,5 ved "AS"/ikke
|
|
|
|
|
|
startet) — speiler hvordan Ryder Cup-TV-dekning viser "hvis det sluttet
|
|
|
|
|
|
nå".
|
|
|
|
|
|
**Reell hendelse underveis, håndtert transparent (ikke skjult):** en
|
|
|
|
|
|
feilformulert `docker exec teeoff_db env | grep -i POSTGRES`-kommando
|
|
|
|
|
|
(ment å liste variabelNAVN) fanget opp `POSTGRES_PASSWORD` sin VERDI også,
|
|
|
|
|
|
siden selve nøkkelnavnet matchet søkemønsteret — eksponerte `teeoff_db`
|
|
|
|
|
|
sitt superbruker-passord (`teeoff_admin`) i verktøyresultatet. Alvorligere
|
|
|
|
|
|
enn de to tidligere passord-hendelsene i prosjektet siden dette er
|
|
|
|
|
|
superbrukeren for HELE den delte Postgres-klyngen (teeoff OG teecup), ikke
|
|
|
|
|
|
en enkelt tjeneste-credential. Flagget til bruker umiddelbart, som valgte
|
|
|
|
|
|
å rotere. Rotert trygt UTEN å noensinne re-eksponere gammel ELLER ny verdi
|
|
|
|
|
|
i noe synlig kommandoresultat: `ALTER ROLE` kjørt via lokal
|
|
|
|
|
|
Unix-socket-`trust`-auth (bekreftet ved å lese `pg_hba.conf`, ingen
|
|
|
|
|
|
hemmelighet involvert i den sjekken) — krevde altså IKKE det gamle
|
|
|
|
|
|
passordet i det hele tatt. Nytt passord generert med `openssl rand -hex
|
|
|
|
|
|
32` (hex, ikke base64 — unngår SAMME klasse URL-enkodings-felle som
|
|
|
|
|
|
`TEECUP_DATABASE_URL`-hendelsen tidligere, siden verdien også ligger i en
|
|
|
|
|
|
`postgres://`-DSN). `/opt/teeoff/.env` sine TO forekomster
|
|
|
|
|
|
(`POSTGRES_PASSWORD` og `DATABASE_URL`) oppdatert med `sed`-mønstre som
|
|
|
|
|
|
ALDRI leser/skriver ut den gamle verdien. `teeoff_api`/`teeoff_worker`
|
|
|
|
|
|
service-nøklene i `docker-compose.prod.yml` viste seg å hete `api`/
|
|
|
|
|
|
`worker` (ikke `teeoff_api`/`teeoff_worker` — det er kun
|
|
|
|
|
|
`container_name`), samme "service-nøkkel ≠ container-navn"-fallgruve som
|
|
|
|
|
|
nettverksalias-hendelsen fra containeriseringsrunden. `docker compose up
|
|
|
|
|
|
-d --force-recreate api worker` gjenskapte OGSÅ `teeoff_db` selv (ikke
|
|
|
|
|
|
eksplisitt navngitt) — Compose oppdager konfigurasjonsendring
|
|
|
|
|
|
(`${POSTGRES_PASSWORD}` i `db`-tjenestens egen `environment:`) og
|
|
|
|
|
|
gjenskaper uansett hvilke tjenester som ble navngitt. Verifisert grundig
|
|
|
|
|
|
ETTERPÅ: `teeoff_db`-loggen viste "Skipping initialization" (datavolum
|
|
|
|
|
|
urørt, ikke reinitialisert) + ren oppstart, `teeoff_api`/`teeoff_worker`
|
|
|
|
|
|
ren oppstart uten en eneste feil-/auth-/passord-linje i hele loggen,
|
|
|
|
|
|
`teeoff.no` OG `teecup.teeoff.no/health` begge `200` etterpå.
|
|
|
|
|
|
**Scratch-verifisert grundig** (fersk `teecup_scratch` 001→011,
|
|
|
|
|
|
isolert `teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs
|
|
|
|
|
|
API-container): `test_isolation.sql` 12/12 (måtte først rettes — tre
|
|
|
|
|
|
RÅ `INSERT INTO tournament`-steder i selve testfilen predaterte
|
|
|
|
|
|
`join_code` og traff den nye NOT NULL-constrainten, rettet med
|
|
|
|
|
|
dummy-koder). Full ende-til-ende-runde: to turneringer opprettet, ulike
|
|
|
|
|
|
koder bekreftet; `by-code`-oppslag bekreftet for kjent OG ukjent kode
|
|
|
|
|
|
(404); anonym lesing av en `org`-synlig turnering BLOKKERT uten kode
|
|
|
|
|
|
(403 NOT_VISIBLE), TILLATT med riktig kode (inkl. case-insensitivt),
|
|
|
|
|
|
FORTSATT blokkert med feil kode; samme mønster bekreftet for
|
|
|
|
|
|
`POST .../register`. Full hull-for-hull-simulering av en singel-match
|
|
|
|
|
|
(hole_result-modus): `leading_side`/`status_text` fulgte hverandre
|
|
|
|
|
|
eksakt gjennom "1 UP (A)" → "AS" (leading_side=null) → avgjort "9&7
|
|
|
|
|
|
(A)", leaderboardets `projected_points` traff nøyaktig 1.0/0.0 mens A
|
|
|
|
|
|
ledet, 0.5/0.5 ved "AS", og ble likt `points`/`projected_points` (begge
|
|
|
|
|
|
1.0/0.0) etter avgjørelse.
|
|
|
|
|
|
**Rullet ut mot ekte `teecup_db` 2026-07-19**, bruker bekreftet
|
|
|
|
|
|
eksplisitt: migrasjon 011 kjørt (eneste eksisterende turnering fikk
|
|
|
|
|
|
automatisk generert kode), `test_isolation.sql` fortsatt 12/12, kun
|
|
|
|
|
|
`teecup_api` redeployet (ingen frontend-endring i denne del-runden),
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
|
|
|
|
|
**Frontend fullført samme dag, egen del-runde:** `login-form.tsx` fikk et
|
|
|
|
|
|
eget kode-modus (`JoinByCode`) — «Har du en invitasjonskode?»-lenke bytter
|
|
|
|
|
|
ut e-post-skjemaet, slår opp `/public/tournaments/by-code/{code}` og
|
|
|
|
|
|
navigerer til `/t/{id}?code=...` med Next sin `useRouter`. `code`
|
|
|
|
|
|
query-param tres gjennom hele veien: `app/t/[id]/page.tsx` leser
|
|
|
|
|
|
`searchParams`, `public-tournament.tsx` sender den med på BÅDE
|
|
|
|
|
|
info-/sessions-lesingen og selve `POST .../register` (ikke bare det
|
|
|
|
|
|
første oppslaget som tok deg dit).
|
|
|
|
|
|
`tournament-detail.tsx` viser koden i en egen kopier-chip i headeren —
|
|
|
|
|
|
ingen enkelt-turnering-`GET` fantes, så komponenten henter i stedet hele
|
|
|
|
|
|
org-ens turneringsliste (som allerede bærer `join_code`) og finner egen
|
|
|
|
|
|
rad, i stedet for å legge til et nytt endepunkt kun for dette.
|
|
|
|
|
|
`tournament-leaderboard.tsx` fikk en ny `SegmentedBar`-komponent — ETT
|
|
|
|
|
|
fargesegmentert rektangel per bar (ikke tall side om side), proporsjonalt
|
|
|
|
|
|
med hvert lags poeng, 50/50 nøytralt ved 0-0. To slike bares rett under
|
|
|
|
|
|
headeren: "Stilling nå" (faktisk) og "Projisert (hvis pågående matcher
|
|
|
|
|
|
holder seg)" — den EKSISTERENDE store tall-scoreboarden beholdt uendret
|
|
|
|
|
|
lenger ned som detaljvisning.
|
|
|
|
|
|
`session-blind-draw.tsx` sin `RevealedView`: matchkortet får nå en farget
|
|
|
|
|
|
toppkant (leaderens `team.color`) og en `status_text`-chip (fylt farge
|
|
|
|
|
|
ved avgjort match m/ konfetti-ikon, lys tone ved pågående) i stedet for
|
|
|
|
|
|
kun klokkeslettet; `RevealSide` for ledende side får en svak fargetonet
|
|
|
|
|
|
bakgrunn. `leading_side`/`status_text`/`points_side_a/b` lagt til
|
|
|
|
|
|
`ApiMatch`-typen (var der allerede i API-et, bare ikke konsumert
|
|
|
|
|
|
frontend-siden før nå).
|
|
|
|
|
|
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
|
|
|
|
|
|
som deployes, ikke dev-server) kompilerte rent, alle 10 ruter listet.
|
|
|
|
|
|
Rullet ut (kun `teecup_frontend`, ingen backend-endring i denne delen),
|
|
|
|
|
|
`teecup.teeoff.no/dashboard` og `/` → 200, `teeoff.no` upåvirket. Ekte
|
|
|
|
|
|
smoke-test i produksjon: `by-code`-oppslag for "De Gamle er Eldst" sin
|
|
|
|
|
|
faktiske kode ga riktig turnering-id, `/t/{id}?code=...` ga 200.
|
|
|
|
|
|
**ADR-020 er dermed helt ferdig** (backend + frontend, alle fire
|
|
|
|
|
|
del-ønsker: kode-basert oppdagelse, kode-felt på login, projisert
|
|
|
|
|
|
stilling, fargekoding av matcher) — bortsett fra kode-regenerering, som
|
|
|
|
|
|
er bevisst utsatt (se FEATURE_BACKLOG.md).
|
|
|
|
|
|
**Reell driftshendelse underveis** (mellom backend- og frontend-delen,
|
|
|
|
|
|
under scratch-oppsett): en feilformulert `grep -i POSTGRES`-kommando
|
|
|
|
|
|
eksponerte `teeoff_db` sitt superbruker-passord ved et uhell. Flagget
|
|
|
|
|
|
umiddelbart, brukeren valgte å rotere — se detaljene under
|
|
|
|
|
|
backend-avsnittet over for hele hendelsen og hvordan roteringen ble
|
|
|
|
|
|
gjennomført uten å noensinne re-eksponere gammel eller ny verdi.
|
|
|
|
|
|
- **Sesjons-bug diagnostisert og FIKSET, LIVE (2026-07-19):** brukeren
|
|
|
|
|
|
rapporterte at hen måtte be om ny magic-link-kode ved HVERT besøk til
|
|
|
|
|
|
`teecup.teeoff.no`, til tross for ADR-009s 30-dagers sesjonscookie. Bad
|
|
|
|
|
|
brukeren sjekke den EKTE cookien i nettleseren fremfor å gjette — kom
|
|
|
|
|
|
tilbake korrekt satt i alle henseender (`Expires` 30 dager frem,
|
|
|
|
|
|
`Secure`/`HttpOnly`/`SameSite=Lax`). Rot-årsaken var derfor IKKE cookien
|
|
|
|
|
|
eller backend-en: `frontend/app/page.tsx` (rot-siden) viste ALLTID
|
|
|
|
|
|
innloggingsskjemaet uten noensinne å sjekke om en gyldig sesjon allerede
|
|
|
|
|
|
fantes. `Dashboard`-komponenten sjekker `/auth/me` og sender til `/` ved
|
|
|
|
|
|
MANGLENDE sesjon, men ingen kode gjorde det motsatte — en bruker som
|
|
|
|
|
|
besøkte roten direkte (i stedet for å navigere til `/dashboard`) så
|
|
|
|
|
|
derfor alltid innloggingsskjemaet uansett sesjonsstatus.
|
|
|
|
|
|
**Fikset:** `page.tsx` gjort om til en async server-komponent som leser
|
|
|
|
|
|
sesjonscookien via `next/headers`, kaller `/auth/me` server-til-server
|
|
|
|
|
|
direkte mot `TEECUP_API_ORIGIN` (samme mønster som `generateMetadata` i
|
|
|
|
|
|
`app/t/[id]/page.tsx` — IKKE gjennom `next.config.mjs` sin `rewrites()`,
|
|
|
|
|
|
som kun gjelder nettleser-trafikk), og sender en allerede innlogget
|
|
|
|
|
|
bruker videre til `/dashboard` med `redirect()` FØR innloggingsskjemaet
|
|
|
|
|
|
når rendres.
|
|
|
|
|
|
**Verifisert presist mot den ekte, live stacken, med brukerens EGEN
|
|
|
|
|
|
ekte sesjonscookie** (ikke en syntetisk test): `curl` uten cookie mot
|
|
|
|
|
|
`https://teecup.teeoff.no/` ga `200` (skjemaet vises, riktig for en
|
|
|
|
|
|
anonym besøkende); samme kall MED den ekte cookien ga `307` til
|
|
|
|
|
|
`/dashboard` (riktig — sender en allerede innlogget bruker rett videre).
|
|
|
|
|
|
Ekte typesjekket produksjonsbuild kjørt FØR utrulling (build-outputet
|
|
|
|
|
|
viste selv at `/` nå er `ƒ` dynamisk i stedet for `○` statisk — bekrefter
|
|
|
|
|
|
at server-sjekken faktisk ble tatt i bruk). Rullet ut live, kun
|
|
|
|
|
|
`teecup_frontend`, ingen migrasjon, `teeoff.no` upåvirket.
|
|
|
|
|
|
**Samme runde:** brukeren stilte to oppfølgingsspørsmål om
|
|
|
|
|
|
autentisering/autorisasjon — «er flere-organisasjoner-eierskap tenkt
|
|
|
|
|
|
gjennom» (bekreftet: ja, ADR-002 fra dag én, ingen kodeendring nødvendig)
|
|
|
|
|
|
og et ønske om passord (valgfritt tillegg)+2FA. Fullt design skrevet som
|
|
|
|
|
|
**ADR-021** (passord/2FA) og **ADR-022** (dele/invitere/frasi seg
|
|
|
|
|
|
eierskap + superadmin) — se ARCHITECTURE_DECISIONS.md.
|
|
|
|
|
|
- **ADR-021 (passord/2FA) + ADR-022 (org-eierskap) BYGGET OG LIVE
|
|
|
|
|
|
(2026-07-19), samme dag:** brukeren ba om begge sammen («Bygg det»,
|
|
|
|
|
|
bekreftet eksplisitt at det gjaldt begge ADR-ene i samme runde). Ny
|
|
|
|
|
|
migrasjon `012_password_2fa_and_org_invitations.sql`: `app_user.
|
|
|
|
|
|
password_hash`/`two_factor_method`/`totp_secret`/`is_super_admin`, ny
|
|
|
|
|
|
tabell `two_factor_code` (samme hash-og-utløp-mønster som
|
|
|
|
|
|
`magic_link_token`), ny tabell `organization_invitation` (RLS
|
|
|
|
|
|
org-isolert, INGEN egen klikkbar aksept-lenke — godtas automatisk ved
|
|
|
|
|
|
neste innlogging med matchende e-post), femte
|
|
|
|
|
|
`SECURITY DEFINER`-bro `accept_pending_invitations_by_email()` (etter
|
|
|
|
|
|
`public_tournament_org` 007, `link_player_by_email` 008,
|
|
|
|
|
|
`public_org_by_slug` 009, `public_tournament_by_code` 011).
|
|
|
|
|
|
**Sesjons-STADIER innført i `app/auth.py`:** en sesjonscookie er ikke
|
|
|
|
|
|
nødvendigvis en full sesjon lenger — `create_session_token()` tar nå en
|
|
|
|
|
|
`stage`-parameter (`full`/`pending_2fa`/`must_enroll_2fa`), lagt inn som
|
|
|
|
|
|
et JWT-claim. `get_current_user` avviser eksplisitt alt annet enn `full`
|
|
|
|
|
|
ELLER en ELDRE token uten stage-claim i det hele tatt (utstedt før denne
|
|
|
|
|
|
runden — behandlet som `full` for bakoverkompatibilitet, ingen
|
|
|
|
|
|
eksisterende bruker logget brått ut). To nye avhengigheter:
|
|
|
|
|
|
`get_pending_user` (kun for 2FA-verifiseringsendepunktene) og
|
|
|
|
|
|
`get_current_or_enrolling_user` (godtar BÅDE en full sesjon — frivillig
|
|
|
|
|
|
2FA-oppsett fra kontoinnstillinger — OG `must_enroll_2fa` — tvunget
|
|
|
|
|
|
oppsett rett etter innlogging — samme oppsett-logikk dekker begge
|
|
|
|
|
|
veiene). `get_current_user_optional` fikk samme stage-sjekk for
|
|
|
|
|
|
konsistens.
|
|
|
|
|
|
**Passord (Argon2id, ikke bcrypt):** bevisst valg for å unngå bcrypt sin
|
|
|
|
|
|
stille 72-byte-trunkering, siden brukeren eksplisitt ba om korrekt
|
|
|
|
|
|
håndtering av spesialtegn/mellomrom. `POST /auth/set-password`/
|
|
|
|
|
|
`/remove-password`/`/login-password` — sistnevnte svarer med IDENTISK
|
|
|
|
|
|
401 uansett om e-posten finnes, mangler passord, eller passordet er
|
|
|
|
|
|
feil (samme anti-enumerering som magic-link).
|
|
|
|
|
|
**2FA (TOTP via `pyotp` ELLER e-post-engangskode via eksisterende SMTP,
|
|
|
|
|
|
brukerens eget valg):** `POST /auth/2fa/setup/start` genererer en
|
|
|
|
|
|
TOTP-secret UTEN å lagre den (rundturer til klienten, som ekkoer den
|
|
|
|
|
|
tilbake i `/setup/confirm` — unngår en halvferdig 2FA-tilstand i
|
|
|
|
|
|
databasen hvis brukeren forlater oppsettet). QR-kode generert
|
|
|
|
|
|
server-side (`qrcode`-biblioteket + eksisterende Pillow-avhengighet,
|
|
|
|
|
|
ingen ny ekstern tjeneste). `POST /auth/2fa/verify` fullfører en
|
|
|
|
|
|
PÅGÅENDE innlogging.
|
|
|
|
|
|
**Tvungen 2FA for org-eier/admin (ADR-021 Beslutning D):**
|
|
|
|
|
|
`user_requires_2fa_enrollment()` sjekket ved HVER innlogging (ikke bare
|
|
|
|
|
|
første gang, siden en bruker kan bli eier av en NY org etter at kontoen
|
|
|
|
|
|
allerede eksisterer uten 2FA) — verifisert eksplisitt: en fersk
|
|
|
|
|
|
org-eier uten 2FA ble korrekt blokkert fra all normal tilgang (401) og
|
|
|
|
|
|
tvunget inn i oppsett-flyten før noe annet ble tilgjengelig.
|
|
|
|
|
|
**Organisasjonseierskap (ADR-022):** `POST/GET/DELETE
|
|
|
|
|
|
/orgs/{id}/invitations` (owner→enhver rolle, admin→KUN member — ellers
|
|
|
|
|
|
en privilegie-eskaleringsvei), `PATCH/DELETE /orgs/{id}/memberships/
|
|
|
|
|
|
{id}` (owner kan endre/fjerne hvem som helst; en bruker kan ALLTID
|
|
|
|
|
|
SENKE egen rolle selv — aldri heve den, ville vært selv-forfremmelse —
|
|
|
|
|
|
og alltid forlate selv), «siste eier»-vern (409 `LAST_OWNER`, `FOR
|
|
|
|
|
|
UPDATE`-lås mot race på alle eier-rader, samme TOCTOU-mønster som
|
|
|
|
|
|
ADR-011s to-lags-grense). Superadmin (`app_user.is_super_admin`, KUN
|
|
|
|
|
|
manuelt DB-tildelt — bevisst INGEN API-vei til å gi seg selv eller
|
|
|
|
|
|
andre flagget) får en parallell autorisasjonssti
|
|
|
|
|
|
(`get_superadmin_user`) som kan sette medlemskap på ENHVER org,
|
|
|
|
|
|
uavhengig av eget medlemskap — bevisst avgrenset til nøyaktig dette,
|
|
|
|
|
|
ikke generell tilgang til andres turnering-/spillerdata.
|
|
|
|
|
|
**Tre reelle bugs funnet OG fikset UNDER scratch-testing, ingen nådde
|
|
|
|
|
|
produksjon:**
|
|
|
|
|
|
1. `verify_magic_link` sendte en rå asyncpg-`UUID` (ikke streng) videre
|
|
|
|
|
|
til sesjonsutstedelse — `jwt.encode()` sin JSON-serialisering
|
|
|
|
|
|
krasjet rått (500) på selve innloggingen. Fant umiddelbart ved første
|
|
|
|
|
|
reelle innloggingstest. Rettet med en eksplisitt `str()`.
|
|
|
|
|
|
2. OG 3. Både `2fa/setup/confirm` og `2fa/verify` kalte først den delte
|
|
|
|
|
|
`_issue_login_result()`-hjelpefunksjonen (ment for PRIMÆR
|
|
|
|
|
|
autentisering) EN GANG TIL etter at 2FA nettopp var bekreftet — som
|
|
|
|
|
|
så (korrekt, men feil kontekst) at `two_factor_method` nå var satt
|
|
|
|
|
|
og krevde EN NY runde med 2FA for akkurat den samme innloggingen,
|
|
|
|
|
|
en uendelig løkke. Fant ved å faktisk fullføre hele innloggings-
|
|
|
|
|
|
syklusen med ekte genererte TOTP-koder (`pyotp` i test-scriptet),
|
|
|
|
|
|
ikke bare ved å lese koden. Rettet ved at begge endepunktene nå
|
|
|
|
|
|
utsteder en full sesjon DIREKTE etter vellykket 2FA-bekreftelse,
|
|
|
|
|
|
ikke via gjenbruk av den generelle sjekken.
|
|
|
|
|
|
**Frontend:** `login-form.tsx` fikk en tredje modus (passord, ved siden
|
|
|
|
|
|
av magic-link og ADR-020s invitasjonskode-modus). Ny delt
|
|
|
|
|
|
`two-factor-flow.tsx` (`TwoFactorVerifyForm`/`TwoFactorSetupForm`),
|
|
|
|
|
|
brukt av BÅDE `login-form.tsx` og `verify-form.tsx` siden begge
|
|
|
|
|
|
primær-autentiseringsveiene kan returnere samme 2FA-mellomtilstand. Ny
|
|
|
|
|
|
`/account`-skjerm (sett/fjern passord, aktiver/deaktiver 2FA) og ny
|
|
|
|
|
|
`/orgs/[id]/members`-skjerm (invitere, endre rolle, fjerne/forlate),
|
|
|
|
|
|
begge lenket fra dashbordets header/org-visning. `next.config.mjs` sin
|
|
|
|
|
|
`rewrites()` utvidet med `/superadmin/:path*` (ADR-016s konsekvens for
|
|
|
|
|
|
enhver ny API-prefiks, selv om ingen frontend-UI faktisk bruker den
|
|
|
|
|
|
ennå).
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (001→012,
|
|
|
|
|
|
`test_isolation.sql` 12/12): full magic-link-bakoverkompatibilitet
|
|
|
|
|
|
(eksisterende flyt uendret for brukere uten 2FA), passord med ekte
|
|
|
|
|
|
spesialtegn/mellomrom/æøå satt og brukt til pålogging, full TOTP-runde
|
|
|
|
|
|
(oppsett→bekreft→logg ut→logg inn→krev 2FA→verifiser med ekte
|
|
|
|
|
|
`pyotp`-generert kode), full e-post-2FA-runde (samme mønster, feil kode
|
|
|
|
|
|
avvist, kode ikke gjenbrukbar), tvungen 2FA-registrering for ny
|
|
|
|
|
|
org-eier, invitasjon→auto-aksept ved førstegangsinnlogging,
|
|
|
|
|
|
rolle-eskalering-forsøk avvist på tre distinkte måter (medlem kan ikke
|
|
|
|
|
|
invitere, admin kan ikke gi eierskap, bruker kan ikke forfremme seg
|
|
|
|
|
|
selv), siste-eier-vern (både PATCH og DELETE), duplikat-invitasjon
|
|
|
|
|
|
avvist, superadmin-sti fungerer/avvises riktig i begge retninger. Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild av frontend (alle nye ruter listet).
|
|
|
|
|
|
**Rullet ut mot ekte systemer 2026-07-19**, bruker bekreftet eksplisitt:
|
|
|
|
|
|
migrasjon 012 kjørt mot ekte `teecup_db`, `test_isolation.sql` fortsatt
|
|
|
|
|
|
12/12, begge containere (`teecup_api`, `teecup_frontend`) redeployet og
|
|
|
|
|
|
bekreftet ren oppstart, `/health` og `/dashboard` fortsatt 200 (eksisterende
|
|
|
|
|
|
sesjoner uendret av stage-bakoverkompatibiliteten), `/auth/login-password`
|
|
|
|
|
|
bekreftet nåbart over ekte https, `teeoff.no` upåvirket.
|
|
|
|
|
|
**ADR-021 og ADR-022 er dermed begge helt ferdig** — backend + frontend.
|
|
|
|
|
|
Bevisst utenfor omfang: dedikert superadmin-UI (brukes via API av en
|
|
|
|
|
|
betrodd operatør), SMS som 2FA-metode.
|
|
|
|
|
|
|
|
|
|
|
|
- **Program-skjerm: tydeligere klikk-hint på øktkort (2026-07-19):** brukeren
|
|
|
|
|
|
påpekte at ingenting i grensesnittet indikerte at et øktkort er klikkbart
|
|
|
|
|
|
inn til blind draw-skjermen. Lagt til en synlig "Sett opp flights og lås
|
|
|
|
|
|
oppstilling →"-rad nederst i hvert kort (`components/tournament-program.tsx`).
|
|
|
|
|
|
Ren frontend-endring, ingen backend-rørt.
|
|
|
|
|
|
- **Brukerroller: kaptein som reell autorisasjon, deltaker-avgrenset scoring
|
|
|
|
|
|
(2026-07-19, ADR-023):** direkte oppfølging av det lenge åpne
|
|
|
|
|
|
"Brukerroller"-punktet i FEATURE_BACKLOG.md. Fire beslutninger avklart
|
|
|
|
|
|
eksplisitt med bruker (AskUserQuestion) før bygging — alle anbefalte valg.
|
|
|
|
|
|
**Bygget:** `app/team_authz.py` skrevet om — `user_is_team_captain`
|
|
|
|
|
|
(erstatter `user_may_act_for_team`) krever `is_captain=true` på
|
|
|
|
|
|
`team_roster` (eller org-eier/admin) for å legge til/fjerne deltakere og
|
|
|
|
|
|
låse et lag (`matches.py`); ny `user_is_match_participant` krever en ekte
|
|
|
|
|
|
`match_participant`-rad for brukeren i AKKURAT den matchen (valgfritt
|
|
|
|
|
|
side-spesifikk via `team_side`) for å føre/korrigere score (`scoring.py`)
|
|
|
|
|
|
— uavhengig av kapteinmerket. Nye feilkoder `NOT_TEAM_CAPTAIN` og
|
|
|
|
|
|
`NOT_MATCH_PARTICIPANT` (erstatter `NOT_ROSTERED_ON_TEAM` på disse fem
|
|
|
|
|
|
stedene). `app/routers/tournaments.py` sin `PATCH`/`POST .../roster`
|
|
|
|
|
|
håndhever nå "kun én kaptein per lag" (fjerner automatisk forrige
|
|
|
|
|
|
kapteins merke i samme transaksjon).
|
|
|
|
|
|
**Reelt funn FØR utrulling, ikke antatt:** sjekket (kun lesing, superbruker
|
|
|
|
|
|
mot ekte `teecup_db`) om noen eksisterende lag ville blitt låst ute av en
|
|
|
|
|
|
ren kaptein-only-regel — "De Unge" i "De Gamle er Eldst" har i dag 0 av 2
|
|
|
|
|
|
roster-rader merket kaptein. Designet derfor en bevisst fallback i
|
|
|
|
|
|
`user_is_team_captain`: har laget INGEN utpekt kaptein ennå, godtas enhver
|
|
|
|
|
|
rostret spiller i stedet for å låse laget helt ute. Ingen lag hadde flere
|
|
|
|
|
|
kapteiner, så "kun én kaptein"-håndhevelsen krevde ingen data-opprydning.
|
|
|
|
|
|
**Reell bug funnet OG fikset UNDER scratch-testing:** `user_is_match_
|
|
|
|
|
|
participant` sin SQL sammenlignet `mp.team_side` (enum-kolonne) direkte
|
|
|
|
|
|
mot en tekst-parameter uten cast når `team_side=None` (hole_result-modus)
|
|
|
|
|
|
— ga en rå 500 (`UndefinedFunctionError: operator does not exist: team_side
|
|
|
|
|
|
= text`). Rettet med et eksplisitt `mp.team_side::text = $3`.
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container):
|
|
|
|
|
|
et 15-punkts Python/httpx-testskript som simulerte fire innloggede
|
|
|
|
|
|
brukere (organisator + tre rostrede spillere på to lag) gjennom hele
|
|
|
|
|
|
syklusen — lag uten kaptein tillater enhver rostret (fallback bekreftet),
|
|
|
|
|
|
kaptein utpekt fjerner andre rostredes rettighet, "kun én kaptein" bekreftet
|
|
|
|
|
|
(ny kaptein avsetter automatisk forrige), org-admin fungerer uendret
|
|
|
|
|
|
uavhengig av kaptein, og scoring (`hole_result`-modus) bekreftet begrenset
|
|
|
|
|
|
til faktiske matchdeltakere (en kaptein som IKKE selv spiller matchen ble
|
|
|
|
|
|
korrekt avvist med `NOT_MATCH_PARTICIPANT`, mens en faktisk deltaker og
|
|
|
|
|
|
org-admin begge fikk føre score). Måtte også oppdage og legge til et
|
|
|
|
|
|
forutsetning-steg underveis: `get_authorized_org` krever
|
|
|
|
|
|
`organization_membership` for ALLE org-scopede endepunkter uansett — en
|
|
|
|
|
|
rostret spiller må derfor også være invitert som org-medlem (minimum
|
|
|
|
|
|
'member', ADR-022s invitasjonsflyt) for i det hele tatt å nå
|
|
|
|
|
|
team_authz-vurderingen; ikke en bug, men en forutsetning testskriptet
|
|
|
|
|
|
først manglet. `test_isolation.sql` 12/12 uendret (ingen skjemaendring).
|
|
|
|
|
|
Ekte typesjekket produksjonsbuild av frontend (øktkort-hintet fra samme
|
|
|
|
|
|
runde) kjørt og bekreftet, alle 11 ruter listet.
|
|
|
|
|
|
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
|
|
|
|
|
begge containere boot-et rent (`Application startup complete`, Next.js
|
|
|
|
|
|
`Ready`), `/health` og `/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Walkover/konsesjon LIVE (2026-07-19, ADR-024):** direkte oppfølging av
|
|
|
|
|
|
Brukerroller-runden samme dag — brukeren ba eksplisitt om å ta fatt på
|
|
|
|
|
|
dette som naturlig neste steg. Løser det lenge kjente hullet: en side som
|
|
|
|
|
|
aldri stiller nok spillere fikk aldri beregnet handicap og matchen kunne
|
|
|
|
|
|
derfor aldri avgjøres — hang uendelig.
|
|
|
|
|
|
Fire beslutninger avklart eksplisitt med bruker (AskUserQuestion) før
|
|
|
|
|
|
bygging — tre anbefalte valg, ett (omfang: match+turnering-nivå samtidig,
|
|
|
|
|
|
ikke bare match) valgt utover anbefalingen.
|
|
|
|
|
|
**Bygget:** ny `apply_concession`-hjelpefunksjon i `app/routers/
|
|
|
|
|
|
scoring.py` (skriver til de samme fire kolonnene som
|
|
|
|
|
|
`recompute_and_cache_match_state` -- `status_text`/`points_side_a/b`/
|
|
|
|
|
|
`leading_side` -- men direkte, ikke utledet fra hull). Ny
|
|
|
|
|
|
`POST /orgs/{id}/matches/{id}/concede` (kun kaptein for det TAPENDE laget,
|
|
|
|
|
|
speiler ekte golf-etikette -- du gir bort DITT tap, krever ikke seier på
|
|
|
|
|
|
motstanderens vegne -- eller org-admin, gjenbruker `user_is_team_captain`
|
|
|
|
|
|
fra ADR-023 uendret). Ny `POST /orgs/{id}/tournaments/{id}/concede`
|
|
|
|
|
|
(`app/routers/tournaments.py`) som gir opp ALLE ikke-avgjorte matcher
|
|
|
|
|
|
laget har i turneringen i én operasjon -- v1s to-lags-grense (ADR-011)
|
|
|
|
|
|
gjør dette trivielt (bare én motstander uansett), `FOR UPDATE`-låser alle
|
|
|
|
|
|
berørte match-rader (samme race-vern som ellers). Kan erklæres uansett
|
|
|
|
|
|
hvor mange hull som allerede er registrert (match-play teller kun
|
|
|
|
|
|
seier/tap/delt for poeng, ikke marginen) -- allerede registrerte hull i
|
|
|
|
|
|
`hole_score`/`match_hole_result` forblir urørt, kun matchens
|
|
|
|
|
|
avgjørelses-felt endres.
|
|
|
|
|
|
**Frontend:** `session-scorecard.tsx` fikk en kollapsbar
|
|
|
|
|
|
"Gi opp matchen (walkover)"-seksjon (to knapper, én per lag, med
|
|
|
|
|
|
bekreftelsessteg) synlig når matchen ikke er avgjort.
|
|
|
|
|
|
`tournament-detail.tsx` sin `TeamPanel` fikk en tilsvarende "Gi opp resten
|
|
|
|
|
|
av turneringen for laget"-knapp nederst i hvert lagkort. Ingen
|
|
|
|
|
|
klientside-forhåndsfiltrering på kapteinstatus noe sted -- begge knappene
|
|
|
|
|
|
vises alltid, en 403 fra backend vises bare som vanlig feiltekst (samme
|
|
|
|
|
|
mønster som resten av appen).
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs
|
|
|
|
|
|
API-container): to separate 15-punkts Python/httpx-testløp. Match-nivå:
|
|
|
|
|
|
vinnende lags kaptein NEKTES å konsedere på vegne av det tapende laget
|
|
|
|
|
|
(kan ikke kreve seier på andres vegne), tapende lags kaptein FÅR, allerede
|
|
|
|
|
|
avgjort match avvist 409, fremmed team_id avvist 400, konsesjon ETTER at
|
|
|
|
|
|
ett hull allerede er registrert bekreftet å fungere OG bekreftet at det
|
|
|
|
|
|
registrerte hull-resultatet forblir synlig i scorekortet etterpå (ikke
|
|
|
|
|
|
overskrevet), org-admin FÅR konsedere direkte uavhengig av kapteinmerke.
|
|
|
|
|
|
Turnering-nivå: feil lags kaptein nektes, riktig kaptein FÅR gi opp
|
|
|
|
|
|
resten (kun de faktisk ikke-avgjorte matchene telles -- allerede avgjorte
|
|
|
|
|
|
matcher fra match-nivå-testene i samme løp ble korrekt hoppet over),
|
|
|
|
|
|
gjentatt kall er trygt (0 nye, idempotent i praksis), ukjent team_id gir
|
|
|
|
|
|
404. `test_isolation.sql` 12/12 uendret (ingen skjemaendring). Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild av frontend kjørt og bekreftet, alle 11
|
|
|
|
|
|
ruter listet.
|
|
|
|
|
|
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
|
|
|
|
|
begge containere boot-et rent, `/health` og `/dashboard` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Turnering-status via API, SCRATCH-VERIFISERT (2026-07-19):** brukeren
|
|
|
|
|
|
valgte dette som neste steg etter walkover/konsesjon-runden (fikk velge
|
|
|
|
|
|
mellom denne lille opprydningen og å starte Kommunikasjon-runden). `status`
|
|
|
|
|
|
(draft/active/completed/archived) har ligget i skjemaet siden migrasjon
|
|
|
|
|
|
001, men INGEN endepunkt kunne endre det — kun `INSERT`-defaulten `'draft'`
|
|
|
|
|
|
fra `create_tournament`.
|
|
|
|
|
|
**Bygget:** lagt til i `TournamentUpdate` (`app/routers/tournaments.py`),
|
|
|
|
|
|
settes via det eksisterende generiske `PATCH /orgs/{id}/tournaments/{id}`
|
|
|
|
|
|
(`exclude_unset`-mønsteret, ekte PATCH-semantikk uendret).
|
|
|
|
|
|
**Reelt funn UNDER scratch-testing, ikke antatt riktig på forhånd:**
|
|
|
|
|
|
`status` er -- ulikt `visibility` (som er ren `text`+`CHECK`) -- en EKTE
|
|
|
|
|
|
Postgres ENUM-type (`tournament_status`). Den generiske
|
|
|
|
|
|
`set_clauses`-byggeren (`f"{key} = ${i}"`) hadde derfor trengt et
|
|
|
|
|
|
eksplisitt cast for at asyncpg sin ukjent-typede parameter skulle løses
|
|
|
|
|
|
riktig mot en enum-kolonne -- lagt til en spesialsjekk (`::tournament_status`
|
|
|
|
|
|
kun for `status`-nøkkelen) FØR jeg antok mekanismen "bare fungerer" fordi
|
|
|
|
|
|
den gjør det for de andre feltene.
|
|
|
|
|
|
Bevisst INGEN tilstandsmaskin/overgangsregler bygget (kan f.eks. gå fra
|
|
|
|
|
|
`completed` tilbake til `draft` fritt) -- samme tillitsnivå som resten av
|
|
|
|
|
|
appen, ikke etterspurt.
|
|
|
|
|
|
**Frontend:** `tournament-status-badge.tsx` fikk en ny redigerbar
|
|
|
|
|
|
`TournamentStatusPicker` (dropdown over de fire verdiene, optimistisk
|
|
|
|
|
|
UI-oppdatering med rollback ved feil), koblet inn i `tournament-detail.tsx`
|
|
|
|
|
|
sin header ved siden av invitasjonskode-chipen. Den eksisterende
|
|
|
|
|
|
skrivebeskyttede `TournamentStatusBadge` (dashbordets kortliste) urørt.
|
|
|
|
|
|
**Verifisert i scratch:** ny turnering får riktig default `draft`, PATCH
|
|
|
|
|
|
til `active` bekreftet enum-castet faktisk løser problemet, PATCH med
|
|
|
|
|
|
status+et annet felt samtidig fungerer, PATCH UTEN status-felt lar
|
|
|
|
|
|
verdien stå urørt (regresjon på eksisterende PATCH-semantikk), ugyldig
|
|
|
|
|
|
status-verdi avvist med 422 (Pydantic-mønster), `GET`-listen viser samme
|
|
|
|
|
|
verdi etterpå. `test_isolation.sql` 12/12 uendret (ingen skjemaendring).
|
|
|
|
|
|
Ekte typesjekket produksjonsbuild kjørt og bekreftet.
|
|
|
|
|
|
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
|
|
|
|
|
begge containere boot-et rent, `/health` og `/dashboard` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Kommunikasjon LIVE (2026-07-19, ADR-025) — det største enkeltløftet i
|
|
|
|
|
|
prosjektet så langt:** lag-intern chat («det hemmelige rommet») + offentlig
|
|
|
|
|
|
runde-feed («Banter Board»), begge med bilder og ekte WebSocket-sanntid,
|
|
|
|
|
|
bygget i samme runde. Fire hovedbeslutninger avklart eksplisitt med bruker
|
|
|
|
|
|
(AskUserQuestion) før bygging — brukeren valgte den mest ambisiøse
|
|
|
|
|
|
kombinasjonen på alle fire (begge deler nå, WebSockets fremfor polling,
|
|
|
|
|
|
ekte privat chat, bilder fra start).
|
|
|
|
|
|
**Datamodell:** ny migrasjon `013_messaging.sql` — delt `message`-tabell
|
|
|
|
|
|
med `scope`-diskriminator (`team`/`tournament_feed`) i stedet for to
|
|
|
|
|
|
separate tabeller, RLS org_isolation som ellers. `author_display_name`
|
|
|
|
|
|
FRYSES ved skrivetidspunkt (samme prinsipp som handicap-snapshot,
|
|
|
|
|
|
ADR-007) — spillerens `player.display_name` i org-en hvis den finnes,
|
|
|
|
|
|
ellers e-postens lokaldel (dekker org-ansatte uten egen spillerprofil).
|
|
|
|
|
|
**Lag-chat er BEVISST ekte privat** — ny `user_is_rostered_on_team` i
|
|
|
|
|
|
`app/team_authz.py`, med VILJE uten org-admin-fallback, ulikt de to andre
|
|
|
|
|
|
funksjonene i samme fil (`user_is_team_captain`/`user_is_match_participant`,
|
|
|
|
|
|
ADR-023, som begge har et slikt unntak). Første sted i hele appen der
|
|
|
|
|
|
org-eier/admin er strukturelt utestengt fra noe.
|
|
|
|
|
|
**Offentlig feed:** LESING gjenbruker `registration.py` sitt eksisterende
|
|
|
|
|
|
trenivå-visibility-mønster (ADR-018) helt uendret, inkl. anonym tilgang.
|
|
|
|
|
|
POSTING er strengere enn lesing — krever ekte innlogging OG org-
|
|
|
|
|
|
medlemskap/faktisk deltakelse, selv på en `public`-synlig turnering (en
|
|
|
|
|
|
helt urelatert innlogget bruker skal ikke kunne poste på en fremmed
|
|
|
|
|
|
offentlig side). Moderering: forfatteren selv ELLER org-eier/admin kan
|
|
|
|
|
|
slette et feed-innlegg (motsatt av lag-chatten, som ikke har noen
|
|
|
|
|
|
ekstern moderator).
|
|
|
|
|
|
**`registration.py` sine fire interne hjelpefunksjoner gjort delt**
|
|
|
|
|
|
(fjernet ledende understrek — samme "gjort delt for gjenbruk"-mønster som
|
|
|
|
|
|
tidligere runder): `resolve_org`, `is_participant`, `code_matches`,
|
|
|
|
|
|
`check_visibility`. `team_authz.py` sin `_is_org_admin` likeens →
|
|
|
|
|
|
`is_org_admin`. Ingen atferdsendring, kun navn, for at `messaging.py`
|
|
|
|
|
|
skulle kunne gjenbruke dem uendret i stedet for å duplisere logikk.
|
|
|
|
|
|
**WebSockets, ikke polling:** in-memory tilkoblingsregister PER PROSESS i
|
|
|
|
|
|
`app/routers/messaging.py` — trygt med dagens ene `teecup_api`-container,
|
|
|
|
|
|
men deles IKKE på tvers av flere prosesser/containere (samme klasse
|
|
|
|
|
|
begrensning som den allerede aksepterte in-memory-cachen, se ARCHITECTURE_
|
|
|
|
|
|
DECISIONS.md "Åpne spørsmål"). Ny `get_current_user_from_websocket` i
|
|
|
|
|
|
`app/auth.py` — WS-ruter kan ikke bruke `get_current_user`/
|
|
|
|
|
|
`get_authorized_org` direkte via `Depends()` (de er `Request`-typet, ingen
|
|
|
|
|
|
ekte HTTP Request finnes i en WS-scope), derfor en bevisst minimal, egen
|
|
|
|
|
|
kopi av samme cookie-dekode-/oppslagslogikk.
|
|
|
|
|
|
**Reell infrastrukturoppdagelse FØR noe ble forsøkt, ikke i etterkant:**
|
|
|
|
|
|
Next.js sin `rewrites()` proxyer ikke WebSocket-oppgraderinger pålitelig i
|
|
|
|
|
|
"standalone"-modus — løst likt som MinIO-media-ruten (ADR-018): en egen
|
|
|
|
|
|
Caddy-rute (`handle /ws/* { reverse_proxy teecup_api:8000 }`) rett til
|
|
|
|
|
|
API-et, forbi Next.js/`teecup_frontend` helt. Caddyfile ligger i det
|
|
|
|
|
|
SEPARATE `/opt/teeoff`-repoet — samme stale-bind-mount-inode-oppførsel som
|
|
|
|
|
|
ALLE tidligere Caddyfile-runder (graceful reload plukker ikke opp
|
|
|
|
|
|
endringen), løst likt: full `docker restart teeoff_caddy`, brukeren
|
|
|
|
|
|
bekreftet eksplisitt på forhånd, noen sekunders nedetid for `teeoff.no`.
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container
|
|
|
|
|
|
med `websockets`-Python-biblioteket installert kun for testen): et
|
|
|
|
|
|
20-punkts asyncio/httpx/websockets-testskript som dekket BEGGE
|
|
|
|
|
|
meldingstyper ende-til-ende. Kritiske personvern-/sanntid-funn, alle
|
|
|
|
|
|
bekreftet med ekte tilkoblinger (ikke bare REST):
|
|
|
|
|
|
- Rostret spiller på lag A FÅR lese/skrive lag A sin chat; rostret spiller
|
|
|
|
|
|
på lag B NEKTES; **organisatoren (org-eier) NEKTES OGSÅ** — bekreftet
|
|
|
|
|
|
BÅDE over REST (`GET`) og over selve WebSocket-håndtrykket (avvist med
|
|
|
|
|
|
lukkekode 4403 før `accept()` i det hele tatt kalles).
|
|
|
|
|
|
- Sanntid bekreftet reelt: A1 koblet til lag A sin chat-socket, A2 sendte
|
|
|
|
|
|
en melding over vanlig REST, A1 mottok den umiddelbart over den åpne
|
|
|
|
|
|
WebSocket-tilkoblingen (ikke bare at REST-svaret så riktig ut).
|
|
|
|
|
|
- Bildeopplasting i chat bekreftet (ekte AVIF-konvertert `image_url`
|
|
|
|
|
|
returnert).
|
|
|
|
|
|
- Offentlig feed: anonym NEKTES å poste (401), en tilfeldig INNLOGGET men
|
|
|
|
|
|
uvedkommende bruker NEKTES (403 `NOT_A_PARTICIPANT`), org-medlem FÅR,
|
|
|
|
|
|
en faktisk deltaker (rostret, IKKE org-medlem) FÅR — beviser
|
|
|
|
|
|
`is_participant`-veien fungerer uavhengig av `is_member`-veien. Anonym
|
|
|
|
|
|
WebSocket-tilkobling til en `public`-synlig turnerings feed FÅR lov og
|
|
|
|
|
|
mottar sanntidsoppdateringer.
|
|
|
|
|
|
- Moderering bekreftet: forfatter sletter eget innlegg, org-admin sletter
|
|
|
|
|
|
ANDRES innlegg (feeden), uvedkommende NEKTES å slette andres innlegg.
|
|
|
|
|
|
`test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket
|
|
|
|
|
|
produksjonsbuild av frontend kjørt og bekreftet, inkl. den nye
|
|
|
|
|
|
`/tournaments/[id]/teams/[teamId]/chat`-ruten.
|
|
|
|
|
|
**Frontend:** ny `components/team-chat.tsx` (meldingsliste med egen/andres-
|
|
|
|
|
|
styling, bildeopplasting, sanntid via nettleserens native `WebSocket`,
|
|
|
|
|
|
slett-egen-melding), lenket fra en ny chat-ikon-knapp i `tournament-
|
|
|
|
|
|
detail.tsx` sin `TeamPanel`. Ny seksjon `TournamentFeed` i
|
|
|
|
|
|
`components/public-tournament.tsx` (nederst på den offentlige
|
|
|
|
|
|
turneringssiden) — viser 401/403-svar fra posting som forklarende
|
|
|
|
|
|
inline-tekst ("logg inn for å poste" / "du må være medlem/deltaker") i
|
|
|
|
|
|
stedet for en generisk feilmelding.
|
|
|
|
|
|
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt for alle tre
|
|
|
|
|
|
stegene (migrasjon, containere, Caddy-restart): migrasjon 013 kjørt mot
|
|
|
|
|
|
ekte `teecup_db`, `test_isolation.sql` fortsatt 12/12, begge containere
|
|
|
|
|
|
boot-et rent, Caddy validert (`caddy validate` — "Valid configuration")
|
|
|
|
|
|
FØR restart, restarten ren (ingen feil i loggen). Verifisert grundig
|
|
|
|
|
|
etterpå: `/health`/`dashboard` → 200, `teeoff.no` → 200, et ekte
|
|
|
|
|
|
`wss://`-håndtrykk over produksjons-https bekreftet å nå helt frem til
|
|
|
|
|
|
applikasjonslaget (testet mot en ukjent turnering-id — ingen ekte data
|
|
|
|
|
|
berørt — ga korrekt 404 fra selve WS-ruten, ikke en Caddy/Next.js-feil),
|
|
|
|
|
|
og et ren-HTTP-kall mot en `/ws/*`-sti bekreftet å returnere FastAPI sin
|
|
|
|
|
|
egen JSON-404 (`{"detail":"Not Found"}`) og ikke Next.js sin HTML-404 —
|
|
|
|
|
|
beviser Caddy-ruten faktisk treffer `teecup_api`, ikke `teecup_frontend`.
|
|
|
|
|
|
|
|
|
|
|
|
- **Tilskuer-rolle LIVE (2026-07-19, ADR-026):** brukeren valgte dette som
|
|
|
|
|
|
neste steg rett etter Kommunikasjon-runden — "tilskuer" var bevisst
|
|
|
|
|
|
utsatt til feed-synligheten (ADR-025) fantes, og nå gjorde den det.
|
|
|
|
|
|
**Kjernebeslutning:** ingen ny rolle/tabell — "tilskuer" er ganske enkelt
|
|
|
|
|
|
enhver som kan SE en turnering per `tournament.visibility` (ADR-018),
|
|
|
|
|
|
utvidet til også å dekke LIVE-data (leaderboard, matcher, scorekort), ikke
|
|
|
|
|
|
bare info-siden/programtidene som før.
|
|
|
|
|
|
**Bygget:** `GET /orgs/.../leaderboard`, `.../sessions/{id}/matches` og
|
|
|
|
|
|
`.../matches/{id}/scorecard` fantes allerede (organisator-/spiller-siden),
|
|
|
|
|
|
men krevde org-medlemskap — en ren spectator kunne aldri se dem. Løst ved
|
|
|
|
|
|
å ekstrahere den delte kjernelogikken til gjenbrukbare funksjoner
|
|
|
|
|
|
(`fetch_leaderboard` i tournaments.py, `fetch_matches` i matches.py,
|
|
|
|
|
|
`fetch_scorecard` i scoring.py — samme "gjort delt"-mønster som tidligere
|
|
|
|
|
|
runder), og la tre nye offentlige endepunkter i `registration.py` kalle
|
|
|
|
|
|
dem etter egen visibility-sjekk. `own_team_ids()` (`blind_draw.py`) gjort
|
|
|
|
|
|
null-sikker (`user_id: str | None`) -- en anonym leser har per definisjon
|
|
|
|
|
|
ingen egne lag, korrekt oppførsel er tom mengde (ser kun avslørte
|
|
|
|
|
|
matcher), ikke en feil.
|
|
|
|
|
|
**To nye sikkerhetssjekker funnet under DESIGN, ikke i etterkant, samme
|
|
|
|
|
|
disiplin som tidligere ADR-018 Beslutning B-lærdommen:**
|
|
|
|
|
|
1. `session_id`/`match_id` i URL-en må eksplisitt verifiseres å høre til
|
|
|
|
|
|
NØYAKTIG `tournament_id` i samme URL — `org_connection()` setter kun
|
|
|
|
|
|
TENANT-grensen (RLS), ikke at stiens id-er faktisk henger sammen. Uten
|
|
|
|
|
|
dette kunne noen med tilgang til én offentlig turnering i en
|
|
|
|
|
|
organisasjon lest en HVILKEN SOM HELST økt/match i samme organisasjon
|
|
|
|
|
|
(inkl. en privat en) ved å gjette/prøve id-er.
|
|
|
|
|
|
2. Scorekortet krever eksplisitt at BEGGE lag har låst oppstillingen
|
|
|
|
|
|
(blind draw, ADR-013) — leaderboard/matchliste arver reveal-skjuling
|
|
|
|
|
|
automatisk via `own_team_ids()`, men scorekortet har ingen tilsvarende
|
|
|
|
|
|
innebygd sjekk.
|
|
|
|
|
|
**Bruker valgte omfang utover anbefalingen:** BÅDE leaderboard+matchliste
|
|
|
|
|
|
OG fullt hull-for-hull-scorekort per match i samme runde (anbefalingen var
|
|
|
|
|
|
kun de to første).
|
|
|
|
|
|
**Frontend:** ny `/t/[id]/live`-side (`components/public-live.tsx`) —
|
|
|
|
|
|
fargesegmentert stillingsbar (faktisk + projisert, samme visuelle idé som
|
|
|
|
|
|
den org-autentiserte leaderboard-skjermen, men egen enklere implementasjon
|
|
|
|
|
|
siden komponentene har ulik autentiseringskontekst), utvidbar øktliste →
|
|
|
|
|
|
matchliste → hull-for-hull-scorekort (fargede hull-chips per lag). Lenket
|
|
|
|
|
|
fra hovedsiden (`public-tournament.tsx`) med en ny "Følg live"-knapp.
|
|
|
|
|
|
**Verifisert grundig mot fersk `teecup_scratch`** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container):
|
|
|
|
|
|
16 automatiserte sjekker, inkl. en PRESIS test av den nye tenant-vs-sti-
|
|
|
|
|
|
sjekken (ikke bare en ukjent id, men en EKTE ANNEN turnering i SAMME org
|
|
|
|
|
|
— bekreftet at match/økt fra turnering A fortsatt ikke kan leses via
|
|
|
|
|
|
turnering B sin offentlige URL), full blind-draw-skjuling FØR/ETTER
|
|
|
|
|
|
reveal for en anonym leser, kode-overstyring (ADR-020) fungerer uendret
|
|
|
|
|
|
for de nye endepunktene, scorekort eksplisitt nektet før reveal. Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild av frontend, inkl. den nye `/t/[id]/live`-
|
|
|
|
|
|
ruten.
|
|
|
|
|
|
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
|
|
|
|
|
begge containere boot-et rent, `/health`/`dashboard` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **"Følg live"-siden koblet til sanntid (2026-07-19, ADR-027):** brukeren
|
|
|
|
|
|
fulgte anbefalingen fra forrige runde — `/t/[id]/live` (bygget i
|
|
|
|
|
|
tilskuer-runden samme dag) krevde omlasting for nye resultater.
|
|
|
|
|
|
**Bygget:** ny, RUTEFRI modul `app/realtime.py` (in-memory
|
|
|
|
|
|
tilkoblingsregister + `broadcast_live_update(tournament_id)`) -- ligger
|
|
|
|
|
|
bevisst BAK alle routere i importgrafen for å unngå en sirkulær import
|
|
|
|
|
|
(`registration.py` importerer allerede fra `scoring.py`/`tournaments.py`/
|
|
|
|
|
|
`matches.py`, og `messaging.py` importerer fra `registration.py`; en
|
|
|
|
|
|
kringkastingsfunksjon i noen av routerne ville derfor bitt seg selv i
|
|
|
|
|
|
halen). Kringkastingen er lagt INN I `recompute_and_cache_match_state` og
|
|
|
|
|
|
`apply_concession` selv (sistnevnte fikk en ny påkrevd `tournament_id`-
|
|
|
|
|
|
parameter) -- kallerne (hole-score/hole-result-innsending, walkover på
|
|
|
|
|
|
både match- og turnering-nivå) trenger ikke huske å gjøre noe selv. Nytt
|
|
|
|
|
|
WS-endepunkt `/ws/public/tournaments/{id}/live` i `messaging.py`
|
|
|
|
|
|
(gjenbruker `/ws/*`-Caddy-ruten fra ADR-025 uendret -- ingen ny
|
|
|
|
|
|
infrastruktur). Sender bevisst kun et "noe endret seg, hent på nytt"-
|
|
|
|
|
|
signal, ikke selve dataene -- unngår å duplisere leaderboardets
|
|
|
|
|
|
projeksjons-regnestykke/blind draw-filtrering i kringkastings-payloaden.
|
|
|
|
|
|
**Frontend:** `components/public-live.tsx` åpner en WebSocket ved
|
|
|
|
|
|
montering; hver melding teller opp en `refreshKey` som utløser refetch av
|
|
|
|
|
|
leaderboardet ALLTID, og av en økts matcher/et scorekort KUN hvis det
|
|
|
|
|
|
faktisk er utvidet/åpent på skjermen akkurat da (ingen unødvendige kall
|
|
|
|
|
|
for lukket innhold). Liten pulserende prikk lagt til ved siden av
|
|
|
|
|
|
"Følg live"-teksten som visuell bekreftelse.
|
|
|
|
|
|
**Verifisert i scratch:** anonym avvist på live-WS for en `org`-synlig
|
|
|
|
|
|
turnering (samme visibility-sjekk som REST), satt til `public`, deretter
|
|
|
|
|
|
bekreftet FAKTISK sanntidsmottak (ikke bare at REST-svaret så riktig ut)
|
|
|
|
|
|
for BÅDE et vanlig hull-resultat OG en walkover-konsesjon -- en åpen
|
|
|
|
|
|
WebSocket-tilkobling mottok kringkastings-signalet i begge tilfeller.
|
|
|
|
|
|
Leaderboard fortsatt korrekt lesbar over REST etterpå (regresjon). Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild kjørt og bekreftet.
|
|
|
|
|
|
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, ingen ny Caddy-endring, `docker compose up -d --build
|
|
|
|
|
|
teecup_api teecup_frontend`, begge containere boot-et rent. Verifisert
|
|
|
|
|
|
grundig etterpå: `/health`/`dashboard` → 200, `teeoff.no` upåvirket, OG et
|
|
|
|
|
|
ekte `wss://`-håndtrykk mot den NYE `/live`-ruten over produksjons-https
|
|
|
|
|
|
(ukjent turnering-id, ingen ekte data berørt) ga korrekt 404 fra selve
|
|
|
|
|
|
applikasjonslaget.
|
|
|
|
|
|
|
|
|
|
|
|
- **Fire brukerrapporterte UI-/UX-hull, DIAGNOSTISERT OG NOTERT, IKKE fikset
|
|
|
|
|
|
(2026-07-19):** brukeren rapporterte fire ting fra faktisk bruk av
|
|
|
|
|
|
`teecup.teeoff.no` rett før PWA-runden startet. Root cause funnet ved
|
|
|
|
|
|
kodegjennomgang for tre av fire (ikke gjettet). Full detalj i
|
|
|
|
|
|
FEATURE_BACKLOG.md sin nye seksjon "Rapporterte UI-/UX-hull (2026-07-19)".
|
|
|
|
|
|
Kort:
|
|
|
|
|
|
1. Dashboard-turneringskortet viser "Ingen datoer satt" alltid — leser
|
|
|
|
|
|
`tournament.start_date`/`end_date` (eget felt, ADR-015), som INGEN
|
|
|
|
|
|
UI-skjema noensinne skriver til. Skal enten få et faktisk skjemafelt,
|
|
|
|
|
|
eller kortet bør heller utlede datoen fra øktenes `scheduled_at`.
|
|
|
|
|
|
2. **Reell, bekreftet rewrite-bug:** `/orgs/{id}/members` gir en rå
|
|
|
|
|
|
FastAPI-404 (`{"detail":"Not Found"}`) i stedet for medlemssiden.
|
|
|
|
|
|
`next.config.mjs` sin `rewrites()` returnerer en plain array (implisitt
|
|
|
|
|
|
"afterFiles") — DYNAMISKE Next.js-sider sjekkes ETTER rewrites, så
|
|
|
|
|
|
`/orgs/:path*`-proxy-regelen (ADR-016) fanger kallet FØR
|
|
|
|
|
|
`app/orgs/[id]/members/page.tsx` noensinne nås. Dette er den FØRSTE
|
|
|
|
|
|
frontend-siden som er nestet direkte under et allerede proxyet prefiks
|
|
|
|
|
|
— ingen tidligere skjerm har truffet dette. Selve siden/komponenten er
|
|
|
|
|
|
riktig bygget, kun ruten dit er blokkert.
|
|
|
|
|
|
3. Ingen UI-vei til å opprette en ANDRE organisasjon når man allerede har
|
|
|
|
|
|
én — `CreateOrganizationState` i `dashboard.tsx` vises kun ved null
|
|
|
|
|
|
org-er. Backend støtter det fullt ut allerede (ADR-021).
|
|
|
|
|
|
4. Ingen sammendrag/indikator noe sted for "alle runder har fått dato" —
|
|
|
|
|
|
må sjekkes manuelt per øktkort på program-skjermen.
|
|
|
|
|
|
**Ingen av de fire fikset i denne runden** — kun dokumentert på brukerens
|
|
|
|
|
|
eksplisitte instruks, PWA-runden prioriteres først.
|
|
|
|
|
|
|
|
|
|
|
|
- **PWA: installasjon + full offline scoreregistrering, BYGGET, IKKE ENNÅ
|
|
|
|
|
|
RULLET UT (2026-07-19, ADR-028):** bruker valgte det mest ambisiøse
|
|
|
|
|
|
omfanget (installerbar app OG offline scoreregistrering, ikke bare
|
|
|
|
|
|
installasjon), og valgte å generere enkle ikoner nå fremfor å vente på
|
|
|
|
|
|
ekte design (med eksplisitt beskjed om at de er midlertidige).
|
|
|
|
|
|
**Ikoner:** ingen `PIL`/`rsvg-convert`/`imagemagick` tilgjengelig i miljøet
|
|
|
|
|
|
— løst med et scratch npm-prosjekt (`sharp@0.33.5`, node18-kompatibel
|
|
|
|
|
|
versjon; nyeste `sharp` krever node ≥20 og feilet først) som genererte et
|
|
|
|
|
|
enkelt grønt golf-flagg-ikonsett (`public/icons/icon-192.png`,
|
|
|
|
|
|
`icon-512.png`, `icon-maskable-512.png`) + erstattet den gamle
|
|
|
|
|
|
`public/apple-icon.png` (var v0.app sin generiske plassholderlogo, ikke
|
|
|
|
|
|
TeeCup-merkevare i det hele tatt).
|
|
|
|
|
|
**Bygget:** `app/manifest.ts` (Next.js sin innebygde manifest-generator,
|
|
|
|
|
|
ikke en statisk `manifest.json`), `appleWebApp`-metadata i `layout.tsx`
|
|
|
|
|
|
(iOS leser ikke manifest.json for hjemskjerm-oppførsel),
|
|
|
|
|
|
`components/sw-register.tsx` (stille no-op uten SW-støtte),
|
|
|
|
|
|
hå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.
|
|
|
|
|
|
|
|
|
|
|
|
- **Tre organisator-oppfølgingspunkter fra FEATURE_BACKLOG.md, ALLE
|
|
|
|
|
|
BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE (2026-07-28), samme dag:**
|
|
|
|
|
|
brukeren ba om at alle tre gjenstående, godt avgrensede oppfølgings-
|
|
|
|
|
|
punkter fra en tidligere gjennomgang av `.md`-filene tas i én runde,
|
|
|
|
|
|
inkludert oppdatering av de relevante backlog-/beslutningsfilene.
|
|
|
|
|
|
1. **Flytte spiller mellom lag:** ny
|
|
|
|
|
|
`POST /orgs/{id}/teams/{team_id}/roster/{roster_id}/move`
|
|
|
|
|
|
(`{"target_team_id": ...}`, `app/routers/tournaments.py`) —
|
|
|
|
|
|
atomisk `UPDATE team_roster SET team_id = ...`, avviser 409
|
|
|
|
|
|
`ALREADY_IN_MATCH` hvis spilleren allerede er lagt til i en match
|
|
|
|
|
|
(roster-raden er referert av `match_participant.team_roster_id`
|
|
|
|
|
|
med `ON DELETE RESTRICT` -- en flytting ville da gjort matchens
|
|
|
|
|
|
`team_side` inkonsistent med spillerens faktiske lag), 400 ved
|
|
|
|
|
|
flytting til samme lag, 404 ved ukjent mållag/roster-id.
|
|
|
|
|
|
Kapteinmerket nullstilles eksplisitt ved flytting (følger ikke med
|
|
|
|
|
|
til det nye laget). Frontend: ny "Flytt til {annet lag}"-handling i
|
|
|
|
|
|
`TeamPanel` sin per-spiller-meny (`tournament-detail.tsx`) — v1s
|
|
|
|
|
|
to-lags-grense (ADR-011) gjør målet entydig, ingen dropdown
|
|
|
|
|
|
nødvendig. Ingen migrasjon.
|
|
|
|
|
|
2. **Individuell rangering PER ØKT i en org-turnering:** ny
|
|
|
|
|
|
`GET /orgs/{id}/sessions/{id}/individual-leaderboard` — bevisst
|
|
|
|
|
|
AVGRENSET til én økt (ikke summert på tvers av turneringen, samme
|
|
|
|
|
|
avklaring som rundeleaderboardets omfang tidligere), og KUN
|
|
|
|
|
|
meningsfull for `scoring_mode='stroke'`-økter i individuell-ball-
|
|
|
|
|
|
format (singles/fourball; delt-ball-formater og hole_result-modus
|
|
|
|
|
|
avvist med 400, ikke krasj, siden de ikke har noen individuell
|
|
|
|
|
|
brutto-score å rangere fra). Gjenbruker `mp.playing_handicap`
|
|
|
|
|
|
(allerede beregnet, allowance-justert) + `allocate_over_played_
|
|
|
|
|
|
holes` (samme mønster som eksisterende match-play-scoring i
|
|
|
|
|
|
`scoring.py`, som fikk sin `_played_hole_numbers` omdøpt til
|
|
|
|
|
|
`played_hole_numbers` -- "fjern understrek når et andre bruksted
|
|
|
|
|
|
dukker opp"-mønsteret, samme som `SIDE_IS_UNIT` tidligere) — ingen
|
|
|
|
|
|
ny regnelogikk. Respekterer samme reveal-gating (`locked_team_ids`/
|
|
|
|
|
|
`own_team_ids`, ADR-013/026) som den eksisterende matchlisten.
|
|
|
|
|
|
Frontend: ny side `/tournaments/[id]/sessions/[sessionId]/
|
|
|
|
|
|
individual-leaderboard` (`session-individual-leaderboard.tsx`,
|
|
|
|
|
|
egen enklere lokal variant av `round-leaderboard.tsx` sitt
|
|
|
|
|
|
rangerings-/mode-toggle-mønster -- brutto/netto/poeng, delt
|
|
|
|
|
|
plassering "T-N"), lenket fra blind draw-skjermen for
|
|
|
|
|
|
kvalifiserende økter. Ingen migrasjon.
|
|
|
|
|
|
3. **Midlertidige spillere + automatisk etter-runde-invitasjon
|
|
|
|
|
|
(økt-nivå):** de tre tidligere åpne spørsmålene avklart eksplisitt
|
|
|
|
|
|
(AskUserQuestion) -- nivå ØKT (ikke turnering), dobbel-utsending-
|
|
|
|
|
|
sperre JA, locale bevisst alltid `nb`. Ny migrasjon
|
|
|
|
|
|
`034_session_scorecard_invitations.sql`
|
|
|
|
|
|
(`match_participant.invitation_sent_at`, samme "tidsstempel =
|
|
|
|
|
|
skjedd"-mønster som `round.started_at` m.fl.). Ny
|
|
|
|
|
|
`POST /orgs/{id}/sessions/{id}/send-scorecard-invitations` —
|
|
|
|
|
|
sender KUN til spillere uten konto (`player.user_id IS NULL`) OG
|
|
|
|
|
|
med registrert e-post OG uten en tidligere sendt invitasjon for
|
|
|
|
|
|
akkurat denne (økt, deltaker)-kombinasjonen; responsen skiller
|
|
|
|
|
|
`sent`/`skipped_has_account`/`skipped_no_email`/
|
|
|
|
|
|
`skipped_already_sent` for full gjennomsiktighet. Gjenbruker
|
|
|
|
|
|
SAMME magic-link-token-mekanisme som vanlig innlogging (ikke bare
|
|
|
|
|
|
en "logg inn senere"-henvisning) -- ny `send_session_result_email()`
|
|
|
|
|
|
i `app/email.py` (begge nb/en-maler klare, kun `nb` faktisk brukt).
|
|
|
|
|
|
E-postens innhold: matchresultat (`status_text`) + individuelt
|
|
|
|
|
|
slagtotal når tilgjengelig (individuell-ball-formater -- `SUM
|
|
|
|
|
|
gross_strokes` er naturlig `NULL` for delt-ball-formater uten noen
|
|
|
|
|
|
egen format-sjekk, siden `match_participant_id` aldri settes i
|
|
|
|
|
|
`hole_score` der). Frontend: ny "Send scorekort til alle med
|
|
|
|
|
|
e-post"-knapp i blind draw-skjermen (`session-blind-draw.tsx`),
|
|
|
|
|
|
synlig når økten har minst én match.
|
|
|
|
|
|
**Liten, men reell ryddejobb funnet FØR e-post-laget ble lagt på**
|
|
|
|
|
|
(delt med forrige rundes rundevarsel-mønster): dev-log-grenen for den
|
|
|
|
|
|
nye invitasjons-e-posten skrev først KUN en oppsummeringstekst, ikke
|
|
|
|
|
|
selve token-en -- umulig å teste innloggingslenken i et scratch-/dev-
|
|
|
|
|
|
miljø uten SMTP. Rettet til å skrive to linjer: én i SAMME format som
|
|
|
|
|
|
`request_magic_link` sin etablerte `[DEV] Magic link for ...`-linje
|
|
|
|
|
|
(så eksisterende dev-verktøy som harvester token derfra fungerer
|
|
|
|
|
|
uendret), pluss en egen lesbar oppsummeringslinje.
|
|
|
|
|
|
**Scratch-verifisert grundig, 54/54 nye sjekker** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
|
|
|
|
|
container, alle 34 migrasjoner kjørt friskt, full org-turnering-
|
|
|
|
|
|
scaffold bygget fra bunnen via API -- org/bane/18 hull/tee/turnering/
|
|
|
|
|
|
to lag/seks spillere/roster/singles-stroke-økt/to matcher): flytting
|
|
|
|
|
|
happy-path + kaptein-nullstilling + alle tre feilveier (409/400/404)
|
|
|
|
|
|
bekreftet, individuell rangering bekreftet tom→fylt→korrekt brutto/
|
|
|
|
|
|
netto/poeng for et hånd-utregnet 4-spiller-scenario (kryssjekket at
|
|
|
|
|
|
netto-til-par alltid ≤ brutto-til-par for alle fire), begge ikke-
|
|
|
|
|
|
kvalifiserende økt-typer (hole_result, foursome) avvist rent,
|
|
|
|
|
|
invitasjons-utsending bekreftet presist (1 sendt/2 manglet e-post/1
|
|
|
|
|
|
hadde allerede konto via en EKTE innlogget "linket" bruker), andre
|
|
|
|
|
|
kall bekreftet idempotent (0 nye, riktig `skipped_already_sent`), OG
|
|
|
|
|
|
den utstedte lenken bekreftet FAKTISK brukbar (spilleren logget inn
|
|
|
|
|
|
med den, kontoen ble koblet til spiller-profilen, samme ADR-017-
|
|
|
|
|
|
mekanisme uendret). PLUSS regresjon av scoring.py-omdøpingen
|
|
|
|
|
|
(`test_holeless_course_crash.py`, 5/5) og to notifikasjons-/e-post-
|
|
|
|
|
|
testsuiter fra forrige runde (28/28, 19/19) — alle fortsatt grønne.
|
|
|
|
|
|
`test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild
|
|
|
|
|
|
kompilerte rent, ny rute `/tournaments/[id]/sessions/[sessionId]/
|
|
|
|
|
|
individual-leaderboard` listet.
|
|
|
|
|
|
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
|
|
|
|
|
|
isolert scratch-backend, ekte innlogging inkl. tvungen 2FA-oppsett for
|
|
|
|
|
|
en fersk organisasjonseier): flyttet en spiller mellom lag direkte i
|
|
|
|
|
|
UI-et og bekreftet begge lags roster-lister/-antall oppdatert
|
|
|
|
|
|
umiddelbart; åpnet individuell rangering og bekreftet alle tre
|
|
|
|
|
|
visningsmodiene (Brutto/Netto/Poeng) viste korrekte, ulike tall for de
|
|
|
|
|
|
samme to spillerne; trykket "Send scorekort til alle med e-post" og
|
|
|
|
|
|
bekreftet resultatteksten "1 invitasjon sendt, 1 mangler registrert
|
|
|
|
|
|
e-post" -- trykket samme knapp igjen og bekreftet "0 invitasjoner
|
|
|
|
|
|
sendt, 1 allerede sendt tidligere, …" (dobbel-sperren synlig direkte i
|
|
|
|
|
|
UI-et, ikke bare i et API-svar). Ingen konsollfeil i noen av de tre
|
|
|
|
|
|
rundene.
|
|
|
|
|
|
**Rullet ut mot ekte systemer 2026-07-28**, bruker bekreftet
|
|
|
|
|
|
eksplisitt: migrasjon 034 kjørt mot ekte `teecup_db`
|
|
|
|
|
|
(`invitation_sent_at`-kolonnen bekreftet, `test_isolation.sql`
|
|
|
|
|
|
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
|
|
|
|
|
|
teecup_frontend`. Begge containere boot-et rent, `/health`/
|
|
|
|
|
|
`/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
**Samtidig, ren dokumentasjonshygiene:** et par steder i
|
|
|
|
|
|
`FEATURE_BACKLOG.md`/`ARCHITECTURE_DECISIONS.md` hadde blitt hengende
|
|
|
|
|
|
etter `CLAUDE.md` (ADR-039-utrulling/frontend og PWA-offline-
|
|
|
|
|
|
browsertesting fremstod fortsatt som "ikke gjort" til tross for at
|
|
|
|
|
|
begge var fullført i tidligere økter denne uken) — rettet til å
|
|
|
|
|
|
stemme med den faktiske, allerede leverte tilstanden.
|
|
|
|
|
|
|
|
|
|
|
|
- **Fire gjenstående forslag fra en tidligere "hva nå?"-runde, ALLE FIRE
|
|
|
|
|
|
BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28, samme dag:**
|
|
|
|
|
|
brukeren ba eksplisitt om å ferdigstille alle fire samtidig
|
|
|
|
|
|
(PWA-installasjon, flere flighter i frittstående runder, scramble/
|
|
|
|
|
|
greensome-statistikk, push-varsler til telefonens OS) — se
|
|
|
|
|
|
FEATURE_BACKLOG.md for full detalj per punkt, kort oppsummert her.
|
|
|
|
|
|
Tre nye migrasjoner (`035_round_flight_group.sql`,
|
|
|
|
|
|
`036_round_hole_selected_participant.sql`, `037_push_subscriptions.sql`),
|
|
|
|
|
|
alle rent additive.
|
|
|
|
|
|
1. **PWA-installasjonsoppfordring** — `lib/pwa-install.ts` (globalt
|
|
|
|
|
|
fanget `beforeinstallprompt`, mountet via `sw-register.tsx` på alle
|
|
|
|
|
|
sider siden eventet kun fyres én gang per side-liv) + ny
|
|
|
|
|
|
`components/install-prompt.tsx`, vist på dashbordet rett under
|
|
|
|
|
|
hilsenen. Android/Chrome-familien får en ekte "Installer"-knapp,
|
|
|
|
|
|
iOS Safari et instruksjonsbanner (ingen programmatisk vei finnes
|
|
|
|
|
|
der), andre nettlesere uten reell installasjonsvei viser ingenting.
|
|
|
|
|
|
2. **Flere flighter i én frittstående runde** (retning 1, løs
|
|
|
|
|
|
gruppering av separate `round`-rader bundet sammen av en klient-
|
|
|
|
|
|
generert delt UUID, `round.flight_group_id`) — ny
|
|
|
|
|
|
`GET /rounds/{id}/flight-group` + `GET .../flight-group/leaderboard`
|
|
|
|
|
|
(slår sammen hver tilgjengelig søsken-flights EGET leaderboard til
|
|
|
|
|
|
én rangert liste, `score_to_par` allerede normalisert og dermed
|
|
|
|
|
|
sammenlignbart på tvers av baner). Ny `FlightGroupPanel` i
|
|
|
|
|
|
`round-detail.tsx` ("+ Legg til en flight til" gjenbruker HELE
|
|
|
|
|
|
opprett-runde-flyten, forhåndsutfylt via URL-parametre — banen
|
|
|
|
|
|
velges på nytt per flight, bevisst), ny side
|
|
|
|
|
|
`/my-rounds/[id]/flights`.
|
|
|
|
|
|
3. **Scramble/greensome: valgt utslag per spiller** — avgrenset til
|
|
|
|
|
|
frittstående runder (ADR-039 sitt delt-ball-format), IKKE
|
|
|
|
|
|
org-scopede turneringer i denne runden (bevisst scope-kutt, se
|
|
|
|
|
|
FEATURE_BACKLOG.md). Ny `round_hole.selected_participant_id`,
|
|
|
|
|
|
`PATCH .../sides/{id}/holes/{n}` fikk et nytt valgfritt felt (samme
|
|
|
|
|
|
"full overwrite hvert kall"-kontrakt som `played`/`score`).
|
|
|
|
|
|
`SideScoreWizard` fikk en ny valgfri "Hvem sitt utslag ble brukt?"-
|
|
|
|
|
|
seksjon (auto-hopp bevisst slått av her, ellers ville brukeren blitt
|
|
|
|
|
|
revet videre før valget kunne gjøres); `round-stats.tsx` fikk en ny
|
|
|
|
|
|
"Utslag brukt"-oppsummering per side.
|
|
|
|
|
|
4. **Push-varsler til telefonens OS** (Web Push/VAPID) — ny
|
|
|
|
|
|
`push_subscription`-tabell, nytt `app/push.py`
|
|
|
|
|
|
(`send_push_to_user`, `pywebpush`), kalt fra `create_notification()`
|
|
|
|
|
|
for ALLE fire varseltyper. Bevisst INGEN egen per-type opt-in
|
|
|
|
|
|
(ulikt e-post-fallbacken) — å abonnere ER samtykket. VAPID-nøkler
|
|
|
|
|
|
valgfrie (`PUSH_CONFIGURED`), samme grasiøs-degraderings-mønster som
|
|
|
|
|
|
SMTP. `public/sw.js` fikk `push`/`notificationclick`-håndtering, ny
|
|
|
|
|
|
`lib/push-subscribe.ts` + seksjon i `/account`
|
|
|
|
|
|
(`PushNotificationSection`).
|
|
|
|
|
|
**VAPID-nøkkelpar generert lokalt** (Python `cryptography`, EC P-256,
|
|
|
|
|
|
rå base64url-kodet privat/offentlig nøkkel — samme format `pywebpush`
|
|
|
|
|
|
og nettleserens `applicationServerKey` forventer), ALDRI vist i
|
|
|
|
|
|
klartekst i chatten (skrevet direkte til en midlertidig fil med 600-
|
|
|
|
|
|
rettigheter, lest inn i ekte `.env` ved utrulling, slettet fra
|
|
|
|
|
|
scratchpad etterpå) — samme regel som alle andre hemmeligheter i
|
|
|
|
|
|
prosjektet.
|
|
|
|
|
|
**Scratch-verifisert grundig, 27/27 sjekker i ett delt testløp**
|
|
|
|
|
|
(isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
|
|
|
|
|
|
API-container med `pywebpush` installert, alle 37 migrasjoner kjørt
|
|
|
|
|
|
friskt): flight-group happy-path + ugyldig UUID avvist + kryss-bruker-
|
|
|
|
|
|
isolasjon (403/404, ingen lekkasje) + slått-sammen leaderboard rangerer
|
|
|
|
|
|
korrekt på tvers av to flighter; scramble-valg PATCH lykkes + feil side
|
|
|
|
|
|
avvist (400) + eksplisitt null nullstiller; VAPID-nøkkel eksponert +
|
|
|
|
|
|
abonnement lagret + anonymt abonnement avvist (401) + en EKTE
|
|
|
|
|
|
`webpush()`-utsendelse forsøkt mot en syntaktisk ugyldig test-nøkkel
|
|
|
|
|
|
(`WebPushException` fanget og logget, in-app-varselet ble uansett
|
|
|
|
|
|
opprettet normalt — beviser feil-isolasjonen fungerer i praksis, ikke
|
|
|
|
|
|
bare i teorien). `test_isolation.sql` 12/12 uendret.
|
|
|
|
|
|
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
|
|
|
|
|
|
isolert scratch-backend, ekte innlogging): PWA-knappen rendret og var
|
|
|
|
|
|
klikkbar (Chrome fyrte faktisk `beforeinstallprompt` i testøkten), ingen
|
|
|
|
|
|
konsollfeil; hele "+ Legg til en flight til"-flyten klikket gjennom FRA
|
|
|
|
|
|
KNAPPETRYKK TIL FERDIG RUNDE (riktig forhåndsutfylt skjema, korrekt
|
|
|
|
|
|
`flightGroupId` i URL-en, DIREKTE DATABASE-bekreftelse at begge rundene
|
|
|
|
|
|
delte samme gruppe-id etterpå), kombinert leaderboard-siden bekreftet
|
|
|
|
|
|
visuelt med korrekt rangering; scramble-valget satt i veiviseren,
|
|
|
|
|
|
bekreftet DIREKTE I DATABASEN, OG "Utslag brukt"-oppsummeringen
|
|
|
|
|
|
bekreftet visuelt på statistikksiden; push-UI-et rendret korrekt og
|
|
|
|
|
|
håndterte avslått tillatelse med en forklarende tekst uten konsollfeil
|
|
|
|
|
|
(denne automatiserte nettleserøkten hadde `Notification.permission`
|
|
|
|
|
|
forhåndssatt til "denied" av selve miljøet — en ekte innvilget
|
|
|
|
|
|
tillatelse → ekte levert OS-varsel er derfor IKKE bevist, ærlig
|
|
|
|
|
|
begrensning, bruker bør selv teste dette på en ekte enhet før full
|
|
|
|
|
|
tillit). Ekte typesjekket produksjonsbuild (`docker build --target
|
|
|
|
|
|
builder`) kompilerte rent, alle 27 ruter listet inkl. den nye
|
|
|
|
|
|
`/my-rounds/[id]/flights`.
|
|
|
|
|
|
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt (plan vist
|
|
|
|
|
|
FØR kjøring, per CLAUDE.md sin ufravikelige regel): migrasjon 035-037
|
|
|
|
|
|
kjørt mot ekte `teecup_db` (alle tre bekreftet med direkte spørring,
|
|
|
|
|
|
`test_isolation.sql` fortsatt 12/12), VAPID-nøkler lagt inn i ekte
|
|
|
|
|
|
`.env`, `docker compose up -d --build teecup_api teecup_frontend`.
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/dashboard` → 200,
|
|
|
|
|
|
`GET /push/vapid-public-key` over ekte https ga korrekt offentlig
|
|
|
|
|
|
nøkkel, `GET /rounds/{ukjent-id}/flight-group` ga korrekt
|
|
|
|
|
|
`401 NOT_AUTHENTICATED` (ikke en rå 404 — bekrefter ruten faktisk når
|
|
|
|
|
|
FastAPI), `teeoff.no` upåvirket.
|
|
|
|
|
|
- **Designinstruksen erstattet/formalisert + seks brukerpunkter, ALLE
|
|
|
|
|
|
BYGGET OG LIVE (2026-07-29):** `alternativ designinstruks.md` (fra
|
|
|
|
|
|
dagen før) erstattet med en revidert versjon fra bruker (allerede
|
|
|
|
|
|
forankret i det eksisterende token-systemet, ikke lenger i konflikt med
|
|
|
|
|
|
oransje/hardkodet-slate-spørsmålene) — INNHOLDET er nå slått sammen inn
|
|
|
|
|
|
i `DESIGN_SYSTEM.md` som den gjeldende fasiten (8-punkts rutenett,
|
|
|
|
|
|
`shadow-md shadow-black/8`, `font-normal` OK ved `text-base`+god
|
|
|
|
|
|
kontrast, `:active`-tilstander, safe-area, 16px input, `transition-all
|
|
|
|
|
|
duration-200`). `alternativ designinstruks.md` selv beholdt kun som
|
|
|
|
|
|
arkiv/historikk. **Konsistens-sveip:** det gamle svake skygge-mønsteret
|
|
|
|
|
|
(`shadow-sm shadow-black/5`) erstattet mekanisk på tvers av 20 filer
|
|
|
|
|
|
(`sed`, verifisert 0 gjenværende treff) — IKKE en fullstendig
|
|
|
|
|
|
strukturell gjennomgang av alle 27 skjermer, kun dette ene, trygge,
|
|
|
|
|
|
mekaniske mønsteret.
|
|
|
|
|
|
**Hurtighandlinger:** dashbordets tre knapper er nå `grid-cols-3` også
|
|
|
|
|
|
på mobil (var `grid-cols-1 sm:grid-cols-3`, tok unødvendig mye plass) —
|
|
|
|
|
|
mindre ikon/tekst/høyde for å fortsatt være lesbare i smalere kolonner.
|
|
|
|
|
|
**Par/stroke-index manglet på rundeleaderboardet** (`/my-rounds/{id}/
|
|
|
|
|
|
leaderboard`) — ny `HoleReferenceStrip` viser Hull/Par/Hcp ÉN gang
|
|
|
|
|
|
(banedata, identisk for alle deltakere) rett over den rangerte listen.
|
|
|
|
|
|
**Venner: kategorisering nå OBLIGATORISK fra vennskapet inngås**
|
|
|
|
|
|
(presisert av bruker: "det skal ikke finnes ukategoriserte venner") —
|
|
|
|
|
|
`POST /friends` og `POST /friends/{id}/accept` krever begge minst én
|
|
|
|
|
|
kategori (`Field(min_length=1)`), satt AV BEGGE PARTER på hvert sitt
|
|
|
|
|
|
naturlige tidspunkt (avsender ved sending, mottaker ved aksept) — ikke
|
|
|
|
|
|
en frivillig senere handling. `PUT .../categories` nekter også å sette
|
|
|
|
|
|
et tomt sett. Ny delt `CategoryPicker`-komponent (friends.tsx, alle
|
|
|
|
|
|
forhåndsvalgt — "man må heller velge bort", presisert av bruker) brukt
|
|
|
|
|
|
tre steder (send/godta/rediger), nekter å fjerne SISTE avkrysning.
|
|
|
|
|
|
Selv-helbredende sikkerhetsnett for venner fra FØR denne regelen: ny
|
|
|
|
|
|
advarselsbanner øverst i `/my-friends` ("N venner mangler kategori"),
|
|
|
|
|
|
tvinger vedkommendes kategori-panel åpent til det er løst — bekreftet
|
|
|
|
|
|
reelt nødvendig i produksjon (Erol hadde aldri kategorisert Tore
|
|
|
|
|
|
Morell, sin faste matchspill-motstander — vil nå bli fanget opp og
|
|
|
|
|
|
tvunget løst neste gang `/my-friends` besøkes).
|
|
|
|
|
|
**Synlighetsregelen for "Venner"-runder endret fra "minst én treffende
|
|
|
|
|
|
kategori" til "ALLE vennens kategorier må være i det synlige settet"**
|
|
|
|
|
|
(presisert av bruker: "'ikke vise' overstyrer 'vise'") — en venn med
|
|
|
|
|
|
ÉN ikke-valgt kategori ekskluderes nå helt, selv om en annen av
|
|
|
|
|
|
kategoriene deres er valgt. En venn med NULL kategorier vises ALDRI
|
|
|
|
|
|
(avklart eksplisitt med bruker via spørsmål — skal i praksis aldri
|
|
|
|
|
|
forekomme lenger pga. regelen over, kun en igjenværende tilstand fra
|
|
|
|
|
|
FØR den ble håndhevet). `_can_view_round`/`_friends_who_can_see_round`
|
|
|
|
|
|
(rounds.py) omskrevet til denne AND-semantikken (fra tidligere OR).
|
|
|
|
|
|
`new-round.tsx` sin kategori-multiselect for "Venner"-synlighet starter
|
|
|
|
|
|
nå med ALLE forhåndsvalgt (samme "velg heller bort"-prinsipp).
|
|
|
|
|
|
**"Match leaderboard" — undersøkt grundig, IKKE en backend-bug:**
|
|
|
|
|
|
bekreftet direkte mot ekte `teecup_db` (kun lesing) at brukerens egen
|
|
|
|
|
|
Tjøme-matchrunde (`d857289c-...`) hadde begge sider korrekt satt opp
|
|
|
|
|
|
med beregnet `playing_handicap` — `format-result`-endepunktet ville
|
|
|
|
|
|
altså allerede regnet ut riktig "1 UP (A)"-status. Det reelle hullet
|
|
|
|
|
|
var at `FormatResultPanel` (matchstatus/skins-tavle, med hull-for-hull
|
|
|
|
|
|
vinner/AS-visning) KUN lå under "Spillere og runde"-fanen, usynlig fra
|
|
|
|
|
|
"Score"-fanen der scoring naturlig skjer. Fikset ved å vise samme
|
|
|
|
|
|
komponent (ikke duplisert logikk) øverst på BEGGE faner.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert rolle+MinIO+engangs API-
|
|
|
|
|
|
container, samme mønster som hele prosjektet): full kategori-håndheving
|
|
|
|
|
|
(422 uten kategorier ved både send/godta/rediger, 200 med), presis
|
|
|
|
|
|
eksklusjon-overstyrer-inklusjon-test (venn med golf_friends+close_family
|
|
|
|
|
|
ekskludert når kun golf_friends er synlig, inkludert når begge er
|
|
|
|
|
|
synlige — bekreftet mot BÅDE rå SQL og de faktiske API-endepunktene),
|
|
|
|
|
|
match-runde med sider satt opp fra bunnen ga korrekt "1 UP (A)".
|
|
|
|
|
|
Deretter en FULL, ekte nettleser-gjennomgang (Chrome DevTools, mobil
|
|
|
|
|
|
390×844): 3-kolonners hurtighandlinger, matchstatus-banner synlig på
|
|
|
|
|
|
Score-fanen, Hull/Par/Hcp-referanserad på leaderboardet, hele venne-
|
|
|
|
|
|
søk→kategorivelger(alle forhåndsvalgt, avkrysning fungerer)→send-
|
|
|
|
|
|
flyten, og selv-helbredende-banner-flyten (simulerte en gammel
|
|
|
|
|
|
ukategorisert venn direkte i databasen, bekreftet banneret dukket opp
|
|
|
|
|
|
OG forsvant igjen etter kategorisering via UI-et).
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, ingen migrasjon (kun eksisterende felt/
|
|
|
|
|
|
logikk endret), `docker compose up -d --build teecup_api
|
|
|
|
|
|
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
|
|
|
|
|
→ 200, anonymt `POST /friends` ga korrekt `401` (ikke en rå 404 —
|
|
|
|
|
|
bekrefter ruten når FastAPI), `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Match-scorekort redesignet på tvers av appen (frittstående runde +
|
|
|
|
|
|
turnering), BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
|
|
|
|
|
|
(2026-07-29):** brukeren delte et referansebilde av en konkurrentapp sitt
|
|
|
|
|
|
1v1-matchscorekort (horisontalt rutenett, farget etter hvem som vant
|
|
|
|
|
|
hvert hull, løpende "Stilling"-rad AS/X UP/X&Y, navn+HCP-banner) og ba om
|
|
|
|
|
|
det tilsvarende for ALLE match-scorekort i TeeCup, uten direkte plagiat.
|
|
|
|
|
|
**Bevisst IKKE en kopi:** beholdt appens egne, allerede etablerte
|
|
|
|
|
|
fargespråk i stedet for referansens røde/blå -- frittstående runder bruker
|
|
|
|
|
|
primær/oransje (samme "side A/side B"-konvensjon som `ResultChip`/
|
|
|
|
|
|
`FormatResultPanel` fra tidligere runder), turnering-matcher bruker lagets
|
|
|
|
|
|
faktiske `team.color` (samme konvensjon som resten av turnering-UI-et).
|
|
|
|
|
|
Droppet bevisst referansens "Matchplay NET/Stableford NET"-fane (ga ikke
|
|
|
|
|
|
entydig mening for delt-ball-formater) og "Lik/Kommentar til
|
|
|
|
|
|
spillfeeden/Spillere"-ikonraden (en helt ny sosial funksjon, ikke en
|
|
|
|
|
|
scorekort-redesign -- utenfor denne rundens omfang).
|
|
|
|
|
|
**Kjernemekanisme, ny og delt idé (separat implementert i begge filer,
|
|
|
|
|
|
ikke faktisk delt kode siden filene allerede har egne lokale typer per
|
|
|
|
|
|
etablert konvensjon):** `computeRunning()` speiler
|
|
|
|
|
|
`handicap_engine.py` sin `compute_match_state()`/`describe()` presist,
|
|
|
|
|
|
men regnet ETT PREFIKS om gangen client-side (backend cacher i dag kun
|
|
|
|
|
|
SLUTT-tilstanden) -- gir en løpende AS/X UP/dormie/X&Y-status per hull i
|
|
|
|
|
|
stedet for kun et sluttresultat.
|
|
|
|
|
|
**`round-scorecard.tsx`:** den generiske `ScoreBlock`-tabellen erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/
|
|
|
|
|
|
liste) erstattet med `MatchScorecardGrid` -- horisontalt rutenett (Hull/
|
|
|
|
|
|
Par/en rad per spiller-ELLER-side/Stilling), identitetsbanner (navn+HCP,
|
|
|
|
|
|
farget dot, løpende sentral status + "Ferdig"-merke når avgjort). Fargen
|
|
|
|
|
|
på scorecellen følger HVEM SOM VANT hullet (fylt sirkel), ikke over/under
|
|
|
|
|
|
par -- egen semantikk fra `ScoreMark` med vilje, siden dette er en match,
|
|
|
|
|
|
ikke en individuell runde. `ApiParticipant` fikk `playing_handicap`
|
|
|
|
|
|
(allerede eksponert av backend, kun en frontend-typeutvidelse).
|
|
|
|
|
|
**`session-scorecard.tsx`:** `HoleSummaryTable`/`OutcomeBadge`/
|
|
|
|
|
|
`grossPairLabel` erstattet med `TournamentMatchGrid` (samme struktur,
|
|
|
|
|
|
brukt som HOVEDVISNING for pågående matcher, og i `DecidedView`s "Hull
|
|
|
|
|
|
for hull" -- én komponent, ikke to). `Unit`-typen fikk `playingHandicap`.
|
|
|
|
|
|
**Ekte, nødvendig backend-endring:** `match_participant.playing_handicap`
|
|
|
|
|
|
ble beregnet og lagret siden ADR-039, men var ALDRI eksponert i
|
|
|
|
|
|
`MatchParticipantOut` (matches.py) -- lagt til i modellen og i begge
|
|
|
|
|
|
SELECT-spørringene som populerer den (`fetch_matches` og
|
|
|
|
|
|
`add_participant`). **Reelt funn, fikset FØR utrulling:**
|
|
|
|
|
|
`add_participant` sin `row` hentes FØR
|
|
|
|
|
|
`compute_and_store_side_handicaps()` kjører -- en naiv
|
|
|
|
|
|
`MatchParticipantOut(**dict(row))` ville derfor alltid returnert
|
|
|
|
|
|
`playing_handicap: null` for en NYLIG lagt til deltaker, selv når verdien
|
|
|
|
|
|
faktisk ble beregnet et øyeblikk senere i samme kall. Fikset med et
|
|
|
|
|
|
eksplisitt re-oppslag av `playing_handicap` RETT FØR responsen bygges.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
|
|
|
|
|
|
friskt, samme mønster som resten av prosjektet): en full match bygget fra
|
|
|
|
|
|
bunnen for BEGGE surfacene (en frittstående match-runde med to sider, OG
|
|
|
|
|
|
en full turnering-scaffold -- org/bane-import/tee/tournament/to lag/
|
|
|
|
|
|
roster/singel-økt/match/deltakere -- bygget fra API-et for FØRSTE gang i
|
|
|
|
|
|
et testskript denne uken, siden frittstående runder aldri har trengt det
|
|
|
|
|
|
apparatet før). 8-9 hull registrert med et bevisst blandet vinn/tap/delt-
|
|
|
|
|
|
mønster, bekreftet at `format-result`/`scorecard` sine per-hull
|
|
|
|
|
|
resultater matchet forventet handicap-justert utfall.
|
|
|
|
|
|
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP):
|
|
|
|
|
|
et REELT, tidkrevende miljøproblem ble diagnostisert og løst underveis --
|
|
|
|
|
|
React-komponentenes egne `useEffect`-datahentinger kjørte ALDRI (evig
|
|
|
|
|
|
lastespinner på HVER side, ikke bare de nye) når dev-serveren ble besøkt
|
|
|
|
|
|
via `127.0.0.1:3100` i stedet for `localhost:3100`; Next.js 16 sin
|
|
|
|
|
|
`allowedDevOrigins`-beskyttelse blokkerer stille dev-ressurser (HMR-
|
|
|
|
|
|
websocket m.m.) for det som oppfattes som et fremmed opphav, og dette
|
|
|
|
|
|
fikk hele klient-hydreringen til å henge uten en eneste konsollfeil.
|
|
|
|
|
|
Løst ved å konsekvent bruke `localhost` i stedet for `127.0.0.1` --
|
|
|
|
|
|
ren miljø-lærdom for fremtidige scratch-frontend-økter, ikke en bug i
|
|
|
|
|
|
selve appen. Etter fiksen: bekreftet BEGGE match-scorekortene visuelt,
|
|
|
|
|
|
i BÅDE lys og mørk modus, i BÅDE pågående (løpende "2 UP"/farget
|
|
|
|
|
|
Stilling-rad) og avgjort tilstand ("8 UP"/"Ferdig"-merke, cachet
|
|
|
|
|
|
banner), inkl. et helt nytt 2FA-oppsett fullført på fersk konto via en
|
|
|
|
|
|
engangs e-post-2FA-kode lest fra dev-loggen (org-eier/admin krever 2FA,
|
|
|
|
|
|
ADR-021) for å nå frem til turnering-siden. Ingen konsollfeil i noen av
|
|
|
|
|
|
rundene.
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
|
|
|
|
|
→ 200 upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Match-scorekort-redesignet også på selve LIVE scoringssiden
|
|
|
|
|
|
(`/my-rounds/{id}` sin Score-fane), BYGGET, GRUNDIG SCRATCH-/
|
|
|
|
|
|
BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag rett etter forrige
|
|
|
|
|
|
punkt:** brukeren viste tre skjermbilder (referansen på nytt, samt et
|
|
|
|
|
|
mobil- OG et PC-skjermbilde av "hvordan du har løst det nå") og spurte
|
|
|
|
|
|
direkte hvorfor det ikke så riktig ut ennå. **Presis diagnose FØR noe ble
|
|
|
|
|
|
bygget:** PC-skjermbildets URL (`teecup.teeoff.no/my-rounds/{id}`, uten
|
|
|
|
|
|
`/scorecard`) avslørte at forrige runde kun traff `round-scorecard.tsx`
|
|
|
|
|
|
(en egen, separat LESE-visning) og `session-scorecard.tsx` (turnering) --
|
|
|
|
|
|
aldri `round-detail.tsx` sin egen, ELDRE `FormatResultPanel`+
|
|
|
|
|
|
`ScorecardGrid`/`SideScorecardGrid`, som er det brukeren faktisk ser og
|
|
|
|
|
|
bruker under selve scoringen. `FormatResultPanel` viste dessuten en
|
|
|
|
|
|
reell, synlig svakhet: falt tilbake til det generiske "Side B" i stedet
|
|
|
|
|
|
for spillerens faktiske navn når siden ikke hadde et eget satt navn.
|
|
|
|
|
|
**Bygget:** `ApiFormatResult` utvidet med samme `holes`-per-hull-
|
|
|
|
|
|
oppløsning som round-scorecard.tsx allerede har (ingen backend-endring
|
|
|
|
|
|
-- feltet fantes allerede i API-svaret, bare ikke lest her). Selve
|
|
|
|
|
|
fetch-en LØFTET fra `FormatResultPanel` opp til hovedkomponenten (`Round
|
|
|
|
|
|
Detail`) som ny delt `formatResult`-state, siden BÅDE banneret OG de to
|
|
|
|
|
|
scorekort-gridene nå trenger den samme dataen (én henting, ikke to).
|
|
|
|
|
|
`FormatResultPanel` gjort om til en ren presentasjonskomponent (tar
|
|
|
|
|
|
`result` som prop) med et nytt identitetsbanner (navn+HCP, farget prikk,
|
|
|
|
|
|
stor sentrert løpende status, "Ferdig"-merke) -- samme visuelle språk
|
|
|
|
|
|
som `MatchScorecardGrid`/`TournamentMatchGrid` fra forrige runde, egen
|
|
|
|
|
|
lokal kopi per prosjektets "ett sted, én fil"-konvensjon. Den tidligere
|
|
|
|
|
|
hull-for-hull-sirkel-listen under banneret FJERNET (overflødig nå som
|
|
|
|
|
|
selve gridet under viser dette per hull).
|
|
|
|
|
|
`ScorecardGrid` (individuell-ball: match/fourball) og `SideScorecardGrid`
|
|
|
|
|
|
(delt-ball: foursome/greensome/scramble) fikk begge: en farget prikk ved
|
|
|
|
|
|
siden av spiller-/side-navnet (grønn=side A, oransje=side B), en ny
|
|
|
|
|
|
`MatchScorecardCell` som farger scorecellen etter HVEM SOM VANT hullet
|
|
|
|
|
|
(fylt sirkel) i stedet for over/under par -- men KUN når formatet
|
|
|
|
|
|
faktisk er to-sidet (`isTwoSided`-prop, gate på `TWO_SIDED_FORMATS`) --
|
|
|
|
|
|
slagspill og skins beholder uendret over/under-par-fargelegging siden de
|
|
|
|
|
|
ikke har noe "vant hullet"-konsept. Fourball sin ikke-tellende
|
|
|
|
|
|
partner-score tones ned (samme "laveste netto teller"-nedtoning som
|
|
|
|
|
|
round-scorecard.tsx). Ny "Stilling"-rad nederst i BEGGE grid, regnet med
|
|
|
|
|
|
en lokal `matchStillingByHole()` (samme prefiks-vise
|
|
|
|
|
|
`compute_match_state()`-speiling som forrige rundes `computeRunning()`,
|
|
|
|
|
|
egen kopi her siden denne trenger å være justert til `holeOrder`, ikke
|
|
|
|
|
|
bare en flat liste). Alt dette er BEVISST lagt OPPÅ den eksisterende
|
|
|
|
|
|
klikk-for-å-registrere-interaksjonen (ScoringWizard/SideScoreWizard)
|
|
|
|
|
|
uendret -- ingen endring i selve registreringsflyten, kun presentasjonen
|
|
|
|
|
|
av allerede registrerte hull.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
|
|
|
|
|
|
friskt): tre nye runder bygget fra bunnen via API for å dekke alle tre
|
|
|
|
|
|
strukturelle grenene -- en MATCH-runde (Erol vs. en gjest, ulik HCP),
|
|
|
|
|
|
en FOURBALL-runde (2v2, individuell netto, bevisst konstruert med et
|
|
|
|
|
|
hull der "feil" partner sin lavere brutto ikke telte pga. handicap-
|
|
|
|
|
|
justering), og en FOURSOME-runde (delt ball). Ekte typesjekket
|
|
|
|
|
|
produksjonsbuild kompilerte rent.
|
|
|
|
|
|
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
|
|
|
|
|
|
samme `localhost`-ikke-`127.0.0.1`-lærdom fra forrige runde anvendt fra
|
|
|
|
|
|
start denne gangen): alle tre rundene bekreftet visuelt -- identitets-
|
|
|
|
|
|
banneret viste ekte spillernavn+HCP (ikke lenger "Side B"), gridcellene
|
|
|
|
|
|
fargela nøyaktig riktig hull som vunnet (kryssjekket presist mot de
|
|
|
|
|
|
faktiske innsendte tallene og aria-labelene, inkl. et hull der en
|
|
|
|
|
|
tilsynelatende brutto-uavgjort (4-4) faktisk ble avgjort på netto pga.
|
|
|
|
|
|
et stort HCP-gap -- bekreftet korrekt, ikke en bug), fourball sin
|
|
|
|
|
|
nedtonede ikke-tellende partner-celle bekreftet nøyaktig på det
|
|
|
|
|
|
konstruerte hullet, Stilling-raden fulgte riktig gjennom AS/1 UP/2 UP-
|
|
|
|
|
|
mønsteret for alle tre rundene. Bekreftet at et klikk på en scorecelle
|
|
|
|
|
|
FORTSATT åpner riktig veiviser (`ScoringWizard`/`SideScoreWizard`)
|
|
|
|
|
|
uendret, i begge grid-typer. Kjørte i tillegg en ren REGRESJONSSJEKK
|
|
|
|
|
|
mot en vanlig `stroke`-formatert runde -- bekreftet ingen Stilling-rad,
|
|
|
|
|
|
ingen fargede prikker, ingen bytte av cellespråk (uendret over/under-
|
|
|
|
|
|
par-styling som før). Ingen konsollfeil i noen av rundene, i verken lys
|
|
|
|
|
|
eller mørk modus.
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (ba om fiksen
|
|
|
|
|
|
rett etter diagnosen): ingen migrasjon, `docker compose up -d --build
|
|
|
|
|
|
teecup_api teecup_frontend`. Begge containere boot-et rent,
|
|
|
|
|
|
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket. **Alle tre stedene
|
|
|
|
|
|
et match-scorekort vises i appen (frittstående runde sin live Score-
|
|
|
|
|
|
fane, frittstående runde sin egen lesevisning, turnering-match) bruker
|
|
|
|
|
|
nå samme konsistente visuelle språk.**
|
|
|
|
|
|
|
|
|
|
|
|
- **Dashbordets rundeboks: viewer-relativt matchresultat + netto til-par,
|
|
|
|
|
|
pluss mottatte slag vist FØR hullet fylles ut, BYGGET, SCRATCH-/
|
|
|
|
|
|
BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag:** brukeren ba om to
|
|
|
|
|
|
ting samtidig -- at rundeboksen (`round-card.tsx`, brukt av dashbordet,
|
|
|
|
|
|
"Egne runder" og "Spilte baner") viser spillerens eget resultat i
|
|
|
|
|
|
matchen/runden ("1 UP"/"+4 netto"/"-2 brutto" osv.), og at det er
|
|
|
|
|
|
tydelig hvor mange slag en spiller MOTTAR på et hull -- kommunisert på
|
|
|
|
|
|
scorekortet, allerede FØR hullet er fylt ut (ikke bare etterpå som
|
|
|
|
|
|
netto-tallet i dag). Fire load-bærende avklaringer bekreftet eksplisitt
|
|
|
|
|
|
(AskUserQuestion, alle anbefalte valg): vis resultat BÅDE for pågående
|
|
|
|
|
|
og fullførte runder (ikke bare fullførte som før), samme "UP"/"AS"/
|
|
|
|
|
|
"X&Y"-golfkonvensjon som scorekortets Stilling-rad (ikke et eget
|
|
|
|
|
|
dagligdags språk kun for dashbordkortet), vis BÅDE netto og brutto
|
|
|
|
|
|
til-par for slagspill når HCP spores, og vis slag-indikatoren INNI selve
|
|
|
|
|
|
den tomme score-ruten (ikke en egen ny rad).
|
|
|
|
|
|
**Backend (`app/routers/rounds.py`):** `RoundOut`/`_load_round_out`
|
|
|
|
|
|
(delt av BÅDE `GET /rounds` (liste) OG `GET /rounds/{id}`, ingen egen
|
|
|
|
|
|
kode for listen) fikk tre nye felt: `my_net_score_to_par` (kun
|
|
|
|
|
|
`play_format=="stroke"` OG `course_handicap_snapshot` satt, samme
|
|
|
|
|
|
`allocate_strokes_by_index`-algoritme som list_holes/update_hole
|
|
|
|
|
|
allerede bruker -- ingen ny utregningsmåte), og `my_match_status`/
|
|
|
|
|
|
`my_match_lead` (to-sidede formater KUN, ALLTID viewer-relativt --
|
|
|
|
|
|
ny `_viewer_match_status_text()`-formatter tar en fortegns-FLIPPET
|
|
|
|
|
|
`lead` fra `_build_format_result` sitt allerede eksisterende, testede
|
|
|
|
|
|
resultat -- gjenbrukt UENDRET, ikke en ny match-motor -- slik at
|
|
|
|
|
|
positivt alltid betyr "spørrende bruker leder", uansett hvilken side de
|
|
|
|
|
|
faktisk sitter på). Et avgjort resultat vises som "Vunnet 2&1"/"Tapt
|
|
|
|
|
|
2&1" (eksplisitt prefiks, siden "2&1" alene ikke lenger sier hvem sin
|
|
|
|
|
|
side det gjaldt når visningen ikke er side-A/B-basert).
|
|
|
|
|
|
**Frontend (`round-card.tsx`):** ny `Round.matchStatus`/`matchLead`/
|
|
|
|
|
|
`netToPar`. To-sidede formater viser ett "Resultat"-felt med
|
|
|
|
|
|
matchstatusen (farget grønn ved ledelse, oransje ved etterslep, samme
|
|
|
|
|
|
"form+farge"-språk som resten av appen -- ALDRI backend sin bokstavelige
|
|
|
|
|
|
side A/B). Slagspill beholder "Resultat"+"Til par" (brutto) og fikk et
|
|
|
|
|
|
nytt "Netto"-felt ved siden av, kun når tilgjengelig. Resultatraden er
|
|
|
|
|
|
nå IKKE lenger gatet til kun fullførte runder -- vises også mens runden
|
|
|
|
|
|
pågår. Alle tre kallstedene (`dashboard.tsx`, `own-rounds.tsx`,
|
|
|
|
|
|
`course-rounds.tsx`) oppdatert til å lese og videresende de tre nye
|
|
|
|
|
|
API-feltene, samme mønster i alle tre (ingen egen logikk per fil).
|
|
|
|
|
|
**Slag-indikator FØR utfylling (`round-detail.tsx`):** `ScorecardGrid`
|
|
|
|
|
|
(individuell-ball) og `SideScorecardGrid` (delt-ball) viser nå en liten
|
|
|
|
|
|
blå "−N"-tekst inni den tomme score-ruten når spilleren/siden faktisk
|
|
|
|
|
|
mottar minst ett slag på det hullet -- samme `strokes_received`-verdi
|
|
|
|
|
|
som allerede ble hentet (kun aldri vist før noe var registrert). Egen
|
|
|
|
|
|
farge (`text-info`, den etablerte blåtonen) for å skille denne FØR-
|
|
|
|
|
|
visningen tydelig fra den eksisterende grå netto-visningen ETTER
|
|
|
|
|
|
utfylling. **Reelt, tidligere ubrukt hull funnet underveis:**
|
|
|
|
|
|
`SideScorecardGrid` sin egen frontend-type `ApiSideHole` manglet
|
|
|
|
|
|
`strokes_received` helt (backend har alltid eksponert feltet i
|
|
|
|
|
|
`RoundSideHoleOut`, bare aldri lest her) -- lagt til.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-container, alle 37 migrasjoner kjørt
|
|
|
|
|
|
friskt): en slagspill-runde ga korrekt brutto til-par 4 / netto til-par
|
|
|
|
|
|
2 (både i enkelt-GET og i listen), en matchrunde der spilleren vant
|
|
|
|
|
|
begge registrerte hull ga korrekt `"2 UP"`/`lead=2`. Deretter en FULL,
|
|
|
|
|
|
ekte nettleser-gjennomgang (samme `localhost`-lærdom anvendt fra start):
|
|
|
|
|
|
rundeboksen viste "2 UP" i grønt for en pågående matchrunde og "Resultat
|
|
|
|
|
|
17 / Til par +4 / Netto +2" (oransje) for en pågående slagspill-runde,
|
|
|
|
|
|
i BÅDE lys og mørk modus; scorekortet viste "−1"-hint i blått på
|
|
|
|
|
|
nøyaktig de tomme rutene der spilleren/siden faktisk mottar et slag,
|
|
|
|
|
|
bekreftet i BÅDE individuell-ball- (match) og delt-ball-grid (foursome).
|
|
|
|
|
|
Ingen konsollfeil i noen av rundene.
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`.
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Match-identitetsbanneret: territorium-bar i stedet for flat 50/50-boks,
|
|
|
|
|
|
BYGGET, GRUNDIG BROWSERVERIFISERT (inkl. en reell overlapp-bug funnet OG
|
|
|
|
|
|
fikset) OG LIVE (2026-07-29), samme dag:** brukeren lastet opp to bilder
|
|
|
|
|
|
av dagens banner (samme identitetskort som ble bygget dagen før -- navn+
|
|
|
|
|
|
HCP i hver ende, en sentrert statusboks) og en "VELDIG DÅRLIG, kun
|
|
|
|
|
|
illustrerende" skisse av eget forslag: den ledende sidens halvdel bør
|
|
|
|
|
|
markeres OVER på motstanderens halvdel (ikke en statisk 50/50-boks), og
|
|
|
|
|
|
ved AS skal begge sider markeres likt (nøytralt).
|
|
|
|
|
|
**Design:** ny `leadZoneFraction(lead, totalHoles)` -- `0.5 + (lead /
|
|
|
|
|
|
totalHoles) * 0.5`, klippet til `[0.18, 0.82]` for å alltid holde begge
|
|
|
|
|
|
navn lesbare selv ved en ekstrem ledelse. Dominant side får en solid,
|
|
|
|
|
|
mettet farge (`bg-primary`/`bg-brand-orange`, hvit/kontrastfarget tekst
|
|
|
|
|
|
via allerede eksisterende `--primary-foreground`/`--brand-orange-
|
|
|
|
|
|
foreground`-tokens) og strekker seg proporsjonalt forbi midtlinjen;
|
|
|
|
|
|
underlegne side beholder samme fargetone som en svak, lys tint
|
|
|
|
|
|
(`/12`-opacity) med vanlig temafarget tekst. Ved AS (eller ingen hull
|
|
|
|
|
|
spilt ennå): begge soner nøyaktig 50/50 og nøytralt `bg-muted` -- ingen
|
|
|
|
|
|
side ser ut til å lede. Egen lokal kopi i alle tre filer der et match-
|
|
|
|
|
|
identitetsbanner finnes (samme "én fil, én kopi"-konvensjon som
|
|
|
|
|
|
MatchScorecardCell-arbeidet dagen før): `round-detail.tsx` sin
|
|
|
|
|
|
`FormatResultPanel` (primary/brand-orange-tokens), `round-scorecard.tsx`
|
|
|
|
|
|
sin nye delte `LeadBar`+`LeadZone` (samme tokens, portert fra den
|
|
|
|
|
|
tidligere `IdentitySide`+`CenterBadge`-duoen), `session-scorecard.tsx`
|
|
|
|
|
|
sin `IdentityBlock` (FAKTISK lagets `team.color`-hex i stedet for faste
|
|
|
|
|
|
tokens -- dominant sone `backgroundColor: color` + hvit tekst, svak sone
|
|
|
|
|
|
`color-mix(in srgb, ${color} 15%, transparent)`, samme kontrastvalg som
|
|
|
|
|
|
den eksisterende `SegmentedBar` i tournament-leaderboard.tsx).
|
|
|
|
|
|
**Reell overlapp-bug funnet OG fikset UNDER selve browserverifiseringen,
|
|
|
|
|
|
ikke antatt riktig fra kodegjennomgang alene:** første forsøk brukte en
|
|
|
|
|
|
`position:absolute`-sentrert statusboks OPPÅ to `width:X%`-delte soner --
|
|
|
|
|
|
et ekte skjermbilde på en smal (390px) mobil-viewport viste at et langt
|
|
|
|
|
|
navn ("Erol Haagenrud") ble delvis SKJULT bak statusboksen ("l Haagenrud"
|
|
|
|
|
|
vist i stedet, ikke en ellipse-trunkering, et reelt visuelt overlapp).
|
|
|
|
|
|
Første fiksforsøk (CSS Grid med `minmax(0,X fr) auto minmax(0,Y fr)` i
|
|
|
|
|
|
stedet for absolutt posisjonering) løste IKKE problemet alene -- fortsatt
|
|
|
|
|
|
samme overlapp ved re-test. Rot-årsaken var dypere: identitetsboksen
|
|
|
|
|
|
brukte `items-start`/`items-end` på sin YTRE flex-kolonne, som sizer
|
|
|
|
|
|
boksen etter INNHOLDETS egen bredde (shrink-to-fit) i stedet for sonens
|
|
|
|
|
|
faktiske tildelte bredde -- innholdet kunne dermed visuelt strekke seg
|
|
|
|
|
|
utover sin egen sone og inn i midt-kolonnen uansett hvor korrekt selve
|
|
|
|
|
|
grid-/bredde-fordelingen var. Fikset ved at boksen alltid STREKKER SEG
|
|
|
|
|
|
(fjernet items-start/items-end helt), med venstre/høyre-justering i
|
|
|
|
|
|
stedet løst via `justify-start`/`justify-end` på navne-raden og
|
|
|
|
|
|
`text-left`/`text-right` på HCP-teksten -- INNI en boks som nå alltid har
|
|
|
|
|
|
sonens fulle, korrekte bredde, slik at `truncate` faktisk virker presist.
|
|
|
|
|
|
**Verifisert grundig i en isolert scratch-nettleserøkt** (fersk
|
|
|
|
|
|
`teecup_scratch`-database + isolert scratch-MinIO + engangs API-
|
|
|
|
|
|
container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP, 390×
|
|
|
|
|
|
844 mobil-viewport): bygget en ekte match-runde fra bunnen via API (egen-
|
|
|
|
|
|
definert 18-hulls bane, Erol HCP 10 vs. gjest "Tore Morell" HCP 54,
|
|
|
|
|
|
samme HCP-mønster som brukerens skjermbilde) og testet re-render etter
|
|
|
|
|
|
HVER kodeendring, ikke bare én gang til slutt -- fanget nettopp DERFOR
|
|
|
|
|
|
både at CSS Grid-fiksen alene ikke var nok, og at stretch-fiksen
|
|
|
|
|
|
faktisk løste det. Bekreftet: normal ledelse ("1 UP", dominant sone
|
|
|
|
|
|
moderat bredere), en EKSTREM ledelse (avgjort "9&7", dominant sone
|
|
|
|
|
|
nesten fyller hele baren, underlegne navn korrekt trunkert "T…"/"HCP…"
|
|
|
|
|
|
i stedet for å overlappe), AS/ingen-hull-spilt (nøytral 50/50, `bg-
|
|
|
|
|
|
muted`), BÅDE lys og mørk modus (kontrast bekreftet i begge), og samme
|
|
|
|
|
|
fiks bekreftet å fungere identisk i `round-scorecard.tsx` sin
|
|
|
|
|
|
`MatchScorecardGrid`-visning (samme runde, `/my-rounds/{id}/scorecard`).
|
|
|
|
|
|
`session-scorecard.tsx` sin tournament-variant IKKE egen nettleser-
|
|
|
|
|
|
testet denne runden (identisk kodemønster, kun `team.color`-hex i stedet
|
|
|
|
|
|
for faste tokens -- lavere risiko, men flagget ærlig som ikke eget
|
|
|
|
|
|
bevist). Ekte typesjekket produksjonsbuild kjørt (to runder -- én etter
|
|
|
|
|
|
første, mislykkede CSS Grid-only-forsøk, én etter den faktiske stretch-
|
|
|
|
|
|
fiksen), alle 24 ruter listet begge ganger.
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon (ren frontend), `docker compose up -d --build
|
|
|
|
|
|
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
|
|
|
|
|
|
ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
|
|
|
|
|
|
`/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Territorium-bar-språket fullført på de to siste "Matchstatus"-boksene,
|
|
|
|
|
|
BYGGET, BROWSERVERIFISERT OG LIVE (2026-07-29), samme dag -- brukeren ba
|
|
|
|
|
|
eksplisitt om "visuell konsistens ferdig helt ut" etter at jeg flagget
|
|
|
|
|
|
disse to som gjenstående:** `round-leaderboard.tsx` sin
|
|
|
|
|
|
`MatchStatusSection` (den innloggede rundens leaderboard-side) og
|
|
|
|
|
|
`watch-round.tsx` sin `MatchStatus` (den offentlige "Følger live"-siden
|
|
|
|
|
|
for en delt/synlig runde, ADR-036 fase 2) hadde begge fortsatt den gamle
|
|
|
|
|
|
flate boksen (kun stor tekst, ingen fargesone). Disse to har INGEN
|
|
|
|
|
|
navn å vise (deltakernavnene vises allerede andre steder på samme side)
|
|
|
|
|
|
-- lagt til KUN de to fargesonene + den sentrerte statuspillen (samme
|
|
|
|
|
|
`leadZoneFraction`+CSS-Grid-mønster som identitetsbanneret dagen før,
|
|
|
|
|
|
egen lokal kopi i hver fil), ikke selve `LeadZone`-identitetsboksen.
|
|
|
|
|
|
Begge filenes lokale `ApiFormatResult`-type manglet `match_lead` helt
|
|
|
|
|
|
(aldri lest her før, selv om backend alltid har eksponert det) -- lagt
|
|
|
|
|
|
til i begge.
|
|
|
|
|
|
**Verifisert i en isolert scratch-nettleserøkt** (fersk `teecup_scratch`-
|
|
|
|
|
|
database + isolert scratch-MinIO + engangs API-container, ekte `next
|
|
|
|
|
|
dev`, Chrome DevTools MCP, 390×844 mobil-viewport): bygget en ekte
|
|
|
|
|
|
OFFENTLIG (`visibility_mode:"public"`) match-runde fra bunnen via API,
|
|
|
|
|
|
bekreftet BEGGE sider (innlogget leaderboard OG anonym `/watch/{id}`)
|
|
|
|
|
|
viser identisk, korrekt fargesatt "1 UP (Side B)"-tilstand med riktig
|
|
|
|
|
|
dominant/lys sone -- OG en egen AS/0-hull-spilt-test (nøytral 50/50,
|
|
|
|
|
|
ingen farge). Begge bekreftet i BÅDE lys og mørk modus. Ingen
|
|
|
|
|
|
konsollfeil (kun en godartet, urelatert PWA-install-loggmelding). Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild kjørt og bekreftet.
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, ingen migrasjon (ren frontend), `docker
|
|
|
|
|
|
compose up -d --build teecup_frontend` (gjenskapte også `teecup_api`
|
|
|
|
|
|
som vanlig bivirkning, ingen backend-kode rørt). Begge containere
|
|
|
|
|
|
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
**Territorium-bar-språket er dermed konsistent på ALLE fem stedene en
|
|
|
|
|
|
matchstatus vises i appen** (tre fulle identitetsbannere + disse to
|
|
|
|
|
|
navnløse variantene) -- listevisningene (blind draw-reveal, offentlig
|
|
|
|
|
|
turnering-live) beholder bevisst sin egen, ulike kort-per-match-stil
|
|
|
|
|
|
(farget topplinje + statuschip), siden de viser MANGE matcher samtidig
|
|
|
|
|
|
og ikke er en fokusert enkelt-match-visning.
|
|
|
|
|
|
|
|
|
|
|
|
- **Stableford som ekte spilleform + "plukket opp"-tilstand for frittstående
|
|
|
|
|
|
runder, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT (inkl. TO reelle bugs
|
|
|
|
|
|
funnet OG fikset UNDER browserverifiseringen) OG LIVE (2026-07-29), samme
|
|
|
|
|
|
dag:** brukeren ba om å ta fatt på det siste, klart avgrensede hullet fra
|
|
|
|
|
|
ADR-038-gjennomgangen -- Stableford-POENGENE var allerede beregnet og vist
|
|
|
|
|
|
flere steder (round-detail.tsx/round-scorecard.tsx/round-leaderboard.tsx
|
|
|
|
|
|
sin `stablefordPoints()`/`total_points`, bygget 2026-07-25/26), men to
|
|
|
|
|
|
ting manglet reelt: (1) `'stableford'` fantes ikke som et faktisk
|
|
|
|
|
|
selvdeklarert `play_format` (kun "stroke"/"match" fantes som individuelle
|
|
|
|
|
|
format-etiketter), (2) ingen vei til å registrere at en spiller plukket
|
|
|
|
|
|
opp ballen fordi hullet uansett var klart 0 poeng.
|
|
|
|
|
|
**Design, ny migrasjon `038_stableford_and_pickup.sql`:** `round_hole.
|
|
|
|
|
|
picked_up boolean DEFAULT false`, `'stableford'` lagt til
|
|
|
|
|
|
`round_play_format_check`. "Plukket opp" lagres IKKE som en NULL-score --
|
|
|
|
|
|
serveren skriver eksplisitt Net Double Bogey (par + 2 + mottatte slag,
|
|
|
|
|
|
`handicap_engine.py` sin allerede eksisterende og testede `max_hole_
|
|
|
|
|
|
score_for_handicap()`, Rule 3.1b) som selve `score`-verdien når
|
|
|
|
|
|
`picked_up=true` sendes til `PATCH .../holes/{n}` -- dette gir automatisk
|
|
|
|
|
|
presis 0 Stableford-poeng (samme formel som ellers, ingen spesialkoding)
|
|
|
|
|
|
OG et korrekt AGS-bidrag for faktisk-HCP (ADR-038) helt uendret -- INGEN
|
|
|
|
|
|
ny motorlogikk trengs, kun ett nytt felt for VISNING ("PU"-merke i stedet
|
|
|
|
|
|
for det underliggende tallet, samme "form+farge, aldri farge alene"-språk
|
|
|
|
|
|
som resten av golfscore-visningen). `HoleUpdate` beregner strokes_received
|
|
|
|
|
|
for AKKURAT det aktuelle hullet FØR selve UPDATE-en (omstrukturert fra
|
|
|
|
|
|
tidligere "beregn etterpå"-rekkefølge) for å kunne skrive riktig NDB-verdi
|
|
|
|
|
|
i samme kall. Avvist tydelig (400 `VALIDATION_FAILED`) hvis deltakeren
|
|
|
|
|
|
ikke har noen beregnet course handicap ennå.
|
|
|
|
|
|
**To reelle bugs funnet OG fikset UNDER selve browserverifiseringen, ikke
|
|
|
|
|
|
antatt riktig fra kodegjennomgang alene** -- begge samme underliggende
|
|
|
|
|
|
mønster: eksisterende kode som spesialsjekket `play_format === "stroke"`
|
|
|
|
|
|
(for å bety "individuelt, ikke to-sidet format") måtte utvides til
|
|
|
|
|
|
eksplisitt å ekskludere `"stableford"` også, ellers falt det nye formatet
|
|
|
|
|
|
feilaktig gjennom til den TO-SIDEDE grenen:
|
|
|
|
|
|
1. `new-round.tsx` sin `needsSidesSetup = playFormat !== "stroke" &&
|
|
|
|
|
|
playFormat !== "skins"` -- et ekte skjermbilde viste "Du setter opp
|
|
|
|
|
|
sidene..."-teksten under Stableford-valget, til tross for at formatet
|
|
|
|
|
|
aldri bruker sider. Rettet ved å legge til `&& playFormat !==
|
|
|
|
|
|
"stableford"`.
|
|
|
|
|
|
2. **Alvorligere:** `round-detail.tsx` sin `FormatResultPanel` (`if
|
|
|
|
|
|
(round.play_format === "stroke" || !result) return null`) -- et ekte
|
|
|
|
|
|
skjermbilde av en fersk Stableford-runde viste en fullstendig
|
|
|
|
|
|
meningsløs "Side A"/"Side B"-territorium-bar (samme komponent bygget
|
|
|
|
|
|
tidligere samme dag for ekte to-sidede formater) med "Ingen hull
|
|
|
|
|
|
spilt ennå", siden backend sin `_build_format_result()` returnerer en
|
|
|
|
|
|
harmløs tom `ready:true`-respons for ethvert format utenfor
|
|
|
|
|
|
`_TWO_SIDED_FORMATS`/`"skins"` (inkl. det nye "stableford"), som
|
|
|
|
|
|
frontend-sjekken ikke fanget opp. Rettet samme sted (lagt til
|
|
|
|
|
|
`round.play_format === "stableford"` i den samme betingelsen), pluss
|
|
|
|
|
|
den tilhørende `useEffect` som utløser selve HTTP-kallet (unødvendig
|
|
|
|
|
|
nettverkskall for Stableford, samme fiks som for "stroke").
|
|
|
|
|
|
**Systematisk oppfølging etter de to funnene, IKKE bare disse to
|
|
|
|
|
|
stedene:** grep'et gjennom HELE frontend-kodebasen etter samme
|
|
|
|
|
|
`"stroke"`-spesialsjekk-mønster og fant tre til ekte hull i
|
|
|
|
|
|
`watch-round.tsx` (den offentlige "Følger live"-siden) -- leaderboard-
|
|
|
|
|
|
henting, `StrokeLeaderboard`-visning, og "ingen live-visning
|
|
|
|
|
|
tilgjengelig"-fallback-meldingen ville alle feilaktig behandlet en
|
|
|
|
|
|
offentlig Stableford-runde som enten to-sidet eller "ukjent format".
|
|
|
|
|
|
Alle tre rettet samme runde, FØR de kunne bli oppdaget i produksjon.
|
|
|
|
|
|
Backend-siden av samme klasse spesialsjekk (`app/routers/rounds.py`)
|
|
|
|
|
|
var allerede korrekt fra første forsøk (`_format_setup_status`,
|
|
|
|
|
|
`my_net_score_to_par`-gaten, `RoundCreate`/`RoundUpdate` sine
|
|
|
|
|
|
`Literal`-typer) -- kun frontend hadde det gjentatte hullet.
|
|
|
|
|
|
`EditRoundPanel` sin spilleform-brytar (rediger-runde-panelet) utvidet
|
|
|
|
|
|
fra to til tre valg (Slagspill/Stableford/Matchspill), samme mønster
|
|
|
|
|
|
som `new-round.tsx`.
|
|
|
|
|
|
**Scratch-verifisert grundig i to lag** (isolert `teecup_app_scratch`-
|
|
|
|
|
|
rolle + isolert scratch-MinIO + engangs API-container, alle 38
|
|
|
|
|
|
migrasjoner kjørt friskt, samme mønster som hele prosjektet): et
|
|
|
|
|
|
hånd-utregnet scenario (course handicap 18, slope 113/rating lik par,
|
|
|
|
|
|
1 slag mottatt per hull) bekreftet "plukket opp" på et par-4-hull med
|
|
|
|
|
|
1 mottatt slag ga eksakt score 7 (4+2+1, identisk med håndregning),
|
|
|
|
|
|
leaderboardets `total_points` stemte eksakt (0 for plukket-opp-hullet +
|
|
|
|
|
|
3 for et normalt netto-birdie-hull = 3), reversering (fjern plukket-
|
|
|
|
|
|
opp, sett ekte score, plukk opp igjen) fungerte, avvist tydelig for en
|
|
|
|
|
|
deltaker uten HCP (400), `_format_setup_status` bekreftet IKKE lenger
|
|
|
|
|
|
krasjer (KeyError) for stableford (`setup_complete:true` uten
|
|
|
|
|
|
sider), `RoundUpdate.play_format` PATCH til "stableford" i etterkant
|
|
|
|
|
|
bekreftet, runde fullført uten krasj.
|
|
|
|
|
|
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
|
|
|
|
|
|
samme `localhost`-ikke-`127.0.0.1`-lærdom fra tidligere runder): hele
|
|
|
|
|
|
opprett-runde-flyten klikket gjennom i UI-et (Stableford-valg, riktig
|
|
|
|
|
|
beskrivelsestekst, INGEN sider-notis etter fiksen), "Plukket opp (0
|
|
|
|
|
|
poeng)"-knappen i selve `ScoringWizard` bekreftet å auto-hoppe videre
|
|
|
|
|
|
akkurat som et vanlig slagtall, og "PU"-merket bekreftet konsekvent på
|
|
|
|
|
|
ALLE FIRE stedene det vises: scorekort-gridet på selve Score-fanen,
|
|
|
|
|
|
det post-runde `round-scorecard.tsx`-scorekortet (med korrekt "Poeng
|
|
|
|
|
|
0"/netto 6), `round-leaderboard.tsx` (både i hull-stripen og i
|
|
|
|
|
|
Poeng-modus, total "3" korrekt), og "Så langt i runden"-tabellen. En
|
|
|
|
|
|
separat REGRESJONSSJEKK av et eksisterende `match`-format (to sider,
|
|
|
|
|
|
territorium-bar, "1 UP") bekreftet UENDRET oppførsel -- de to bug-
|
|
|
|
|
|
fiksene rørte kun stableford-grenen. Ingen konsollfeil i noen av
|
|
|
|
|
|
rundene.
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist
|
|
|
|
|
|
FØR migrasjonen, per CLAUDE.md sin ufravikelige regel): migrasjon 038
|
|
|
|
|
|
kjørt mot ekte `teecup_db` (kolonne + utvidet CHECK-constraint
|
|
|
|
|
|
bekreftet, `test_isolation.sql` fortsatt 12/12), deretter `docker
|
|
|
|
|
|
compose up -d --build teecup_api teecup_frontend`. Begge containere
|
|
|
|
|
|
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Scramble/greensome: "utslag brukt"-statistikk NÅ OGSÅ for org-scopede
|
|
|
|
|
|
turneringer, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
|
|
|
|
|
|
(2026-07-29), samme dag:** direkte oppfølging av brukerens eget forslag
|
|
|
|
|
|
fra "hva nå?"-runden rett etter Stableford-arbeidet -- funksjonen ble
|
|
|
|
|
|
bevisst holdt utenfor migrasjon 036 (frittstående runder, 2026-07-25),
|
|
|
|
|
|
se dens egen kommentar; dette er den avgrensede oppfølgeren.
|
|
|
|
|
|
**Design, ny migrasjon `039_hole_score_selected_participant.sql`:**
|
|
|
|
|
|
`hole_score.selected_participant_id uuid` (nullable), composite FK
|
|
|
|
|
|
`(organization_id, selected_participant_id) REFERENCES match_
|
|
|
|
|
|
participant(organization_id, id) ON DELETE SET NULL` -- samme mønster
|
|
|
|
|
|
som `hole_score.match_participant_id` sin egen FK, men SET NULL (ikke
|
|
|
|
|
|
CASCADE): fjernes en deltaker fra matchen (mulig FØR lås) skal ikke
|
|
|
|
|
|
slette allerede registrerte hull-scorer, kun nullstille selve valget.
|
|
|
|
|
|
Kun meningsfullt på en DELT-BALL-hull-rad (`match_participant_id IS
|
|
|
|
|
|
NULL`) -- håndhevet i app-laget (`submit_hole_score`), ikke en CHECK,
|
|
|
|
|
|
samme mønster som migrasjon 036.
|
|
|
|
|
|
**Backend (`app/routers/scoring.py`):** `HoleScoreCreate`/`HoleScoreOut`
|
|
|
|
|
|
fikk `selected_participant_id`. Validering: avvist (400
|
|
|
|
|
|
`WRONG_PARTICIPANT_MODE`) for individuell ball (`match_participant_id`
|
|
|
|
|
|
satt) -- gir ingen mening der. For delt ball: validert å tilhøre
|
|
|
|
|
|
matchen OG samme `team_side` som selve scoren (400 `VALIDATION_FAILED`
|
|
|
|
|
|
ellers), speiler `round.py` sin identiske sjekk for frittstående runder.
|
|
|
|
|
|
Begge INSERT/UPSERT-setningene i `submit_hole_score` oppdatert (delt-
|
|
|
|
|
|
ball-grenen skriver/oppdaterer feltet, individuell-ball-grenen inkluderer
|
|
|
|
|
|
det aldri i det hele tatt -- alltid NULL der). `get_scorecard` sin
|
|
|
|
|
|
`stroke_entries`-SELECT utvidet -- feltet flyter automatisk gjennom
|
|
|
|
|
|
siden den allerede delte `HoleScoreOut`-modellen gjenbrukes.
|
|
|
|
|
|
**Frontend (`session-scorecard.tsx`):** ny "Hvem sitt utslag ble
|
|
|
|
|
|
brukt?"-knapperad (samme mønster som round-detail.tsx sin
|
|
|
|
|
|
`SideScoreWizard` fikk 2025-07-25) under `StrokePicker` for delt-ball-
|
|
|
|
|
|
enheter -- vises KUN når et slagtall allerede er registrert for hullet
|
|
|
|
|
|
(ulikt frittstående runders `round_hole`, som alltid forhåndsopprettes
|
|
|
|
|
|
med nullbar score, krever `hole_score`-raden faktisk å EKSISTERE først,
|
|
|
|
|
|
siden `gross_strokes` er `NOT NULL` i skjemaet -- en reell strukturell
|
|
|
|
|
|
forskjell fra migrasjon 036s mønster, løst ved å gjenbruke samme
|
|
|
|
|
|
`submitStroke`-funksjon med et nytt valgfritt fjerde argument som
|
|
|
|
|
|
enten beholder gjeldende valg (utelatt) eller setter et nytt (inkl.
|
|
|
|
|
|
eksplisitt `null` for å fjerne). Ny `SelectedDriverSummary`-komponent
|
|
|
|
|
|
(samme presentasjon som round-stats.tsx sin tilsvarende, tilpasset
|
|
|
|
|
|
denne sidens `teams`/`match.participants`/`scorecard.stroke_entries`-
|
|
|
|
|
|
datform) lagt inn i den eksisterende "Vis full oversikt"-seksjonen.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-container, alle 39 migrasjoner
|
|
|
|
|
|
kjørt friskt): en FULL org-turnering-scaffold bygget fra bunnen via API
|
|
|
|
|
|
for FØRSTE gang i en test for akkurat denne funksjonen (org/egendefinert
|
|
|
|
|
|
bane/18 hull/tee/turnering/to lag/fire spillere/roster/foursome-økt+
|
|
|
|
|
|
match/fire deltakere/lås) -- 51/51 sjekker: skriving uten valg, skriving
|
|
|
|
|
|
med valg (gross_strokes bevart), feil-side-valg avvist (400
|
|
|
|
|
|
VALIDATION_FAILED), individuell-ball+valg avvist (400
|
|
|
|
|
|
WRONG_PARTICIPANT_MODE), persistens bekreftet via `GET .../scorecard`,
|
|
|
|
|
|
korrekt opptelling (2 vs. 1 utslag), eksplisitt `null` fjerner valget.
|
|
|
|
|
|
`test_isolation.sql` 12/12 uendret (additiv migrasjon). Ekte typesjekket
|
|
|
|
|
|
produksjonsbuild kjørt og bekreftet.
|
|
|
|
|
|
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP,
|
|
|
|
|
|
isolert scratch-backend, inkl. et ekte 2FA-oppsett for en fersk
|
|
|
|
|
|
organisasjonseier siden ADR-021 krever det): naviger til foursome-
|
|
|
|
|
|
matchens scorekort, bekreftet at allerede-registrerte valg (satt via
|
|
|
|
|
|
API) vises korrekt som trykte knapper for RIKTIG spiller på RIKTIG
|
|
|
|
|
|
hull -- OG et ekte klikk i UI-et som byttet valgt spiller for et hull,
|
|
|
|
|
|
bekreftet persistert direkte i databasen etterpå (ikke bare at UI-et
|
|
|
|
|
|
så riktig ut). "Vis full oversikt" sin nye "Utslag brukt"-seksjon
|
|
|
|
|
|
bekreftet med nøyaktig riktig opptelling for begge lag (2/1 og 1/0,
|
|
|
|
|
|
inkl. "Registrert på N av M spilte hull"-teksten). Ingen konsollfeil.
|
|
|
|
|
|
**Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt (plan vist
|
|
|
|
|
|
FØR migrasjonen): migrasjon 039 kjørt mot ekte `teecup_db` (kolonne +
|
|
|
|
|
|
FK-constraint bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
|
|
|
|
|
|
`docker compose up -d --build teecup_api teecup_frontend`. Begge
|
|
|
|
|
|
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket. **"Utslag brukt"-statistikken finnes dermed nå for BEGGE
|
|
|
|
|
|
domener** (frittstående runder siden 2026-07-25, org-scopede
|
|
|
|
|
|
turneringer siden i dag) -- se ARCHITECTURE_DECISIONS.md sitt åpne
|
|
|
|
|
|
spørsmål 4 (Scramble-grensesnitt), som dermed er helt avsluttet.
|
|
|
|
|
|
|
|
|
|
|
|
- **ADR-037 (individuelle/flerrunde-turneringer): migrasjon + motor + API
|
|
|
|
|
|
BYGGET OG SCRATCH-VERIFISERT, IKKE ENNÅ RULLET UT (2026-07-30):** bruker
|
|
|
|
|
|
ba om å gå videre med anbefalingen fra en dypdykk-gjennomgang av
|
|
|
|
|
|
.md-filene -- det eneste gjenværende punktet med en ferdig, load-bærende
|
|
|
|
|
|
strukturbeslutning (ADR-037, 2026-07-26) uten kode. Bygget i tre lag,
|
|
|
|
|
|
samme "test i isolasjon FØR resten"-rekkefølge som ADR-005/033/038/039.
|
|
|
|
|
|
**Migrasjon `040_individual_tournaments.sql`:** `tournament.format_type`
|
|
|
|
|
|
(`team`/`individual`, default `team` -- alle eksisterende rader uendret)
|
|
|
|
|
|
+ `tournament.scoring_method` (`stroke_gross`/`stroke_net`/`stableford`,
|
|
|
|
|
|
nullable). Fem nye, RLS-beskyttede tabeller: `tournament_round` (samme
|
|
|
|
|
|
rolle som `session`, peker til org-ens EGEN bane -- ingen snapshot,
|
|
|
|
|
|
ulikt ADR-033), `tournament_participant` (samme rolle som `team_roster`,
|
|
|
|
|
|
fryser `handicap_index_snapshot`), `tournament_round_participant`
|
|
|
|
|
|
(tee + cachet course/playing-handicap PER runde), `tournament_round_hole`
|
|
|
|
|
|
(rå brutto slag, kilde-sannhet), `tournament_round_score` (ferdig
|
|
|
|
|
|
utregnet brutto/netto/Stableford-total PER deltaker PER runde, cachet --
|
|
|
|
|
|
samme mønster som `match.status_text`/`points_side_a/b`; sammenlagt over
|
|
|
|
|
|
flere runder summeres VED LESING i leaderboardet, ingen egen tredje
|
|
|
|
|
|
cache-tabell, Beslutning C).
|
|
|
|
|
|
**Scratch-verifisert alene FØR API-et ble bygget:** alle 40 migrasjoner
|
|
|
|
|
|
kjørte rent i rekkefølge, `test_isolation.sql` fortsatt 12/12, 10 egne
|
|
|
|
|
|
funksjonelle sjekker (kryss-org-isolasjon på BÅDE lesing og skriving,
|
|
|
|
|
|
`format_type`-CHECK+default, unik-constraints, `gross_strokes`-CHECK,
|
|
|
|
|
|
kaskade-sletting).
|
|
|
|
|
|
**Motor** (`handicap_engine.py`, ny seksjon rett etter
|
|
|
|
|
|
`allocate_over_played_holes`): `stroke_play_gross_total`/
|
|
|
|
|
|
`stroke_play_net_total`/`stableford_points_for_hole`/`stableford_total`
|
|
|
|
|
|
-- rene funksjoner, ingen ny slagfordeling (bruker samme
|
|
|
|
|
|
`allocate_over_played_holes`-output som resten av motoren). 8 nye
|
|
|
|
|
|
tester, alle 63 (55 eksisterende + 8 nye) bestått i
|
|
|
|
|
|
`test_handicap_engine.py`.
|
|
|
|
|
|
**API** (nytt `app/routers/individual_tournaments.py`, registrert i
|
|
|
|
|
|
`main.py`): CRUD for runder/turnering-deltakere/rundedeltakere (med
|
|
|
|
|
|
handicap-beregning ved tilføyelse -- v1 har INGEN allowance-prosent for
|
|
|
|
|
|
individuelle turneringer, `playing_handicap` er alltid identisk med
|
|
|
|
|
|
avrundet `course_handicap`, ulikt lagturneringenes komplekse relative
|
|
|
|
|
|
`AllowanceStrategy`-familie, som er bygget for et to-siders oppgjør og
|
|
|
|
|
|
ikke gir mening for et flatt felt), hull-for-hull-scoring
|
|
|
|
|
|
(`PATCH .../holes/{n}`, cacher totalen på nytt ved hver innsending),
|
|
|
|
|
|
leaderboard som summerer på tvers av runder ved lesing.
|
|
|
|
|
|
`tournaments.py` sin `TournamentCreate`/`TournamentUpdate`/`Tournament`
|
|
|
|
|
|
utvidet med `format_type`/`scoring_method` (samme `exclude_unset`-PATCH-
|
|
|
|
|
|
mønster som resten av filen).
|
|
|
|
|
|
**To reelle funn, begge fikset FØR utrulling:**
|
|
|
|
|
|
1. Leaderboard-endepunktet kunne IKKE hete
|
|
|
|
|
|
`/orgs/{id}/tournaments/{id}/leaderboard` -- den stien er allerede
|
|
|
|
|
|
`tournaments.py` sitt LAG-leaderboard, og siden `tournaments.router`
|
|
|
|
|
|
registreres FØR `individual_tournaments.router` i `main.py`, ville
|
|
|
|
|
|
det stille skygget for det nye endepunktet (funnet presist ved en
|
|
|
|
|
|
ekte API-test som krasjet på feil responsform). Løst med et eget
|
|
|
|
|
|
navn, `/individual-leaderboard` -- samme kollisjonsklasse som
|
|
|
|
|
|
`/rounds` vs. `/my-rounds` tidligere, denne gangen unngått fra start.
|
|
|
|
|
|
2. `list_rounds`/`list_tournament_participants` manglet en eksplisitt
|
|
|
|
|
|
"finnes turneringen"-sjekk (samme mønster `list_sessions` allerede
|
|
|
|
|
|
har) -- ga stille en tom liste under RLS for en fremmed
|
|
|
|
|
|
turnering-id i stedet for 404 (ingen sikkerhetslekkasje, RLS
|
|
|
|
|
|
blokkerte fortsatt all faktisk data, men inkonsistent med resten av
|
|
|
|
|
|
API-et). Rettet til å matche `list_sessions` presist.
|
|
|
|
|
|
**Scratch-API-verifisert grundig, 52/52 sjekker** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
|
|
|
|
|
|
API-container, ekte HTTP via `requests`, ekte magic-link-innlogging via
|
|
|
|
|
|
dev-log): full happy path fra org til leaderboard, hånd-utregnet netto-
|
|
|
|
|
|
kryssjekk for to spillere (course handicap 11/20, stemte eksakt),
|
|
|
|
|
|
`front_9`-runde avviser hull utenfor omfang, kryss-org-isolasjon
|
|
|
|
|
|
(bekreftet BÅDE lesing og en FK-basert skrivesperre), slette-vern (runde
|
|
|
|
|
|
MED deltakere avvist 409, turnering-deltaker fortsatt referert av en
|
|
|
|
|
|
rundedeltaker avvist 400 RESTRICT-FK, løst opp igjen etter fjerning).
|
|
|
|
|
|
`test_isolation.sql` 12/12 uendret (additiv migrasjon).
|
|
|
|
|
|
**IKKE bygget i denne runden, bevisst neste steg:** frontend (ingen
|
|
|
|
|
|
skjerm ennå -- samme lagdelings-rekkefølge som ADR-033/038/039).
|
|
|
|
|
|
Autorisasjon er bredt org-medlemskap for ALT i denne runden, inkl. selve
|
|
|
|
|
|
scoreregistreringen -- en senere innstramming (analogt ADR-023s
|
|
|
|
|
|
kaptein-only) er en naturlig, separat oppfølger. De fem konkrete
|
|
|
|
|
|
formatene (Københavner m.fl.) og Order of Merit fortsatt ikke designet.
|
|
|
|
|
|
**Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt
|
|
|
|
|
|
(plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige regel):
|
|
|
|
|
|
migrasjon 040 kjørt mot ekte `teecup_db` (alle fem nye tabeller + de to
|
|
|
|
|
|
nye `tournament`-kolonnene bekreftet, eksisterende turnering fikk
|
|
|
|
|
|
korrekt default `format_type='team'`, `test_isolation.sql` fortsatt
|
|
|
|
|
|
12/12 mot ekte database), deretter `docker compose up -d --build
|
|
|
|
|
|
teecup_api` (kun backend, ingen frontend-kode denne runden). Containeren
|
|
|
|
|
|
boot-et rent (`Application startup complete`), `/health`/`/dashboard`
|
|
|
|
|
|
→ 200, `teeoff.no` upåvirket. Verifisert presist at den nye ruten
|
|
|
|
|
|
faktisk når FastAPI gjennom hele produksjonsstacken (Caddy → Next.js
|
|
|
|
|
|
rewrite → `teecup_api`): et anonymt kall mot
|
|
|
|
|
|
`/orgs/.../tournaments/.../rounds` ga korrekt `401 NOT_AUTHENTICATED`,
|
|
|
|
|
|
ikke en rå 404.
|
|
|
|
|
|
|
|
|
|
|
|
- **ADR-037 frontend HÅNDKODET, GRUNDIG BROWSERVERIFISERT (inkl. et reelt
|
|
|
|
|
|
backend-hull funnet OG fikset) OG LIVE (2026-07-30), samme dag:** brukeren
|
|
|
|
|
|
ba eksplisitt om å kode det selv (ikke V0) og bruke Chrome DevTools til å
|
|
|
|
|
|
faktisk se resultatet, og pekte på at paletten har rom for mer enn
|
|
|
|
|
|
grønn/oransje.
|
|
|
|
|
|
**Bygget:** ny `components/individual-tournament-detail.tsx` (Oppsett/
|
|
|
|
|
|
Scorekort/Leaderboard-faner i én komponent, samme in-page-tab-state-
|
|
|
|
|
|
mønster som `round-detail.tsx`) + ny `components/tournament-router.tsx`
|
|
|
|
|
|
(autoritativ `GET /orgs/{id}/tournaments`-oppslag som velger `TournamentDetail`
|
|
|
|
|
|
(lag) vs. `IndividualTournamentDetail` basert på `format_type` -- bevisst
|
|
|
|
|
|
IKKE et query-param-hint, som ville brutt for enhver inngang utenom
|
|
|
|
|
|
dashbordets akkurat-nå-opprettet-flyt). `app/tournaments/[id]/page.tsx`
|
|
|
|
|
|
peker nå til routeren. `dashboard.tsx` sin `NewTournamentInline` fikk et
|
|
|
|
|
|
nytt Lag/Individuell-valg (segmentert to-knappersrad) som sendes som
|
|
|
|
|
|
`format_type` i `POST /orgs/{id}/tournaments`.
|
|
|
|
|
|
**Ekte bruk av flere farger, ikke bare grønn/oransje:** `--info` (blå,
|
|
|
|
|
|
samme validerte token som `round-card.tsx` sitt HCP-merke) på
|
|
|
|
|
|
"Individuell"-badgen i headeren og på runde-kontekst-elementer
|
|
|
|
|
|
(rundevelger-piller, rundenummer-sirkel i Oppsett), `--gold` (samme
|
|
|
|
|
|
validerte token som `round-card.tsx` sitt "Personlig rekord"-merke) på
|
|
|
|
|
|
leaderboardets 1.-plass-rad (medaljeikon + gullbakgrunn).
|
|
|
|
|
|
**Scorekortet** gjenbruker `round-scorecard.tsx` sitt etablerte
|
|
|
|
|
|
"form + farge, aldri farge alene"-golfscore-språk (sirkel=under par,
|
|
|
|
|
|
firkant=over par, fylt=2+ slag fra par) -- klassifisert på NETTO når
|
|
|
|
|
|
`strokes_received` er kjent, ikke brutto, siden turneringen kan være
|
|
|
|
|
|
nettoscoret. Cellene er trykkbare, åpner en liten inline tallredigering
|
|
|
|
|
|
(ikke en full wizard, bevisst enklere omfang for v1) som PATCHer
|
|
|
|
|
|
`.../holes/{n}` direkte.
|
|
|
|
|
|
**Ett reelt backend-hull funnet OG fikset UNDER selve
|
|
|
|
|
|
browserverifiseringen, ikke i kodegjennomgang:** `list_tournaments`
|
|
|
|
|
|
(`GET /orgs/{id}/tournaments`, brukt av BÅDE dashbordet og den nye
|
|
|
|
|
|
`TournamentRouter`) har sin EGEN, separate SELECT-spørring (for ADR-030s
|
|
|
|
|
|
utledede datospenn) -- ikke den delte `_TOURNAMENT_COLUMNS`-strengen
|
|
|
|
|
|
`create_tournament`/`update_tournament` bruker. Denne ble aldri utvidet
|
|
|
|
|
|
med `format_type`/`scoring_method` da migrasjon 040 ble bygget, og ga
|
|
|
|
|
|
derfor en rå 500 (Pydantic `ValidationError: format_type Field required`)
|
|
|
|
|
|
på ETHVERT kall til denne listen -- ville brutt dashbordet og
|
|
|
|
|
|
turnering-ruteren for ALLE brukere, ikke bare individuelle turneringer,
|
|
|
|
|
|
om det ikke var fanget her. Rettet med to nye kolonner i SELECT-en.
|
|
|
|
|
|
**Ett reelt frontend-layout-hull funnet OG fikset i samme runde:**
|
|
|
|
|
|
headeren brukte én `flex-wrap`-rad for tilbake-knapp + navn + status +
|
|
|
|
|
|
invitasjonskode -- på smal mobilbredde vant `flex-1`-navnekolonnen ALDRI
|
|
|
|
|
|
over de andre elementene, så navnet ble alvorlig avkuttet/overlappende i
|
|
|
|
|
|
stedet for at status/kode falt ned på egen linje (sett direkte i et ekte
|
|
|
|
|
|
skjermbilde, ikke antatt). Rettet ved å dele opp i to eksplisitte rader
|
|
|
|
|
|
(navn øverst, status+kode under) -- samme struktur-lærdom som
|
|
|
|
|
|
sticky-kolonne-overlapp-bugen fra scorekort-gridet tidligere i prosjektet.
|
|
|
|
|
|
**Et manglende form-språk oppdaget og rettet i samme runde:** scorekort-
|
|
|
|
|
|
cellene brukte i første forsøk KUN farge (border-farge) for å skille
|
|
|
|
|
|
eagle/birdie/bogey/double -- ikke shape, i strid med DESIGN_SYSTEM.md sin
|
|
|
|
|
|
"aldri farge alene"-regel og `round-scorecard.tsx` sin egen etablerte
|
|
|
|
|
|
`ScoreMark`. Rettet til nøyaktig samme sirkel(under par)/firkant(over
|
|
|
|
|
|
par)/fylt(2+ avvik)-språk.
|
|
|
|
|
|
**Browserverifisert grundig, mot en isolert scratch-backend** (samme
|
|
|
|
|
|
mønster som resten av uken -- isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-container, pluss en egen engangs
|
|
|
|
|
|
frontend-container med kildekoden KOPIERT inn, ikke bind-mountet -- en
|
|
|
|
|
|
bind-mountet `next dev` viste seg ustabil her, gjentatte Turbopack-panics
|
|
|
|
|
|
("Next.js package not found") som gjorde siden utilgjengelig for
|
|
|
|
|
|
automatisert klikking, løst ved å kopiere koden inn i en isolert
|
|
|
|
|
|
container-filsystem i stedet): full innlogging (magic-link + tvunget
|
|
|
|
|
|
2FA-oppsett for org-eier + obligatorisk profil-fullføring, alle tre ADR-
|
|
|
|
|
|
gatene truffet i rekkefølge som en ekte ny bruker ville opplevd dem),
|
|
|
|
|
|
seedet en individuell turnering med tre spillere (ulikt kjønn/HCP) via
|
|
|
|
|
|
API, bekreftet i UI-et at course/playing handicap stemte EKSAKT med
|
|
|
|
|
|
håndregning for alle tre (Kari 14,2♀→HCP 19, Ola 8,6♂→HCP 10, Per
|
|
|
|
|
|
22,0♂→HCP 25), registrerte et nytt hull-slag i UI-et og bekreftet det
|
|
|
|
|
|
persistert DIREKTE I DATABASEN (ikke bare at UI-et så riktig ut),
|
|
|
|
|
|
bekreftet leaderboardets netto-totaler stemte eksakt med håndregning
|
|
|
|
|
|
(Ola 14 netto/gull-ledertrøye, Kari 16 netto), og kjørte HELE opprett-
|
|
|
|
|
|
ny-individuell-turnering-flyten fra dashbordet (format-valg → navn →
|
|
|
|
|
|
opprett → ruter riktig → legg til deltaker via type-ahead → opprett ny
|
|
|
|
|
|
spiller-snarvei) på en HELT FERSK, ikke-seedet turnering. Ingen
|
|
|
|
|
|
konsollfeil (`list_console_messages`) gjennom hele økten. Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild (samme `Dockerfile` som deployes) kompilerte
|
|
|
|
|
|
rent til slutt, med begge fiksene inne.
|
|
|
|
|
|
**Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt:
|
|
|
|
|
|
ingen migrasjon, `docker compose up -d --build teecup_api
|
|
|
|
|
|
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
|
|
|
|
|
→ 200, `teeoff.no` upåvirket, `GET /orgs/.../tournaments` (den fikset
|
|
|
|
|
|
ruten) bekreftet nåbar med korrekt `401` gjennom hele produksjonsstacken.
|
|
|
|
|
|
**ADR-037 er dermed live, backend + frontend** (grunnstruktur — de fem
|
|
|
|
|
|
konkrete formatene og Order of Merit fortsatt ikke designet, se
|
|
|
|
|
|
FEATURE_BACKLOG.md).
|
|
|
|
|
|
|
|
|
|
|
|
- **To reelle punkter fra bruker, BEGGE FIKSET OG LIVE, samme dag
|
|
|
|
|
|
(2026-07-30):** rapportert med et vedlagt skjermbilde av
|
|
|
|
|
|
Chip/Bunker/Straffeslag-stepperne som gled over i hverandre på
|
|
|
|
|
|
`/my-rounds/{id}`, pluss en forespørsel om et TeeCup-relatert favikon.
|
|
|
|
|
|
1. **Stepper-overlapp, root cause presist identifisert:** `ScoringWizard`
|
|
|
|
|
|
sin "flere detaljer"-seksjon (`round-detail.tsx`) er fast begrenset
|
|
|
|
|
|
til `max-w-sm` (384px) UANSETT hvor bred selve viewporten er -- men
|
|
|
|
|
|
Chip/Bunker/Straffeslag-gridet brukte et VIEWPORT-basert
|
|
|
|
|
|
`sm:grid-cols-3`-brudd (640px). Enhver skjerm bredere enn 640px
|
|
|
|
|
|
(dvs. de fleste telefoner i liggende modus, nettbrett, og skjermbildet
|
|
|
|
|
|
brukeren delte) trigget dermed 3-kolonne-modus INNI en 384px-bred
|
|
|
|
|
|
kolonne -- hver Stepper har to faste 44px-knapper (tilgjengelighets-
|
|
|
|
|
|
kravet, kan ikke krympes) + verdi + padding, som aldri kan bli
|
|
|
|
|
|
smalere enn ca. 190px, så tre av dem kolliderte alltid. Samme
|
|
|
|
|
|
klasse feil som de tidligere sticky-kolonne- og match-identitets-
|
|
|
|
|
|
boks-overlappene i prosjektet (fast innholdsbredde møter et
|
|
|
|
|
|
viewport-basert, ikke container-basert, brudd). Fikset ved å fjerne
|
|
|
|
|
|
`sm:grid-cols-3` helt -- alltid stablet i én kolonne, som uansett var
|
|
|
|
|
|
den eneste bredden som noensinne hadde plass i en 384px-container.
|
|
|
|
|
|
2. **Favikon var faktisk V0s egen logo, ikke TeeCup:** `icon.svg` og
|
|
|
|
|
|
`icon-light-32x32.png`/`icon-dark-32x32.png` (selve nettleser-fane-
|
|
|
|
|
|
ikonet, styrt av `layout.tsx` sin `metadata.icons`) viste seg -- ved
|
|
|
|
|
|
faktisk å åpne og se på filene, ikke anta -- å fortsatt være en
|
|
|
|
|
|
ubrukt "V0"-logo (sort/hvit v0.app-merke) helt siden V0-eksporten,
|
|
|
|
|
|
aldri erstattet. `apple-icon.png` og selve PWA-ikonsettet
|
|
|
|
|
|
(`public/icons/icon-192.png` m.fl.) var derimot ALLEREDE korrekt
|
|
|
|
|
|
TeeCup-merket (grønt golf-flagg, fra PWA-runden 2026-07-19) -- kun
|
|
|
|
|
|
favikon-stien var glemt. Fikset ved å gjenbruke SAMME etablerte
|
|
|
|
|
|
golf-flagg-design: `icon-light-32x32.png`/`icon-dark-32x32.png`
|
|
|
|
|
|
regenerert (nedskalert fra `icon-512.png` via en engangs
|
|
|
|
|
|
`sharp`-scratch, samme verktøy som PWA-runden brukte) og `icon.svg`
|
|
|
|
|
|
skrevet på nytt som en ren vektor av samme flagg (grønn `#8BC24A`-
|
|
|
|
|
|
bakgrunn, hvit flaggstang+flagg -- appens egen merkevarefarge, ikke
|
|
|
|
|
|
funnet på).
|
|
|
|
|
|
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`
|
|
|
|
|
|
som deployes) kompilerte rent BEGGE ganger (én mislykket 404-test mot
|
|
|
|
|
|
kun `--target builder`-imaget, som IKKE har `public/`-mappen kopiert inn
|
|
|
|
|
|
ved siden av `server.js` ennå -- forventet byggestrenge-artefakt, ikke en
|
|
|
|
|
|
reell bug -- rettet ved å bygge og teste det FULLE, faktiske
|
|
|
|
|
|
produksjonsimaget i stedet, som ga korrekt `200`/riktig `content-type`
|
|
|
|
|
|
for alle tre ikonfilene). Stepper-fiksen browserverifisert grundig i en
|
|
|
|
|
|
isolert scratch-økt (ekte innlogging, en fersk `full`-stat_level-runde
|
|
|
|
|
|
seedet via API, veiviseren kjørt gjennom Slag→Putter→Avstand→Detaljer):
|
|
|
|
|
|
bekreftet INGEN overlapp ved 800px viewport (der bugen reprodusertes
|
|
|
|
|
|
presist, matcher brukerens skjermbilde) OG ingen regresjon ved 390px
|
|
|
|
|
|
(alltid stablet der uansett, uendret oppførsel). Ingen konsollfeil.
|
|
|
|
|
|
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt (samme
|
|
|
|
|
|
"redeploy begge"-godkjenning som ADR-037): ingen migrasjon,
|
|
|
|
|
|
`docker compose up -d --build teecup_frontend` (gjenskapte også
|
|
|
|
|
|
`teecup_api` som vanlig bivirkning, ingen backend-kode rørt). Begge
|
|
|
|
|
|
containere boot-et rent, `/health`/`/dashboard` → 200, favikon-filene
|
|
|
|
|
|
bekreftet nåbare over ekte https med riktig `content-type`, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Individuelle turneringer (ADR-037): scoring-autorisasjon strammet inn,
|
|
|
|
|
|
BYGGET, SCRATCH-VERIFISERT OG LIVE (2026-07-30), samme dag som frontend-
|
|
|
|
|
|
runden:** direkte oppfølging av det noterte hullet fra byggerunden
|
|
|
|
|
|
tidligere samme dag — ethvert org-medlem kunne skrive score for HVEM SOM
|
|
|
|
|
|
HELST i en individuell turnering, ikke bare sin egen.
|
|
|
|
|
|
Ny `user_is_own_tournament_participant` i `app/team_authz.py` — samme
|
|
|
|
|
|
mønster som `user_is_match_participant` (ADR-023): krever at brukeren ER
|
|
|
|
|
|
spilleren bak `tournament_participant`-raden (via `player.user_id`),
|
|
|
|
|
|
eller er org-eier/admin. Brukt av `individual_tournaments.py` sin
|
|
|
|
|
|
`update_hole` (eneste endepunkt strammet inn). Runde-/deltaker-OPPSETT
|
|
|
|
|
|
(opprett/slett runde, legg til/fjern turnering-/rundedeltaker) forblir
|
|
|
|
|
|
bevisst på vanlig org-medlemsnivå — matcher presedensen fra `session`-
|
|
|
|
|
|
opprettelse og `team_roster`-tilføyelse i tournaments.py, som heller
|
|
|
|
|
|
aldri har vært captain-/admin-gatet.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-container, alle 40 migrasjoner kjørt
|
|
|
|
|
|
friskt): full regresjon av den eksisterende 52-punkts testsuiten (uendret
|
|
|
|
|
|
grønn — org-eier brukes gjennomgående der, rammes ikke av innstrammingen),
|
|
|
|
|
|
pluss 13 nye målrettede sjekker: et fremmed org-medlem (uten kobling til
|
|
|
|
|
|
noen av deltakerne) NEKTES å score for både en annen deltaker OG en
|
|
|
|
|
|
tredje deltaker (403 `NOT_TOURNAMENT_PARTICIPANT`), en spiller KOBLET til
|
|
|
|
|
|
sin egen `tournament_participant` (via `player.email` + ADR-017s
|
|
|
|
|
|
kontokobling ved innlogging) FÅR score seg selv men NEKTES å score for en
|
|
|
|
|
|
annen, org-eier beholder uendret admin-fallback for begge, og lesing
|
|
|
|
|
|
(`GET .../holes`) er bekreftet uendret tilgjengelig for et vanlig
|
|
|
|
|
|
org-medlem (kun skriving er strammet inn, ikke lesing).
|
|
|
|
|
|
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon (ren Python-logikk), `docker compose up -d --build
|
|
|
|
|
|
teecup_api`. Containeren boot-et rent (`Application startup complete`),
|
|
|
|
|
|
`/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **To notater fra brukeren, BEGGE BYGGET, GRUNDIG BROWSERVERIFISERT OG
|
|
|
|
|
|
LIVE (2026-07-30), samme dag som ADR-037-autorisasjonsfiksen:**
|
|
|
|
|
|
1. **GIR-auto-inferens for "Innspill: Traff":** ny `useEffect` i
|
|
|
|
|
|
`ScoringWizard` (`round-detail.tsx`) -- idet "flere detaljer"-steget
|
|
|
|
|
|
nås, settes `stat.approach = "hit"` automatisk når
|
|
|
|
|
|
`stat.strokes - stat.putts <= hole.par - 2` (samme formel som den
|
|
|
|
|
|
allerede eksisterende GIR-STATISTIKK-inferensen i `round-stats.tsx`
|
|
|
|
|
|
sin `isGir`, nå også koblet til selve REGISTRERINGEN) -- men KUN når
|
|
|
|
|
|
`stat.approach` fortsatt er `null` (rører aldri et allerede satt
|
|
|
|
|
|
manuelt ELLER tidligere auto-satt valg).
|
|
|
|
|
|
2. **Auto-prompt "Fullfør runde":** ny `allHolesEnteredForEveryone`-
|
|
|
|
|
|
beregning + `AllHolesEnteredBanner`-komponent i `RoundDetail` -- viser
|
|
|
|
|
|
en tydelig CTA øverst på Score-fanen ("Alle hull er ført — Klar til å
|
|
|
|
|
|
fullføre runden?") så snart ALLE spillere (eller BEGGE sider for
|
|
|
|
|
|
delt-ball-formater) har `played=true` på alle hull i `holeOrder`,
|
|
|
|
|
|
gatet på `round.setup_complete`. Kaller samme `finishRound()` som den
|
|
|
|
|
|
eksisterende, tidligere passive knappen.
|
|
|
|
|
|
**Browserverifisert grundig i en isolert scratch-nettleserøkt** (fersk
|
|
|
|
|
|
`teecup_scratch`-database + isolert scratch-MinIO + engangs API-
|
|
|
|
|
|
container + en isolert `next dev`-frontend-container med KOPIERT, ikke
|
|
|
|
|
|
bind-mountet, kildekode -- bind-mount ga gjentatte Turbopack-panics i
|
|
|
|
|
|
denne økten, løst ved å `tar`-kopiere kildetreet inn i en isolert
|
|
|
|
|
|
container-filsystem i stedet): GIR-auto-inferens bekreftet BÅDE positivt
|
|
|
|
|
|
(birdie+1-putt -> "Traff" auto-merket umiddelbart, uten klikk, bekreftet
|
|
|
|
|
|
visuelt OG direkte i databasen) og negativt (bogey+1-putt -> "Traff"
|
|
|
|
|
|
korrekt IKKE forhåndsmerket). Auto-prompt-banneret bekreftet å dukke opp
|
|
|
|
|
|
automatisk idet siste hull ble fylt for begge spillerne i en 18-hulls
|
|
|
|
|
|
2-spiller-runde, og "Fullfør runden"-knappen i banneret bekreftet å
|
|
|
|
|
|
fullføre runden korrekt (håndterte den native `confirm()`-dialogen via
|
|
|
|
|
|
Chrome DevTools -- HCP-differensialer beregnet og vist etterpå). Ingen
|
|
|
|
|
|
konsollfeil i noen av rundene.
|
|
|
|
|
|
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon (ren frontend), `docker compose up -d --build
|
|
|
|
|
|
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning,
|
|
|
|
|
|
ingen backend-kode rørt). Begge containere boot-et rent, `/health`/
|
|
|
|
|
|
`/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Symbolforklaringen fjernet fra scorekortet + to design-notater fanget
|
|
|
|
|
|
opp (2026-07-30), samme dag:** brukeren viste et skjermbilde av
|
|
|
|
|
|
`round-scorecard.tsx` med "Under par (sirkel)/Over par (firkant)/Fylt
|
|
|
|
|
|
symbol..."-forklaringen sirklet inn og ba om at den fjernes — `Legend`-
|
|
|
|
|
|
komponenten (kun ett bruksted) fjernet fullstendig. Samtidig reist:
|
|
|
|
|
|
(1) spilleren bør kunne se historikk/statistikk for NØYAKTIG hullet som
|
|
|
|
|
|
spilles/er spilt (ikke bygget — krever en ny, ikke-triviell "aggreger på
|
|
|
|
|
|
tvers av runder, filtrert til banenavn+hullnummer"-spørring, notert i
|
|
|
|
|
|
FEATURE_BACKLOG.md); (2) manglende lenke scorekort→statistikk, og et
|
|
|
|
|
|
åpent spørsmål om scorekort/statistikk/live-registrering burde slås
|
|
|
|
|
|
sammen til én visning, med mottatte slag vist som prikker. Svarte
|
|
|
|
|
|
direkte (ikke bygget): behold scorekort og statistikk som to separate
|
|
|
|
|
|
sider (bevisst skilt 2026-07-25 nettopp for å unngå én lang/rotete
|
|
|
|
|
|
side) — legg heller til den manglende lenken; behold også selve
|
|
|
|
|
|
live-registrerings-gridet uendret (bygget 2026-07-27 som en AKTIV
|
|
|
|
|
|
data-entry-flate, ikke en lesevisning — et vertikalt front9/back9-delt
|
|
|
|
|
|
format ville svekket registreringsergonomikken). "Prikker for mottatte
|
|
|
|
|
|
slag" vurdert som en god, uavhengig senere polish-oppgave. Full
|
|
|
|
|
|
begrunnelse i FEATURE_BACKLOG.md.
|
|
|
|
|
|
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ekte
|
|
|
|
|
|
typesjekket produksjonsbuild kompilerte rent, `docker compose up -d
|
|
|
|
|
|
--build teecup_frontend` (gjenskapte også `teecup_api` som vanlig
|
|
|
|
|
|
bivirkning). Begge containere boot-et rent, `/health` → 200,
|
|
|
|
|
|
scorekort-siden bekreftet 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **De to avgrensede scorekort-oppfølgerne fra samme dag BYGGET,
|
|
|
|
|
|
BROWSERVERIFISERT OG LIVE (2026-07-30):** (1) ny "Se full
|
|
|
|
|
|
rundestatistikk"-lenke nederst på `round-scorecard.tsx` (symmetrisk med
|
|
|
|
|
|
den eksisterende motsatte lenken på statistikksiden); (2) mottatte
|
|
|
|
|
|
slag-hintet (vist FØR et hull er fylt ut) endret fra "−N"-tekst til
|
|
|
|
|
|
prikker (`StrokeDots`, `round-detail.tsx`, i BÅDE `ScorecardGrid` og
|
|
|
|
|
|
`SideScorecardGrid`) — selve tallet bevart i `aria-label` for
|
|
|
|
|
|
tilgjengelighet. Selve spørsmålet om å slå sammen scorekort/statistikk/
|
|
|
|
|
|
live-registrering til én visning er BEVISST IKKE avgjort — bruker
|
|
|
|
|
|
ønsker en fremtidig brukertest først.
|
|
|
|
|
|
**Browserverifisert grundig i en isolert scratch-nettleserøkt** (samme
|
|
|
|
|
|
mønster som resten av uken): en HCP 28-spiller (course handicap 30)
|
|
|
|
|
|
bekreftet å vise nøyaktig to prikker på hull 1-12 i live-
|
|
|
|
|
|
registreringsgridet (riktig ut fra 30-18=12 ekstra slag), den nye
|
|
|
|
|
|
lenken bekreftet klikkbar og navigerte korrekt til `/stats`. Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild kompilerte rent. Ingen konsollfeil (kun en
|
|
|
|
|
|
godartet, urelatert WebSocket-advarsel fra rask sidenavigasjon).
|
|
|
|
|
|
**Rullet ut live 2026-07-30**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
|
|
|
|
|
|
også `teecup_api` som vanlig bivirkning). Begge containere boot-et
|
|
|
|
|
|
rent, `/health`/`/dashboard`/scorekort-siden → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Åtte nye turneringsformater: BACKEND FERDIG, SCRATCH-VERIFISERT OG
|
|
|
|
|
|
RULLET UT LIVE (2026-07-30):** brukeren ba om å ta fatt på det
|
|
|
|
|
|
lenge noterte "flere turneringsformater"-punktet fra 2026-07-19. Listen
|
|
|
|
|
|
ble utvidet fra fire til åtte (Shamble, Chapman/Pinehurst, Bingo Bango
|
|
|
|
|
|
Bongo, Money Ball/Lone Ranger, Nassau Match Play lagt til; "High-low-
|
|
|
|
|
|
high"/"Try all" presist avklart av bruker med et fullt utregnet
|
|
|
|
|
|
eksempel; "Robbins" droppet). Full plan skrevet og godkjent (plan-
|
|
|
|
|
|
modus) FØR bygging, deretter bygget ETT format om gangen i rekkefølgen
|
|
|
|
|
|
Chapman → Nassau → Københavner → Bingo Bango Bongo → Flaggturnering →
|
|
|
|
|
|
Shamble → Money Ball → High-low-high, motor→migrasjon→API→scratch-
|
|
|
|
|
|
verifisering per format FØR neste startet.
|
|
|
|
|
|
**Arkitektonisk hjem** (bekreftet av bruker "begge, fra start"): flatt-
|
|
|
|
|
|
felt-formater (Københavner/BBB/Flag) → frittstående runder OG den
|
|
|
|
|
|
individuelle org-turnering-modellen (ADR-037), ALDRI org-lagturneringer
|
|
|
|
|
|
(ADR-011s to-lags-modell passer strukturelt ikke). To-siders-formater
|
|
|
|
|
|
(Chapman/Nassau/Shamble/Money Ball/High-low-high) → frittstående runder
|
|
|
|
|
|
OG org-lagturneringer, ALDRI den individuelle org-modellen. Shamble/
|
|
|
|
|
|
Money Ball i frittstående runder: ETT LAG = HELE RUNDENS deltakersett
|
|
|
|
|
|
(bekreftet av bruker, INGEN `round_side`) -- flere lag kobles via
|
|
|
|
|
|
eksisterende `flight_group_id`-leaderboard.
|
|
|
|
|
|
**Ny delt `ENGINE_FORMAT_ALIASES`-mekanisme** i `app/handicap.py` --
|
|
|
|
|
|
Chapman/Shamble/Money Ball/High-low-high aliaserer til
|
|
|
|
|
|
foursome/singles/singles/fourball for HCP-beregning (ingen dupliser
|
|
|
|
|
|
allowance-logikk), løst opp FØRST i `parse_allowance_config`/
|
|
|
|
|
|
`compute_and_store_side_handicaps`/`relative_strokes_for_match`.
|
|
|
|
|
|
**Reelle funn/presiseringer underveis, alle rettet FØR utrulling:**
|
|
|
|
|
|
Nassau kan hindres av org-matchers eksisterende ALREADY_DECIDED-sperre
|
|
|
|
|
|
(ADR-012) hvis overall-matchen avgjøres tidlig -- dokumentert v1-
|
|
|
|
|
|
begrensning, ikke fikset. Københavner-poeng må regnes om for ALLE tre
|
|
|
|
|
|
deltakerne samtidig (eneste scoring_method som bryter "regn om for ÉN
|
|
|
|
|
|
deltaker"-mønsteret). Money Ball-rotasjon er basert på POSISJON i spilt
|
|
|
|
|
|
rekkefølge, ikke rått hullnummer. High-low-high passer ikke inn i den
|
|
|
|
|
|
ternære `HoleResult`-cachen -- egen dedikert leseendepunkt per system
|
|
|
|
|
|
(som Nassau), generisk `status_text` viser en ufarlig statisk "AS"-
|
|
|
|
|
|
plassholder for dette formatet. To reelle "manglende SELECT-kolonne"-
|
|
|
|
|
|
krasj (bbb_sweep_bonus_enabled, shamble_best_n) fanget og rettet under
|
|
|
|
|
|
scratch-testing, før noe nådde en antatt-ferdig tilstand.
|
|
|
|
|
|
**Verifisert grundig:** `handicap_engine.py`-enhetstester 98/98 (opp fra
|
|
|
|
|
|
63 FØR denne runden), full scratch-API-verifisering i begge relevante
|
|
|
|
|
|
systemer per format (isolert `teecup_app_scratch`-rolle + isolert
|
|
|
|
|
|
scratch-MinIO + engangs API-container) -- over 400 sjekker totalt på
|
|
|
|
|
|
tvers av de åtte formatenes testskript, inkl. High-low-high sin
|
|
|
|
|
|
eksakte gjenskaping av brukerens eget håndregnede eksempel ("1-1 etter
|
|
|
|
|
|
hull 1", "2-1 til lag 2"). `test_isolation.sql` 12/12 uendret gjennom
|
|
|
|
|
|
hele runden (migrasjonene 041-050 er rent additive).
|
|
|
|
|
|
**IKKE bygget ennå, bevisst neste steg:** frontend for samtlige åtte
|
|
|
|
|
|
formater (nye format-valg i opprett-runde/opprett-økt, nye
|
|
|
|
|
|
resultatvisninger) — backend er fullt funksjonelt og testet, men
|
|
|
|
|
|
ubrukelig fra selve appen inntil frontend bygges, samme lagdelings-
|
|
|
|
|
|
mønster som ADR-037/038/039.
|
|
|
|
|
|
**Rullet ut mot ekte systemer 2026-07-30**, bruker bekreftet eksplisitt
|
|
|
|
|
|
("Ja takk"): migrasjonene 041-050 kjørt i rekkefølge mot ekte
|
|
|
|
|
|
`teecup_db` (alle 10 rene, additive `DROP/ADD CONSTRAINT`+nye
|
|
|
|
|
|
tabeller/kolonner, ingen eksisterende rader rørt), `test_isolation.sql`
|
|
|
|
|
|
fortsatt 12/12, deretter `docker compose up -d --build teecup_api`.
|
|
|
|
|
|
Containeren boot-et rent (`Application startup complete`), `/health`/
|
|
|
|
|
|
`/dashboard` → 200, `teeoff.no` upåvirket. Verifisert presist at det nye
|
|
|
|
|
|
Nassau-endepunktet faktisk når FastAPI gjennom hele produksjonsstacken:
|
|
|
|
|
|
et anonymt kall ga korrekt `401 NOT_AUTHENTICATED`, ikke en rå 404.
|
|
|
|
|
|
Scratch-miljøet (isolert rolle/database/MinIO/API-container, alle
|
|
|
|
|
|
test-skript) ryddet opp etterpå, som vanlig. Se FEATURE_BACKLOG.md for
|
|
|
|
|
|
full detalj per format.
|
|
|
|
|
|
|
|
|
|
|
|
- **Driftsvarsel ved ny konto, BYGGET, SCRATCH-VERIFISERT OG LIVE
|
|
|
|
|
|
(2026-07-30):** brukeren ba om en e-post til seg selv
|
|
|
|
|
|
(`hei@erol.no`) hver gang noen oppretter en HELT NY TeeCup-konto —
|
|
|
|
|
|
avklart eksplisitt (AskUserQuestion) at dette gjelder kontoopprettelse
|
|
|
|
|
|
generelt (ikke turnering-selvregistrering, ADR-017, som var det andre
|
|
|
|
|
|
alternativet). Ingen migrasjon — ren kode-endring.
|
|
|
|
|
|
Ny `settings.NEW_ACCOUNT_ALERT_EMAIL` (`app/config.py`, env-variabel
|
|
|
|
|
|
`TEECUP_NEW_ACCOUNT_ALERT_EMAIL`, default `hei@erol.no` — egen setting
|
|
|
|
|
|
fremfor hardkodet adresse, kan endres uten ny utrulling). Ny
|
|
|
|
|
|
`send_new_account_alert_email()` i `app/email.py` (alltid norsk — internt
|
|
|
|
|
|
driftsvarsel til én fast, kjent mottaker, ikke brukervendt i18n-tekst).
|
|
|
|
|
|
**Kjernestykket:** `verify_magic_link` (`app/routers/auth.py`) er den
|
|
|
|
|
|
ENESTE plassen en `app_user`-rad noensinne settes inn (bekreftet med
|
|
|
|
|
|
`grep -rn "INSERT INTO app_user"` — null andre treff) — utvidet til å
|
|
|
|
|
|
fange `is_new_account` (sann KUN når selve INSERT-en faktisk vant, ikke
|
|
|
|
|
|
ved en samtidig konflikt ELLER en sekundær-e-post-innlogging som løses
|
|
|
|
|
|
til en eksisterende konto, `FEATURE_BACKLOG.md` "Én person, flere
|
|
|
|
|
|
e-postadresser"). Selve e-postutsendingen skjer BEVISST ETTER at
|
|
|
|
|
|
`plain_connection()`-blokken er lukket (samme "e-post skal aldri sendes
|
|
|
|
|
|
mens en tilkobling/transaksjon holdes åpen"-prinsipp som resten av
|
|
|
|
|
|
appen), med samme `DEV_LOG_MAGIC_LINKS`/`SMTP_CONFIGURED`/try-except-
|
|
|
|
|
|
mønster som all annen e-postutsending — en driftsfeil i selve
|
|
|
|
|
|
sendingen kan aldri endre innloggingsresponsen.
|
|
|
|
|
|
**Scratch-verifisert, 8/8 sjekker:** varsel logget nøyaktig én gang ved
|
|
|
|
|
|
aller første innlogging for en ny e-post, INGEN nytt varsel ved en
|
|
|
|
|
|
påfølgende innlogging for SAMME konto, og et helt nytt, eget varsel for
|
|
|
|
|
|
en ANNEN ny e-post (uendret telling for den første). `test_isolation.sql`
|
|
|
|
|
|
12/12 uendret (ingen skjemaendring).
|
|
|
|
|
|
**Rullet ut live 2026-07-30**, sammen med migrasjonene 041-050 over (én
|
|
|
|
|
|
samlet utrulling, bruker bekreftet eksplisitt): ingen migrasjon for
|
|
|
|
|
|
denne delen isolert, dekket av samme `docker compose up -d --build
|
|
|
|
|
|
teecup_api`-kjøring, `/health`/`/dashboard` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **Login-skjermen redesignet ("Forest Green", inspirert av et eksternt
|
|
|
|
|
|
design-verktøy kalt "Stitch"), BYGGET OG LIVE (2026-08-01/02):**
|
|
|
|
|
|
brukeren ba eksplisitt om et V0-prompt basert på Stitchs skisse, kjørte
|
|
|
|
|
|
det selv, sendte zip-eksporten tilbake. `frontend/components/login-
|
|
|
|
|
|
form.tsx` skrevet fullstendig om — V0s nye visuelle skall (låst
|
|
|
|
|
|
fargepalett `const C = {...}`, WCAG-kontrast verifisert presist, ikke
|
|
|
|
|
|
anslått, for hvert fargevalg) flettet med 100 % ekte logikk fra den
|
|
|
|
|
|
gamle komponenten uendret: `sendLink()` (`POST /auth/request-link`),
|
|
|
|
|
|
passord-innlogging (`POST /auth/login-password`), invitasjonskode-
|
|
|
|
|
|
oppslag (`GET /public/tournaments/by-code/{code}` + navigasjon).
|
|
|
|
|
|
`TwoFactorVerifyForm`/`TwoFactorSetupForm` (ADR-021) beholdt HELT
|
|
|
|
|
|
uendret, kun rendret inni det nye kortskallet. **Reell bug funnet under
|
|
|
|
|
|
integrering, browserverifisert:** `app/page.tsx` hadde sin egen
|
|
|
|
|
|
`<Wordmark/>`+tagline-header OVER selve `LoginForm`, som nå OGSÅ hadde
|
|
|
|
|
|
fått sin egen header inni det nye kortet — duplikat. Fjernet den ytre
|
|
|
|
|
|
headeren + det nå ubrukte `Wordmark`-importet fra `page.tsx`.
|
|
|
|
|
|
**Rullet ut live**, bruker bekreftet eksplisitt ("Rull ut live. Jeg har
|
|
|
|
|
|
comitted til git og kan reversere om nødvendig."): ren frontend-endring,
|
|
|
|
|
|
ingen migrasjon.
|
|
|
|
|
|
|
|
|
|
|
|
- **Dashbordet redesignet i samme visuelle retning, BYGGET OG LIVE
|
|
|
|
|
|
(2026-08-01/02), to runder samme dag:** første runde (V0-eksport)
|
|
|
|
|
|
skrev om `dashboard.tsx` fra bunnen — flettet ekte ADR-035-datalag
|
|
|
|
|
|
(organisasjoner/turneringer/runder/HCP-historikk/venner/varsler) med
|
|
|
|
|
|
V0s nye visuelle seksjoner, la til en NY «Venner på banen»-seksjon
|
|
|
|
|
|
(bekreftet av bruker at den skal ligge RETT UNDER hurtighandlingene,
|
|
|
|
|
|
jf. Stitchs egen skisse — jeg hadde ikke selv sjekket dette FØR
|
|
|
|
|
|
brukeren spurte eksplisitt, rettet før prompten ble sendt).
|
|
|
|
|
|
**Ny backend-endepunkt bygget for dette:** `GET /friends/on-course`
|
|
|
|
|
|
(`app/routers/rounds.py`, ny `FriendOnCourseEntry`-modell) — venner med
|
|
|
|
|
|
en PÅGÅENDE runde, eller en fullført innen siste 24 timer (bekreftet
|
|
|
|
|
|
av bruker), filtrert gjennom SAMME synlighets-SQL som `_can_view_
|
|
|
|
|
|
round` sin venner-gren (ADR-036 fase 2) — ingen egen, parallell
|
|
|
|
|
|
synlighetsregel.
|
|
|
|
|
|
**Andre runde, presise fargekorreksjoner mot Stitchs FAKTISKE
|
|
|
|
|
|
skjermbilde** (ikke bare beskrivelse — brukeren lastet opp det ekte
|
|
|
|
|
|
bildet etter at jeg først måtte innrømme jeg hadde slettet den
|
|
|
|
|
|
opprinnelige zip-en og derfor sammenlignet blindt): pikselverdier
|
|
|
|
|
|
hentet presist via PIL i en engangs Docker-container (Stitchs egen
|
|
|
|
|
|
knappegrønn viste seg å være `#2d950c`, kontrast mot hvit tekst kun
|
|
|
|
|
|
3,88:1 — FEILER WCAG AA, flagget proaktivt FØR den ble brukt).
|
|
|
|
|
|
`const C` fikk `primary:"#1f6b08"` (samme fargefamilie, kontrast
|
|
|
|
|
|
~6,6:1, verifisert med samme relative-luminans-formel), `primaryInk:
|
|
|
|
|
|
"#ffffff"`, `borderSoft:"#e4ebe3"`, ny `AVATAR_HUES`-array (pastell-
|
|
|
|
|
|
par til venne-avatarer). Header endret fra glassmorfisme til flat
|
|
|
|
|
|
opak bakgrunn, flagg-ikonet fikk lys grønn bakgrunn i stedet for
|
|
|
|
|
|
oransje, `QuickAction`/`ShortcutButton` og `LiveFriends`-kortene
|
|
|
|
|
|
fargekorrigert tilsvarende.
|
|
|
|
|
|
**Ny fast bunn-fanerad** (`BottomTabBar`, eksportert fra
|
|
|
|
|
|
`dashboard.tsx`, brukt av flere sider): fem faner (Hjem/Runder/
|
|
|
|
|
|
Turneringer/Profil/Mer). **Reell rute-kollisjon funnet og rettet FØR
|
|
|
|
|
|
utrulling** (samme klasse feil som tidligere i prosjektet, `/rounds`
|
|
|
|
|
|
vs. API-prefikset) — V0s genererte `href="/rounds"`/`href="/tournaments"`
|
|
|
|
|
|
pekte begge feil (ingen slik side finnes) — rettet til `/my-rounds` og
|
|
|
|
|
|
et ankerpunkt `/dashboard#kommende-turneringer` (ingen egen turnering-
|
|
|
|
|
|
liste-side finnes). Ny, tidligere ikke-eksisterende `/more`-side bygget
|
|
|
|
|
|
(`components/more-menu.tsx`, `app/more/page.tsx`) som femte fanes reelle
|
|
|
|
|
|
mål — samler Konto/Venner/Varsler/organisasjoner ett sted, ekte
|
|
|
|
|
|
`handleLogout()`. `<main>`-padding økt (`pb-24`) for å ikke overlappe
|
|
|
|
|
|
den nye faste bunnraden.
|
|
|
|
|
|
`install-prompt.tsx` fikk kun ny JSX (mørk grønn gradient-banner),
|
|
|
|
|
|
100 % av den ekte iOS/Android-deteksjons-/`beforeinstallprompt`-/
|
|
|
|
|
|
14-dagers-utsettelseslogikken uendret.
|
|
|
|
|
|
**Rullet ut live**, bruker bekreftet eksplisitt: ingen migrasjon for
|
|
|
|
|
|
visuelle deler, ny `/friends/on-course`-ruten dekket av samme
|
|
|
|
|
|
`docker compose up -d --build teecup_api teecup_frontend`. Begge
|
|
|
|
|
|
containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
|
|
|
|
|
|
- **«Det store grepet»: sammenhengende rundeoppsett + delt Score/
|
|
|
|
|
|
Scorekort/Leaderboard-navigasjon — steg 1+2 av 5 BYGGET, SCRATCH-/
|
|
|
|
|
|
BROWSERVERIFISERT OG LIVE (2026-08-02), se ADR-040 for full
|
|
|
|
|
|
beslutningslogg:** direkte oppfølging av login-/dashbord-redesignet —
|
|
|
|
|
|
brukeren pekte på to strukturelle hull utover selve fargespråket
|
|
|
|
|
|
(rundeoppsettet spredt over to skjermer, Score/Scorekort/Leaderboard
|
|
|
|
|
|
deler ingen fast navigasjon). Fem beslutninger bekreftet (ADR-040
|
|
|
|
|
|
Beslutning A-E), plan skrevet som en levende Artifact-skisse før
|
|
|
|
|
|
bygging.
|
|
|
|
|
|
**Steg 1 (backend, HCP-prosent alltid justerbar + Match-HCP-bryter):**
|
|
|
|
|
|
ny migrasjon `051_round_allowance_override.sql` (`round.allowance_
|
|
|
|
|
|
override jsonb`), koblet inn i `_recompute_side_handicaps`/`_relative_
|
|
|
|
|
|
strokes_for_round`/`POST`+`PATCH /rounds` (`app/routers/rounds.py`).
|
|
|
|
|
|
Gjenbruker ADR-014s allerede ferdigbygde motor uendret (`match_play_
|
|
|
|
|
|
strokes()`, `parse_allowance_config`) — frittstående runder manglet
|
|
|
|
|
|
bare selve kolonnen.
|
|
|
|
|
|
**Steg 2 (ren kode-fiks, ikke V0): spillere i scorekortet var ALDRI
|
|
|
|
|
|
gruppert lagvis** — reelt, tidligere udokumentert funn rapportert av
|
|
|
|
|
|
bruker med en konkret lenke (fourball-runde der to kjente lagkamerater
|
|
|
|
|
|
ble vist interleaved). Bekreftet i koden: `ScorecardGrid` (`round-
|
|
|
|
|
|
detail.tsx`) og `MatchScorecardGrid` (`round-scorecard.tsx`) sorterte
|
|
|
|
|
|
ingen av dem på `round_side_id`. Fikset med stabil sortering (side A
|
|
|
|
|
|
samlet, deretter side B) i begge.
|
|
|
|
|
|
**Scratch-verifisert presist:** 100 %→50 %-prosent ga `playing_
|
|
|
|
|
|
handicap` 24/10 → 12/5 (nøyaktig som beregnet for hånd), `use_
|
|
|
|
|
|
matchplay_handicap:false` ga rå 12/5 i stedet for differensial 7/0.
|
|
|
|
|
|
Lag-grupperingsfiksen browserverifisert mot en fersk scratch-fourball-
|
|
|
|
|
|
runde som gjenskapte brukerens eget scenario nøyaktig (interleaved
|
|
|
|
|
|
tilføyelsesrekkefølge A→Rødt, B→Blått, C→Rødt, D→Blått) — bekreftet
|
|
|
|
|
|
visuelt i BEGGE visningene at lagene nå vises samlet.
|
|
|
|
|
|
**Rullet ut mot ekte systemer 2026-08-02**, bruker bekreftet eksplisitt
|
|
|
|
|
|
(plan vist FØR migrasjonen): migrasjon 051 kjørt mot ekte `teecup_db`
|
|
|
|
|
|
(kolonne bekreftet, `test_isolation.sql` fortsatt 12/12), deretter
|
|
|
|
|
|
`docker compose up -d --build teecup_api teecup_frontend`. Begge
|
|
|
|
|
|
containere boot-et rent, `/health`/`/dashboard` → 200, anonym
|
|
|
|
|
|
`PATCH /rounds/{ukjent-id}` ga korrekt `401` (ikke en rå 404), `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
**Gjenstår (steg 3-5 av 5, ingen V0-prompt skrevet/sendt ennå):**
|
|
|
|
|
|
V0-prompt 1 (den samlede rundeoppsett-veiviseren), V0-prompt 2 (den
|
|
|
|
|
|
delte fane-raden + spørsmålet om lag-markering utover sortering),
|
|
|
|
|
|
deretter integrering/browserverifisering/utrulling av begge. Se
|
|
|
|
|
|
FEATURE_BACKLOG.md for detaljert arbeidsnotat.
|
|
|
|
|
|
|
|
|
|
|
|
- **«Det store grepet»: steg 3 av 5 (V0-prompt 1, rundeoppsett-
|
|
|
|
|
|
veiviseren) BYGGET, GRUNDIG VERIFISERT OG LIVE (2026-08-02), samme
|
|
|
|
|
|
dag:** V0-prompten (Beslutning A/B) skrevet, kjørt av bruker (zip 25),
|
|
|
|
|
|
`frontend/components/new-round.tsx` skrevet fullstendig om —
|
|
|
|
|
|
V0s 5-stegs veiviser-skall flettet med ekte logikk (offisiell/egen
|
|
|
|
|
|
bane-søk inkl. nearby-geolokasjon, ekte egen-bane-opprettelse portert
|
|
|
|
|
|
fra tidligere versjon, ekte `/people/search`, ny `allowance_override`-
|
|
|
|
|
|
konstruksjon i steg 2 samme mønster som `CreateSessionCard`, og en helt
|
|
|
|
|
|
ny innsendingsorkestrering — runden+deltakere+sider/lineup finnes
|
|
|
|
|
|
FØRST når hele veiviseren er fullført, ulikt den gamle "opprett runden
|
|
|
|
|
|
først"-flyten).
|
|
|
|
|
|
**Reelle feil funnet og rettet under integrering:** V0s egen
|
|
|
|
|
|
`Tee`-type manglet kjønnsfelt (ville vist ufiltrerte utslag) — lagt til
|
|
|
|
|
|
`genders`, filtrert riktig. To TS-feil (prop-spredning som overskrev
|
|
|
|
|
|
`course`/`ownGender` tilbake til `null`, en `"x"`-kjønn-mismatch i
|
|
|
|
|
|
tee-filtreringen).
|
|
|
|
|
|
**Verifisert grundig i isolert scratch:** full produksjonsbuild (alle
|
|
|
|
|
|
ruter listet), to komplette nettleser-gjennomkjøringer mot en fersk
|
|
|
|
|
|
scratch-backend (én Fourball-runde med egen bane/gjest/to sider/90 %
|
|
|
|
|
|
HCP-prosent, én Slagspill-runde med rene standardvalg) — bekreftet
|
|
|
|
|
|
direkte mot databasen at `allowance_override` ble bygget nøyaktig
|
|
|
|
|
|
riktig i begge tilfeller (`per_player` for Fourball siden det ikke er
|
|
|
|
|
|
en side-enhet, `null` for Slagspill), sidene/tildelingene stemte, og
|
|
|
|
|
|
HCP ble beregnet korrekt fra ekte profildata.
|
|
|
|
|
|
**Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
|
|
|
|
|
|
også `teecup_api` som vanlig bivirkning). Begge containere boot-et
|
|
|
|
|
|
rent, `/health`/`/dashboard`/`/my-rounds/new` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket. Full detalj i ARCHITECTURE_DECISIONS.md (ADR-040).
|
|
|
|
|
|
|
|
|
|
|
|
- **«Det store grepet»: steg 4-5 av 5 (V0-prompt 2, den delte Score/
|
|
|
|
|
|
Scorekort/Leaderboard-fane-raden + integrering) BYGGET, GRUNDIG
|
|
|
|
|
|
VERIFISERT OG LIVE (2026-08-02), samme dag — ADR-040 dermed HELT
|
|
|
|
|
|
FERDIG:** V0-prompten (Beslutning C/D) bevisst smalere i omfang enn
|
|
|
|
|
|
prompt 1 — kun selve header/fane-chrome-en, ikke en redesign av
|
|
|
|
|
|
Scorekort-sidens innhold (den sammenslåingen med Statistikk er ren
|
|
|
|
|
|
kode-sammenstabling, løst direkte uten V0). Kjørt av bruker (zip 26)
|
|
|
|
|
|
— ny `components/round-header.tsx` (tatt inn uendret: kontekstblokk
|
|
|
|
|
|
+ tre-fanet segmentert kontroll + en "Administrer"-knapp som åpner en
|
|
|
|
|
|
bunnsheet-dialog med "Spillere og runde"-lenke + "Fullfør runde" +
|
|
|
|
|
|
to-stegs "Slett runde").
|
|
|
|
|
|
**Ekte datalag:** ny `components/round-page-shell.tsx` — henter
|
|
|
|
|
|
rundedata for headeren, implementerer `onFinishRound`/`onDeleteRound`
|
|
|
|
|
|
som egne, selvstendige kall (samme `POST .../complete`/`DELETE ...`
|
|
|
|
|
|
som før, men uavhengig siden komponenten deles av alle tre sidene).
|
|
|
|
|
|
**Wiret inn:** Score (`round-detail.tsx` sin gamle egne header
|
|
|
|
|
|
fjernet, erstattet med `RoundPageShell` — den interne "Score"/
|
|
|
|
|
|
"Spillere og runde"-fanevekslingen bevisst BEHOLDT som lavrisiko-valg,
|
|
|
|
|
|
nås nå i tillegg via headerens dialog med en ny `?tab=manage`-URL-
|
|
|
|
|
|
parameter), Scorekort (`round-scorecard.tsx`+`round-stats.tsx` slått
|
|
|
|
|
|
sammen til ÉN side via en ny `embedded`-prop på begge -- statistikk
|
|
|
|
|
|
RETT UNDER scorekortet, gammel `/stats`-rute er nå en redirect dit),
|
|
|
|
|
|
Leaderboard.
|
|
|
|
|
|
**Reelt funn og fiks under integrering:** to `<main>`-landemerker på
|
|
|
|
|
|
samme side etter sammenslåingen (ugyldig HTML/a11y) -- rettet med en
|
|
|
|
|
|
`ContentTag`-switch (`div` når embedded) i `round-stats.tsx`. Fjernet
|
|
|
|
|
|
nå overflødige kryss-lenker, kollapset "Se scorekort"/"Se full
|
|
|
|
|
|
rundestatistikk" til én knapp i `round-detail.tsx` sitt fullført-
|
|
|
|
|
|
banner.
|
|
|
|
|
|
**Verifisert grundig i isolert scratch:** full produksjonsbuild, full
|
|
|
|
|
|
nettleser-gjennomkjøring av hele "Administrer"-flyten -- headeren
|
|
|
|
|
|
konsistent på tvers av alle tre fanene, dialogens lenke til "Spillere
|
|
|
|
|
|
og runde" bekreftet å faktisk åpne riktig intern fane, ekte "Fullfør
|
|
|
|
|
|
runde" (bekreftet `completed_at` satt via API etterpå) og ekte
|
|
|
|
|
|
to-stegs "Slett runde" (bekreftet navigerte korrekt til tom-tilstand
|
|
|
|
|
|
etterpå). Traff en Turbopack-flakighet i scratch-dev-serveren
|
|
|
|
|
|
underveis (stuck spinner) -- løst med frisk containerstart, bekreftet
|
|
|
|
|
|
IKKE en kodefeil.
|
|
|
|
|
|
**Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
|
|
|
|
|
|
også `teecup_api` som vanlig bivirkning). Begge containere boot-et
|
|
|
|
|
|
rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
**Bevisst IKKE dekket:** Beslutning E sitt spørsmål om lag-
|
|
|
|
|
|
gruppering trenger mer enn celle-fargelegging (sortering i seg selv
|
|
|
|
|
|
ble fikset i steg 2) -- prompt 2 ble bevisst avgrenset til kun
|
|
|
|
|
|
header-chrome-en, dette spørsmålet er derfor fortsatt åpent, ikke
|
|
|
|
|
|
stilt til V0 ennå. Se FEATURE_BACKLOG.md.
|
|
|
|
|
|
|
|
|
|
|
|
- **Rundeoppsett-veiviseren: dato/klokkeslett forhåndsutfylt, LIVE
|
|
|
|
|
|
(2026-08-02), samme dag:** brukeren ba om at dagens dato og gjeldende
|
|
|
|
|
|
klokkeslett skal være default når man setter opp en runde, i stedet
|
|
|
|
|
|
for tomme felt. `frontend/components/new-round.tsx` sitt Steg 1 fikk
|
|
|
|
|
|
to nye lokale hjelpefunksjoner (`todayIso()` -- lokal tidssone, ikke
|
|
|
|
|
|
UTC, portert fra en tidligere versjon av filen, samme presisjonshensyn
|
|
|
|
|
|
som `<input type="date">` alltid har krevd i dette prosjektet;
|
|
|
|
|
|
`nowTimeString()`) — `date`/`time`-state initialiseres nå med disse i
|
|
|
|
|
|
stedet for tomme strenger (dato respekterer fortsatt `prefillPlayedAt`
|
|
|
|
|
|
fra "legg til en flight til"-flyten der den er satt). Browserverifisert
|
|
|
|
|
|
i isolert scratch: begge feltene viste korrekt dagens dato/klokkeslett
|
|
|
|
|
|
ved første besøk til Steg 1s felt-visning. Rullet ut live, ingen
|
|
|
|
|
|
migrasjon, kun `docker compose up -d --build teecup_frontend`, `/health`/
|
|
|
|
|
|
`/my-rounds/new` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
- **Grønnfarge-vasken fjernet på tvers av hele appen, BYGGET, SCRATCH-/
|
|
|
|
|
|
BROWSERVERIFISERT OG LIVE (2026-08-02), samme dag:** brukeren delte et
|
|
|
|
|
|
Stitch-referansebilde og et skjermbilde av `/my-rounds/new` og spurte
|
|
|
|
|
|
hvorfor TeeCup fortsatt virket "fast i det lysegrønne utseendet" til
|
|
|
|
|
|
tross for at Stitch-referansen tydelig bare bruker grønt to steder
|
|
|
|
|
|
(valgt tilstand + CTA). Diagnostisert presist FØR noe ble endret (ikke
|
|
|
|
|
|
gjettet): `--primary` (den mettede merkevaregrønnen) var riktig, men
|
|
|
|
|
|
(1) de "nøytrale" tokenene (`--background`/`--muted`/`--accent`/
|
|
|
|
|
|
`--border`/`--secondary`) hadde alle en svak grønn hue (130-145)
|
|
|
|
|
|
iblandet i `globals.css` selv når de skulle være ren grå -- ga et
|
|
|
|
|
|
vedvarende grønt skjær på nesten hver bakgrunn/hover/kant i hele appen;
|
|
|
|
|
|
og (2) et helt separat, mye mer synlig problem: et `bg-primary/15
|
|
|
|
|
|
text-primary`-ikonchip-/avatar-/pille-mønster var brukt UBETINGET
|
|
|
|
|
|
(uavhengig av valgt/aktiv-tilstand) i over 30 komponentfiler --
|
|
|
|
|
|
`grep -rl "bg-primary/(5|10|15|20)|bg-accent/"` traff nesten hele
|
|
|
|
|
|
`components/`-mappen.
|
|
|
|
|
|
**To lag fikset, med bevisst avgrensning:**
|
|
|
|
|
|
1. **Token-nivå** (`frontend/app/globals.css`, `:root`/`.dark`/
|
|
|
|
|
|
`@media (prefers-color-scheme: dark)`, alle tre synkronisert):
|
|
|
|
|
|
nøytrale tokens satt til ekte `chroma 0` (f.eks. `--background:
|
|
|
|
|
|
oklch(0.985 0.005 130)` → `oklch(0.985 0 0)`). `--primary`/
|
|
|
|
|
|
`--brand-orange`/`--destructive`/`--ring`/`--info`/`--gold`/
|
|
|
|
|
|
`chart-1..6` UENDRET -- kun de tokenene som skulle vært "nøytral
|
|
|
|
|
|
grå" ble rettet, ikke merkevarefargene. `DESIGN_SYSTEM.md` sin
|
|
|
|
|
|
token-tabell + en ny historikk-note oppdatert til å reflektere dette.
|
|
|
|
|
|
2. **Ikonchip-sveip** (~17 filer, ~35 enkeltsteder, gjennomgått ett og
|
|
|
|
|
|
ett med `grep -B1 -A3` for kontekst FØR endring, ikke et blindt
|
|
|
|
|
|
sed-sveip over hele treet): `bg-primary/{5,10,12,15,20}` +
|
|
|
|
|
|
`text-primary` byttet til `bg-muted`/`text-muted-foreground` KUN der
|
|
|
|
|
|
mønsteret var ubetinget (samme farge uansett tilstand) --
|
|
|
|
|
|
seksjonshode-ikoner (`account-settings.tsx` × 7, `two-factor-
|
|
|
|
|
|
flow.tsx` × 3, `tournament-program.tsx` × 2, m.fl.), avatar-/
|
|
|
|
|
|
initial-chips (`new-round.tsx` sin `Avatar`, `friend-profile.tsx`,
|
|
|
|
|
|
`friends.tsx`, `round-detail.tsx` sitt medspiller-søk), tomtilstand-
|
|
|
|
|
|
ikoner (`course-rounds.tsx`, `own-rounds.tsx`, `rounds-stats-
|
|
|
|
|
|
summary.tsx`), kategori-piller (`friends.tsx`), og en gjennomgående
|
|
|
|
|
|
navigasjons-ikonchip (`round-header.tsx`, vises på ALLE tre
|
|
|
|
|
|
rundeskjermene). **Bevisst latt urørt** der grønt faktisk BÆRER
|
|
|
|
|
|
mening (dokumentert i `DESIGN_SYSTEM.md`s "primær = valgt/aktiv,
|
|
|
|
|
|
bekreftet"-regel): ternary-baserte valgt-/aktiv-tilstander (`Choice
|
|
|
|
|
|
Card`, `Pill`, kategori-avkrysning), "bekreftet"-tilstander
|
|
|
|
|
|
(`public-tournament.tsx` sin "Du er påmeldt!"-status,
|
|
|
|
|
|
`verify-form.tsx`/`verify-email-form.tsx` sine suksess-skjermer),
|
|
|
|
|
|
territorium-baren (match-lederskap, dokumentert bespoke mønster),
|
|
|
|
|
|
GIR-kompassets bevisste sentercelle, scorekortets "hero-rad"
|
|
|
|
|
|
(kommentert i koden som en bevisst fremhevet rad), og "Valgt
|
|
|
|
|
|
bane"-bekreftelsesbanneret i `new-round.tsx` (bekrefter et fullført
|
|
|
|
|
|
valg, samme semantikk som en "confirmed"-tilstand).
|
|
|
|
|
|
**Scratch-verifisert grundig, to browserrunder** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
|
|
|
|
|
container + full produksjonsbuild av frontend, samme mønster som hele
|
|
|
|
|
|
prosjektet): ekte innlogging, profil-fullføring, og skjermbilder av
|
|
|
|
|
|
nøyaktig samme skjerm brukeren viste (`/my-rounds/new`), pluss
|
|
|
|
|
|
dashbord og `/my-friends` -- bekreftet ikonchips nå nøytral grå,
|
|
|
|
|
|
kortbakgrunner hvite, sidebakgrunn ekte grå, grønt kun på
|
|
|
|
|
|
steg-indikatoren/wordmarket/CTA-knappen. Ingen konsollfeil. Ekte
|
|
|
|
|
|
typesjekket produksjonsbuild kompilerte rent begge runder.
|
|
|
|
|
|
**Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt (etter at
|
|
|
|
|
|
skjermbildene ikke lot seg vise i klienten -- bekreftet muntlig i
|
|
|
|
|
|
stedet: "implementer endringen"): ingen migrasjon, ren frontend-
|
|
|
|
|
|
endring, `docker compose up -d --build teecup_frontend` (gjenskapte
|
|
|
|
|
|
også `teecup_api` som vanlig bivirkning, ingen backend-kode rørt).
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket.
|
|
|
|
|
|
- **Den EGENTLIGE gjenværende grønnfarge-kilden funnet og fikset,
|
|
|
|
|
|
BROWSERVERIFISERT AV BRUKER SELV, RULLET UT LIVE (2026-08-02), samme
|
|
|
|
|
|
dag:** brukeren fortsatte å se `#F7FAF8` som sidebakgrunn på ekte
|
|
|
|
|
|
produksjon selv etter forrige runde -- viste ekte DOM-inspeksjon fra en
|
|
|
|
|
|
nettleser som aldri hadde vært brukt før (utelukket cache/service
|
|
|
|
|
|
worker som årsak). Diagnostisert presist (ikke gjettet): `--background`
|
|
|
|
|
|
i `globals.css` var faktisk korrekt (`#fff`, bekreftet direkte mot den
|
|
|
|
|
|
servert CSS-bunten via `curl`), men `dashboard.tsx` og `more-menu.tsx`
|
|
|
|
|
|
hadde sin EGEN, LÅSTE JS-fargepalett (`const C = {...}`, fra "Forest
|
|
|
|
|
|
Green"-redesignet 2026-08-01) med `bg: "#f7faf8"`, satt via `style={{
|
|
|
|
|
|
backgroundColor: C.bg }}` på sidens ytterste wrapper/header/loading-
|
|
|
|
|
|
spinner -- en INLINE STYLE bypasser CSS custom properties helt, uansett
|
|
|
|
|
|
hva `globals.css` sier. Dette brøt `DESIGN_SYSTEM.md` sin egen,
|
|
|
|
|
|
eksisterende regel ("ALDRI hardkodede farger... bryter automatisk lys/
|
|
|
|
|
|
mørk-tema-logikk") -- introdusert av meg selv i en tidligere runde uten
|
|
|
|
|
|
å fange bruddet da.
|
|
|
|
|
|
**Fikset:** `bg: "#f7faf8"` fjernet fra `const C` i BEGGE filer, alle
|
|
|
|
|
|
fire `style={{ backgroundColor: C.bg }}`-stedene (dashboard.tsx sin
|
|
|
|
|
|
loading-spinner/hovedwrapper/header, more-menu.tsx sin hovedwrapper)
|
|
|
|
|
|
erstattet med Tailwind-klassen `bg-background` (nå faktisk `#fff`,
|
|
|
|
|
|
forrige rundes fiks) -- ren fjerning, ikke bare en ny hex-verdi, slik at
|
|
|
|
|
|
en FREMTIDIG token-endring automatisk forplanter seg hit også. Et femte
|
|
|
|
|
|
sted (en varselboble sin `boxShadow`-"utskjærings"-ring mot side-
|
|
|
|
|
|
bakgrunnen) endret fra `${C.bg}` til `var(--background)` -- kan ikke
|
|
|
|
|
|
bruke en Tailwind-klasse inni en inline `boxShadow`-streng, men
|
|
|
|
|
|
`var(--background)` holder den fortsatt koblet til token-systemet
|
|
|
|
|
|
fremfor en ny hardkodet verdi. `login-form.tsx`/`round-stats.tsx` sine
|
|
|
|
|
|
egne `const C`-paletter sjekket og bekreftet IKKE berørt (ingen egen
|
|
|
|
|
|
`bg`-bakgrunnsverdi der -- `round-stats.tsx` sin er allerede `var(--
|
|
|
|
|
|
chart-N)`-referanser, riktig fra før).
|
|
|
|
|
|
**Verifisert presist, denne gangen mot selve det bygde bunten, ikke
|
|
|
|
|
|
bare kildekoden:** `grep -rl f7faf8` mot HELE `.next/static`-mappen
|
|
|
|
|
|
INNI det faktiske produksjonsimaget (`docker run --rm --entrypoint sh
|
|
|
|
|
|
teecup-teecup_frontend ... grep`) ga null treff -- streng-nivå-bevis at
|
|
|
|
|
|
verdien er borte fra det som faktisk sendes til nettleseren, ikke bare
|
|
|
|
|
|
fra kilden. Ekte typesjekket build kompilerte rent.
|
|
|
|
|
|
**Rullet ut live 2026-08-02**, bruker bekreftet eksplisitt (viste
|
|
|
|
|
|
konkret DOM-bevis, ba om fiks): ingen migrasjon, `docker compose up -d
|
|
|
|
|
|
--build teecup_frontend` (gjenskapte også `teecup_api` som vanlig
|
|
|
|
|
|
bivirkning). Begge containere boot-et rent, `/health`/`/dashboard`/
|
|
|
|
|
|
`/more` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
**Lærdom, notert eksplisitt for fremtidige runder:** når en visuell
|
|
|
|
|
|
fiks angivelig er utført men brukeren fortsatt ser feil farge, sjekk
|
|
|
|
|
|
ALLTID for hardkodede inline `style={{...}}`-verdier og låste lokale
|
|
|
|
|
|
JS-fargepaletter (`const C = {...}`, kjent mønster fra "Forest Green"-
|
|
|
|
|
|
arbeidet) FØR man antar det er nettleser-cache -- CSS-token-nivå-
|
|
|
|
|
|
verifisering alene (kun `globals.css`/den kompilerte CSS-bunten) er
|
|
|
|
|
|
IKKE tilstrekkelig bevis når V0-avledede skjermer kan ha sin egen,
|
|
|
|
|
|
parallelle fargekilde som omgår token-systemet helt.
|
|
|
|
|
|
- **CLAUDE.md splittet i regler + historikk (2026-08-02):** filen hadde
|
|
|
|
|
|
vokst til 6500+ linjer og ble injisert i sin helhet i konteksten hver
|
|
|
|
|
|
eneste forespørsel -- brukeren foreslo en konkret splitt (invarianter i
|
|
|
|
|
|
CLAUDE.md, kronologisk historikk i en egen fil), som ble vurdert å
|
|
|
|
|
|
genuint hjelpe (mindre kontekstforbruk per runde, mindre risiko for at
|
|
|
|
|
|
de faktiske reglene drukner i historien). Hele "Status"-seksjonen
|
|
|
|
|
|
flyttet til denne filen (`CHANGELOG.md`, ny), verifisert byte-for-byte
|
|
|
|
|
|
identisk med originalen via `diff` FØR noe ble slettet. CLAUDE.md
|
|
|
|
|
|
redusert til ~117 linjer (autoritative kilder, sikkerhetsregler,
|
|
|
|
|
|
arkitektur-invarianter, tilgjengelighet, navneformat, arbeidsmåte) +
|
|
|
|
|
|
en pekende seksjon til denne filen. 47 nå-utdaterte
|
|
|
|
|
|
`CLAUDE.md-status`/`CLAUDE.md sin statuslogg`-kryssreferanser rettet
|
|
|
|
|
|
til `CHANGELOG.md` på tvers av `ARCHITECTURE_DECISIONS.md`/
|
|
|
|
|
|
`FEATURE_BACKLOG.md`/`DESIGN_SYSTEM.md`. Ren dokumentasjonsendring,
|
|
|
|
|
|
ingen kode/migrasjon/utrulling.
|
|
|
|
|
|
- **"Bruk course handicap-justering" skjult for ordinære formater, LIVE
|
|
|
|
|
|
(2026-08-02), samme dag:** brukeren påpekte at bryteren er unødvendig
|
|
|
|
|
|
for rene Slagspill-/Stableford-runder — bedt om å skjules (ikke
|
|
|
|
|
|
slettes) og alltid holdes funksjonelt PÅ for disse to formatene.
|
|
|
|
|
|
`frontend/components/new-round.tsx`: ny `ORDINARY_FORMATS = new
|
|
|
|
|
|
Set(["slagspill", "stableford"])`, en `useEffect` i `NewRound` som
|
|
|
|
|
|
tvinger `useCourseHcp` tilbake til `true` hver gang `format` går inn i
|
|
|
|
|
|
dette settet (dekker et bytte FRA et annet format der bryteren var
|
|
|
|
|
|
slått av), og selve `ToggleRow` for "Bruk course handicap-justering"
|
|
|
|
|
|
gatet bort i `Step2` for disse to formatene. Ren UI-/state-endring —
|
|
|
|
|
|
`buildAllowanceOverride()` er urørt, returnerer fortsatt `null` (ingen
|
|
|
|
|
|
override sendt) når alt er på standardverdi, akkurat som før. Ekte
|
|
|
|
|
|
typesjekket build kompilerte rent. **Rullet ut live**, ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_frontend` (gjenskapte
|
|
|
|
|
|
også `teecup_api` som vanlig bivirkning). Begge containere boot-et
|
|
|
|
|
|
rent, `/health`/`/my-rounds/new` → 200, `teeoff.no` upåvirket.
|
|
|
|
|
|
- **"HCP-prosent"-feltet skjult når "Bruk handicap" er av, LIVE
|
|
|
|
|
|
(2026-08-02), samme dag, oppfølging av forrige punkt:** brukeren
|
|
|
|
|
|
påpekte at feltet ikke ga mening synlig når HCP uansett er slått av,
|
|
|
|
|
|
og presiserte eksplisitt at dette skal gjelde universelt (alle
|
|
|
|
|
|
formater), ikke bare de to ordinære. Løst med én eneste betingelse
|
|
|
|
|
|
(`{useHcp && (...)}`) rundt feltet i `Step2` — siden ALLE spilleformer
|
|
|
|
|
|
deler nøyaktig denne ene komponenten for "Avanserte handicap-
|
|
|
|
|
|
innstillinger", dekker denne ene endringen automatisk hver
|
|
|
|
|
|
spilleform-visning uten noen per-format-liste (ulikt forrige punkts
|
|
|
|
|
|
`ORDINARY_FORMATS`-gating, som var format-spesifikk med hensikt).
|
|
|
|
|
|
Bevisst IKKE nullstilt `hcpPercent`-state når feltet skjules — en
|
|
|
|
|
|
eventuell gjenværende verdi er funksjonelt harmløs (backend sin
|
|
|
|
|
|
`compute_and_store_side_handicaps` returnerer tidlig når
|
|
|
|
|
|
`use_handicap` er usann, FØR `strategy`/prosent leses i det hele
|
|
|
|
|
|
tatt), så ingen ekstra state-rydding var nødvendig utover selve
|
|
|
|
|
|
skjulingen. Ekte typesjekket build kompilerte rent. **Rullet ut
|
|
|
|
|
|
live**, ingen migrasjon, `docker compose up -d --build
|
|
|
|
|
|
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
|
|
|
|
|
- **Reell inkonsistens funnet av bruker og fikset, LIVE (2026-08-02),
|
|
|
|
|
|
samme dag:** brukeren viste et skjermbilde av Fourball med "Bruk
|
|
|
|
|
|
handicap" AV, men "Bruk course handicap-justering" OG "Bruk
|
|
|
|
|
|
matchplay-handicap" fortsatt vist som PÅ — begge sub-bryterne var kun
|
|
|
|
|
|
gatet på format (`ORDINARY_FORMATS`/`twoSided`), ALDRI på selve
|
|
|
|
|
|
`useHcp`-hovedbryteren. Ingen funksjonell bug (backend sin
|
|
|
|
|
|
`compute_and_store_side_handicaps` returnerer tidlig når
|
|
|
|
|
|
`use_handicap` er usann, FØR disse leses — samme resonnement som
|
|
|
|
|
|
forrige punkts `hcpPercent`), men en reell visuell selvmotsigelse.
|
|
|
|
|
|
Fikset ved å legge til `useHcp &&` foran begge betingelsene i `Step2`
|
|
|
|
|
|
(`frontend/components/new-round.tsx`) — siden ALLE 16 formater deler
|
|
|
|
|
|
denne ene komponenten, dekker denne ene endringen konsistent
|
|
|
|
|
|
atferd for hver spilleform uten en per-format-sjekk. Når "Bruk
|
|
|
|
|
|
handicap" er av, vises nå KUN selve hovedbryteren i "Avanserte
|
|
|
|
|
|
handicap-innstillinger" — ingen sub-innstillinger, uansett format.
|
|
|
|
|
|
**Samme bug funnet i en parallell, ikke-forespurt fil under
|
|
|
|
|
|
undersøkelsen** (`tournament-program.tsx` sin `CreateSessionCard`,
|
|
|
|
|
|
org-turnering-øktoppsettet) — samme tre brytere, samme mangel på
|
|
|
|
|
|
`useHcp`-gating, og der attpåtil UTEN `twoSided`-gating på
|
|
|
|
|
|
"Bruk matchplay-handicap" i det hele tatt (vises alltid, uavhengig av
|
|
|
|
|
|
format). Flagget til bruker, IKKE fikset i denne runden (egen fil,
|
|
|
|
|
|
ikke det som ble spurt om). Ekte typesjekket build kompilerte rent.
|
|
|
|
|
|
**Rullet ut live**, ingen migrasjon, `docker compose up -d --build
|
|
|
|
|
|
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
|
|
|
|
|
- **Samme bug fikset i tournament-program.tsx (org-turneringens
|
|
|
|
|
|
økt-oppsett), LIVE (2026-08-02), samme dag:** bruker bekreftet at den
|
|
|
|
|
|
flaggede parallellfilen også skulle rettes. Enklere fiks enn i
|
|
|
|
|
|
new-round.tsx: `SessionFormat` her har KUN 10 to-sidede lag-format
|
|
|
|
|
|
(foursome/greensome/scramble_2/scramble_4/fourball/singles/chapman/
|
|
|
|
|
|
shamble/money_ball/high_low_high) — org-lagturneringer har ingen flat
|
|
|
|
|
|
individuell-formatvariant (den hører hjemme i den separate
|
|
|
|
|
|
individuell-turnering-modellen, ADR-037), så det finnes ingen
|
|
|
|
|
|
`ORDINARY_FORMATS`/`twoSided`-distinksjon å ta hensyn til her, ulikt
|
|
|
|
|
|
den andre filen. Løsning: alle tre (`Bruk course handicap-justering`,
|
|
|
|
|
|
`Bruk matchplay-handicap`, `HCP-prosent`) samlet i én
|
|
|
|
|
|
`{useHandicap && (<>...</>)}`-blokk i `CreateSessionCard`
|
|
|
|
|
|
(`tournament-program.tsx`) — bekreftet at dette er ENESTE stedet i
|
|
|
|
|
|
filen disse tre bryterne finnes (ingen separat rediger-økt-variant med
|
|
|
|
|
|
samme mangel). Ekte typesjekket build kompilerte rent. **Rullet ut
|
|
|
|
|
|
live**, ingen migrasjon, `docker compose up -d --build
|
|
|
|
|
|
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/dashboard` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
|
|
|
|
|
- **Reell Tailwind-cascade-bug funnet og fikset, BROWSERVERIFISERT MED
|
|
|
|
|
|
PRESISE DOM-MÅLINGER, LIVE (2026-08-02), samme dag:** brukeren
|
|
|
|
|
|
rapporterte at siden ikke lot seg scrolle helt ned på Steg 2 i
|
|
|
|
|
|
rundeoppsett-veiviseren, med et skjermbilde som kuttet av rett før
|
|
|
|
|
|
"Antall hull"-knappene. IKKE en scroll-bug -- root cause presist
|
|
|
|
|
|
diagnostisert i en isolert scratch-nettleserøkt (samme mønster som
|
|
|
|
|
|
resten av uken): `main`s className hadde `pb-32` (128px, reservert
|
|
|
|
|
|
klaring for den faste Tilbake/Neste-linjen) OG `sm:py-8` (32px,
|
|
|
|
|
|
ment for generell luft på større skjermer) samtidig. Tailwind
|
|
|
|
|
|
emitterer responsive (`sm:`)-varianter i en EGEN media-blokk ETTER
|
|
|
|
|
|
grunnklassene i den kompilerte CSS-en -- så `sm:py-8` vant cascaden
|
|
|
|
|
|
over `pb-32` på ALLE skjermer 640px og bredere, UANSETT rekkefølge i
|
|
|
|
|
|
selve className-strengen. Bekreftet presist med `getComputedStyle`:
|
|
|
|
|
|
`padding-bottom` var faktisk 32px, ikke 128px, ved en bred viewport
|
|
|
|
|
|
(1998px) -- mens en smal mobilviewport (390px, under `sm:`-grensen)
|
|
|
|
|
|
fortsatt fikk riktige 128px, noe som forklarer hvorfor bugen var usett
|
|
|
|
|
|
til nå (all tidligere browserverifisering denne uken har vært på
|
|
|
|
|
|
390px). Effekten: ved bredder ≥640px der totalt innhold tilfeldigvis
|
|
|
|
|
|
var kortere enn viewporten, fikk siden RETT OG SLETT ikke lov til å
|
|
|
|
|
|
scrolle langt nok til å avdekke "Antall hull"-knappene fullt ut --
|
|
|
|
|
|
den faste navigasjonslinjen dekket de nederste ~58 av 64 pikslene.
|
|
|
|
|
|
**Fikset** ved å dele opp de vertikale paddingene til KUN topp
|
|
|
|
|
|
(`py-6`→`pt-6`, `sm:py-8`→`sm:pt-8`) slik at ingenting lenger kan
|
|
|
|
|
|
konkurrere med `pb-32` om `padding-bottom` uansett skjermbredde --
|
|
|
|
|
|
`pb-32` er nå den ENESTE kilden til bunn-klaring. Grep'et gjennom HELE
|
|
|
|
|
|
frontend-treet etter samme `pb-N`+`sm:py-N`-mønster -- kun dette ene
|
|
|
|
|
|
stedet (`new-round.tsx`), ingen andre skjermer rammet.
|
|
|
|
|
|
**Verifisert presist, ikke bare "ser bedre ut":** eksakte
|
|
|
|
|
|
`getBoundingClientRect()`/`getComputedStyle()`-målinger FØR fiksen
|
|
|
|
|
|
(padding-bottom 32px, "18 hull"-knappen fra y=1378 til y=1442, fast
|
|
|
|
|
|
navigasjon fra y=1384 -- 58px reell overlapp bekreftet med tall, ikke
|
|
|
|
|
|
bare visuelt) og ETTER (padding-bottom 128px, full synlig klaring,
|
|
|
|
|
|
scrollHeight økte fra 1474 til 1570px -- nøyaktig de manglende 96px
|
|
|
|
|
|
= 128-32). Regresjonssjekket på 390px mobilviewport (uendret riktig
|
|
|
|
|
|
oppførsel, som allerede fungerte). Ekte typesjekket build kompilerte
|
|
|
|
|
|
rent (måtte rette en selvpåført JSX-kommentar-bug underveis -- en
|
|
|
|
|
|
bokstavelig `*/`-sekvens inni kommentarteksten min egen selv lukket
|
|
|
|
|
|
kommentaren for tidlig, rettet ved å omformulere).
|
|
|
|
|
|
**Rullet ut live**, ingen migrasjon, `docker compose up -d --build
|
|
|
|
|
|
teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning).
|
|
|
|
|
|
Begge containere boot-et rent, `/health`/`/my-rounds/new` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket.
|
2026-08-03 06:29:07 +02:00
|
|
|
|
- **Midlertidige spillere (gjester på frittstående runder): full runde
|
|
|
|
|
|
ferdig -- e-post, navnesplitt, retroaktiv kobling, autofyll, "gjenkjenn
|
|
|
|
|
|
gjest"-oppslag, BYGGET, GRUNDIG SCRATCH-/BROWSERVERIFISERT OG LIVE
|
|
|
|
|
|
(2026-08-03):** brukeren ba om seks ting samtidig (e-post som valgfritt
|
|
|
|
|
|
felt manglet, navnesplitt for-/etternavn, retroaktiv lagring/kobling av
|
|
|
|
|
|
gjeste-runder til en fremtidig konto, automatisk e-post med scorekort+
|
|
|
|
|
|
statistikk+invitasjon ved fullføring, autofyll fra søkefeltet inn i
|
|
|
|
|
|
gjesteskjemaet, og "gjenkjenn en tidligere registrert gjeste-e-post") og
|
|
|
|
|
|
ba eksplisitt om innspill på hva mer som var lurt. Grundig
|
|
|
|
|
|
kodeutforskning FØR noe ble bygget avdekket presist hva som faktisk
|
|
|
|
|
|
manglet: `guest_email` fantes ALLEREDE i backend (2026-07-26), bare
|
|
|
|
|
|
aldri eksponert i selve "legg til gjest"-skjemaet; `guest_name` var ETT
|
|
|
|
|
|
enkelt tekstfelt brukt i 10+ spørringer.
|
|
|
|
|
|
**Tre load-bærende avklaringer bekreftet av bruker** (AskUserQuestion):
|
|
|
|
|
|
(A) en retroaktivt koblet runde (noen registrerte deg som gjest FØR du
|
|
|
|
|
|
hadde konto) teller IKKE automatisk mot faktisk HCP -- `exclude_from_
|
|
|
|
|
|
handicap` settes til `true` som default ved kobling, personen må selv
|
|
|
|
|
|
slå den på (samme mekanisme ADR-038 allerede bygget, kun default
|
|
|
|
|
|
snudd for denne ene banen inn). Begrunnelse: en org-turnering sin
|
|
|
|
|
|
eksisterende `link_player_by_email`-presedens (ADR-017) er trygg fordi
|
|
|
|
|
|
org-scoring aldri teller mot faktisk HCP -- frittstående runder GJØR
|
|
|
|
|
|
det (ADR-038), så blind auto-inkludering ville latt en fremmed
|
|
|
|
|
|
påvirke noens HCP uten samtykke. (B) treffer en gjeste-e-post en
|
|
|
|
|
|
EKSISTERENDE konto, opprettes gjesten likevel -- kobles ved neste
|
|
|
|
|
|
innlogging, ikke et eget "denne personen har konto"-forgreiningssteg
|
|
|
|
|
|
(enklere, bevisst valgt fremfor det opprinnelig anbefalte). (C) bygget
|
|
|
|
|
|
i én samlet runde (skjema→backend→frontend→e-post lagvis, samme
|
|
|
|
|
|
disiplin som ellers i prosjektet).
|
|
|
|
|
|
**Migrasjon `052_guest_name_split.sql`:** `round_participant.
|
|
|
|
|
|
guest_first_name`/`guest_last_name` lagt til, `guest_name` beholdt
|
|
|
|
|
|
UENDRET som et auto-synkronisert, lagret "fullt navn" (samme mønster
|
|
|
|
|
|
som `app_user.display_name` synkes fra `first_name`/`last_name`,
|
|
|
|
|
|
2026-07-25-bugfiksen) -- unngikk å måtte røre alle eksisterende
|
|
|
|
|
|
SELECT-steder. Backfill: "første ord = fornavn, resten = etternavn".
|
|
|
|
|
|
**Retroaktiv kobling** (`app/routers/auth.py`, ny
|
|
|
|
|
|
`_link_round_participants_by_email`, kalt fra BEGGE innloggingsveiene
|
|
|
|
|
|
-- magic-link og passord, samme "kjør trygt på hver innlogging"-
|
|
|
|
|
|
idempotens som `link_player_by_email`): INGEN SECURITY DEFINER-bro
|
|
|
|
|
|
trengs siden `round`/`round_participant` ikke har RLS (ADR-033
|
|
|
|
|
|
Beslutning A) -- en rett UPDATE er nok. `NOT EXISTS`-vaktet mot en
|
|
|
|
|
|
allerede eksisterende ekte deltaker-rad på samme runde (ville ellers
|
|
|
|
|
|
brutt migrasjon 027 sin UNIQUE-indeks). **Reell bug funnet OG fikset
|
|
|
|
|
|
UNDER scratch-testing:** en første versjon nullet `gender` sammen med
|
|
|
|
|
|
de andre gjeste-feltene ved kobling -- krasjet med en NOT NULL-
|
|
|
|
|
|
violation, siden `gender` er en påkrevd snapshot for ALLE deltakere
|
|
|
|
|
|
(brukt til utslags-rating-oppslag), ikke bare gjester. Rettet ved å la
|
|
|
|
|
|
`gender` stå urørt (uansett låst mot videre endring av `update_
|
|
|
|
|
|
participant` sin egen `guest_only_fields`-sjekk så snart `user_id` er
|
|
|
|
|
|
satt).
|
|
|
|
|
|
**HTML-e-post** (`app/email.py`, FØRSTE i appen): `_send_sync` fikk en
|
|
|
|
|
|
valgfri `html_body`-parameter (`EmailMessage.add_alternative`,
|
|
|
|
|
|
multipart/alternative -- ekte tekst-fallback bevart). Ny
|
|
|
|
|
|
`send_round_summary_email` -- scorekort som HTML-tabell, statistikk
|
|
|
|
|
|
gradert etter `stat_level`/individuell- vs. delt-ball (kalleren bygger
|
|
|
|
|
|
`stat_lines`, e-post-modulen antar ingenting), ekte magic-link-
|
|
|
|
|
|
innlogging (samme token-mønster som `send_scorecard_invitations`,
|
|
|
|
|
|
ADR/CLAUDE.md 2026-07-28). **Navneformat (ufravikelig regel):**
|
|
|
|
|
|
hilsenen er "Hei {fornavn}," -- direkte adressering, kun fornavn, aldri
|
|
|
|
|
|
fullt navn. Trigget fra en ny `_send_guest_round_summaries`, kalt fra
|
|
|
|
|
|
`complete_round` for hver deltaker med `guest_email` satt -- bygger
|
|
|
|
|
|
scorekortet fra ENTEN `_build_participant_holes` (individuell) ELLER
|
|
|
|
|
|
`_build_side_holes` (delt-ball, ADR-039), gjenbruker eksisterende
|
|
|
|
|
|
hjelpefunksjoner uendret, ingen egen regnelogikk.
|
|
|
|
|
|
**Known-guest-oppslag** (`GET /rounds/guests/known`, statisk rute
|
|
|
|
|
|
plassert FØR `/rounds/{round_id}` i filen, samme mønster som
|
|
|
|
|
|
`/rounds/stats/summary`): bevisst scoped til KUN den spørrende
|
|
|
|
|
|
brukerens EGNE tidligere registrerte gjester (`WHERE r.owner_user_id =
|
|
|
|
|
|
$1`) -- et globalt oppslag ville latt hvem som helst skrive inn en
|
|
|
|
|
|
tilfeldig e-post og se navn/kjønn/HCP en HELT ANNEN organisator har
|
|
|
|
|
|
registrert, en reell personvernlekkasje unngått FØR bygging (flagget
|
|
|
|
|
|
proaktivt til bruker, bekreftet enig).
|
|
|
|
|
|
**Frontend** (`round-detail.tsx`): `AddGuestForm` skrevet om -- separate
|
|
|
|
|
|
Fornavn/Etternavn-felt (etternavn valgfritt), nytt E-post-felt med
|
|
|
|
|
|
debounced known-guest-oppslag + en "Bruk disse opplysningene"-forslags-
|
|
|
|
|
|
boks, `initialName`-prop fra søkefeltet (`AddParticipantForm`s `query`)
|
|
|
|
|
|
autofyller navnefeltene ved "Legg til uten konto (gjest)" via en ny
|
|
|
|
|
|
`splitName()`-heuristikk (samme "første ord/resten" som backfillen).
|
|
|
|
|
|
`EditParticipantPanel` oppdatert til samme to-felts navnestruktur (var
|
|
|
|
|
|
i ferd med å bli en regresjon siden backend ikke lenger godtar
|
|
|
|
|
|
`guest_name` direkte). **Reelt, uforespurt funn fanget under
|
|
|
|
|
|
implementering:** rundeoppsett-veiviseren (`new-round.tsx`) sender
|
|
|
|
|
|
OGSÅ `guest_name` ved opprettelse av spillere i Steg 3 -- ville brutt
|
|
|
|
|
|
hvis ikke oppdatert samtidig; løst med samme splitt-heuristikk direkte
|
|
|
|
|
|
ved innsending (ingen UI-endring i wizarden denne runden, kun
|
|
|
|
|
|
kontraktsrettelse).
|
|
|
|
|
|
**Scratch-verifisert grundig, 41/41 sjekker** (isolert
|
|
|
|
|
|
`teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs API-
|
|
|
|
|
|
container, alle 52 migrasjoner kjørt friskt, samme mønster som hele
|
|
|
|
|
|
prosjektet): navnesplitt+auto-synk, delvis PATCH bevarer det andre
|
|
|
|
|
|
feltet, validering (verken/begge user_id+guest_first_name avvist),
|
|
|
|
|
|
known-guest-oppslag (funnet for egen bruker, IKKE synlig for en annen
|
|
|
|
|
|
organisator -- personvern-scopingen bekreftet presist), full
|
|
|
|
|
|
fullførings-e-post-syklus (dev-log bekreftet både magic-link OG
|
|
|
|
|
|
rundeoppsummering), retroaktiv kobling (user_id satt, gjeste-felt
|
|
|
|
|
|
nullstilt, `gender` BEVART, `exclude_from_handicap=true`, `display_
|
|
|
|
|
|
name` viser nå kontoens ekte navn), idempotent gjeninnlogging, OG et
|
|
|
|
|
|
eget delt-ball-scenario (foursome) som bekreftet ingen krasj ved
|
|
|
|
|
|
fullføring og korrekt sideoppsummering i e-post-loggen.
|
|
|
|
|
|
**Deretter en FULL, ekte nettleser-gjennomgang** (Chrome DevTools MCP):
|
|
|
|
|
|
hele "+ Medspiller"→"Legg til uten konto"-flyten klikket gjennom med et
|
|
|
|
|
|
navn som ikke fantes -- bekreftet AUTOFYLL fungerte (Fornavn/Etternavn
|
|
|
|
|
|
korrekt splittet), skrev inn en kjent gjeste-e-post og bekreftet
|
|
|
|
|
|
"Vi fant Anna fra en tidligere runde..."-forslaget dukket opp, trykket
|
|
|
|
|
|
"Bruk disse opplysningene" og bekreftet navn/kjønn ble overskrevet
|
|
|
|
|
|
korrekt (HCP forble tomt, siden Anna ikke hadde noen registrert),
|
|
|
|
|
|
sendte inn skjemaet og bekreftet gjesten dukket opp i spillerlisten
|
|
|
|
|
|
UTEN konsollfeil, åpnet rediger-panelet og bekreftet det viser samme
|
|
|
|
|
|
to-felts navnestruktur korrekt forhåndsutfylt. E-postens faktiske
|
|
|
|
|
|
HTML/tekst-innhold generert og inspisert direkte (ikke bare at
|
|
|
|
|
|
utsendingen ble trigget) -- bekreftet riktig "Hei Kari,"-hilsen,
|
|
|
|
|
|
korrekt hull-tabell inkl. et uspilt hull vist som "–", og korrekt
|
|
|
|
|
|
byggede statistikklinjer.
|
|
|
|
|
|
**Rullet ut mot ekte systemer 2026-08-03**, bruker bekreftet
|
|
|
|
|
|
eksplisitt (plan vist FØR migrasjonen, per CLAUDE.md sin ufravikelige
|
|
|
|
|
|
regel): migrasjon 052 kjørt mot ekte `teecup_db` (nye kolonner
|
|
|
|
|
|
bekreftet, backfill verifisert nøyaktig mot de 3 eksisterende gjeste-
|
|
|
|
|
|
radene i produksjon -- "Christer Heitun"→"Christer"/"Heitun", "Sigurd"
|
|
|
|
|
|
→"Sigurd"/null, "Vidar Hoksrød"→"Vidar"/"Hoksrød" -- `test_isolation.sql`
|
|
|
|
|
|
fortsatt 12/12), deretter `docker compose up -d --build teecup_api
|
|
|
|
|
|
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
|
|
|
|
|
|
→ 200, `GET /rounds/guests/known` anonymt ga korrekt `401` gjennom hele
|
|
|
|
|
|
produksjonsstacken (ikke en rå 404), `teeoff.no` upåvirket.
|
2026-08-03 14:32:48 +02:00
|
|
|
|
- **Score-fanens gjennomgang endte i to runder etter samme rapport
|
|
|
|
|
|
(2026-08-03):** bruker sendte skjermbilde av det brede
|
|
|
|
|
|
ScorecardGrid-et og påpekte at det ikke var intuitivt hvor man skal
|
|
|
|
|
|
trykke for å registrere score. Første prompt til V0 var snevert
|
|
|
|
|
|
(kun synlig trykk-affordance på tomme celler). Bruker ba om en
|
|
|
|
|
|
friere runde 2: "Kan det være mulig å presentere scoreføringsvinduet
|
|
|
|
|
|
på en helt annen måte, uten at informasjon [...] blir borte" +
|
|
|
|
|
|
"det ser generelt ikke særlig bra ut på mobil [...] tar for stor
|
|
|
|
|
|
plass i bredden. Gi V0 et friere spillerom." Ny, åpen prompt skrevet
|
|
|
|
|
|
(mål+datainventar-sjekkliste i stedet for layoutdiktat, eksplisitt
|
|
|
|
|
|
IKKE rørt ved den allerede gode fullskjerm-registreringsveiviseren
|
|
|
|
|
|
-- kun oversiktsgridet). Grundig kodeutforskning FØR prompten ble
|
|
|
|
|
|
skrevet bekreftet at `ScoringWizard` (fullskjerm-veiviser) allerede
|
|
|
|
|
|
matcher spec §2 og ikke skulle røres, og at `ScorecardGrid` (§1) sin
|
|
|
|
|
|
hver-celle-er-en-knapp-logikk allerede fungerte -- kun tomme cellers
|
|
|
|
|
|
manglende visuelle "trykk her"-signal var det faktiske problemet i
|
|
|
|
|
|
runde 1.
|
|
|
|
|
|
**V0-eksport "zip 27" mottatt og portet, LIVE (2026-08-03):**
|
|
|
|
|
|
`components/score-overview.tsx` fra zip-en var en velfungerende,
|
|
|
|
|
|
selvstendig mock-datavisning (hull-fokuserte spillerkort, dashet
|
|
|
|
|
|
"+"-affordance for uregistrerte hull, sammenleggbart fullt
|
|
|
|
|
|
scorekort, hurtig-hopp-hullstripe) som eksplisitt respekterte
|
|
|
|
|
|
omfanget -- V0 gjenkjente selv at registreringsvinduet ikke skulle
|
|
|
|
|
|
røres og mocket kun et representativt stedfortreder-vindu for det.
|
|
|
|
|
|
Portet MANUELT mot ekte data i stedet for å ta filen direkte (samme
|
|
|
|
|
|
rutine som alltid): ny `PlayerHoleCards`-komponent i
|
|
|
|
|
|
round-detail.tsx gjenbruker ekte `Player`/`ApiHole`/`ApiFormatResult`
|
|
|
|
|
|
-typene, ekte `goPrev`/`goNext` (wrap-around, ikke V0s klampede
|
|
|
|
|
|
prev/next), ekte `sumForPlayer`/`stablefordPoints`/`golfTermForScore`
|
|
|
|
|
|
-hjelpere, og EKSAKT samme match-fargelegging/ikke-tellende-partner-
|
|
|
|
|
|
logikk som `ScorecardGrid` allerede brukte (`formatResult.holes[].
|
|
|
|
|
|
entries[].counted`) -- ingen egen reimplementert kopi av denne
|
|
|
|
|
|
logikken. `ScorecardGrid` selv er UENDRET, bare flyttet til en ny
|
|
|
|
|
|
"Vis hele scorekortet"-bryter (default lukket) i stedet for å være
|
|
|
|
|
|
standardvisningen -- ingen informasjon fjernet, kun omprioritert.
|
|
|
|
|
|
`ScorecardCell` fikk en ny valgfri `size`-prop ("sm"/"lg") for å la
|
|
|
|
|
|
det store spillerkortet gjenbruke SAMME form-/fargekomponent som det
|
|
|
|
|
|
kompakte gridet, i stedet for en divergerende kopi.
|
|
|
|
|
|
**Én reell avviksfeil funnet og rettet FØR utrulling:** V0s
|
|
|
|
|
|
hurtig-hopp-hullstripe brukte `size-9` (36px) -- under CLAUDE.md sitt
|
|
|
|
|
|
ufravikelige 44px-trykkgulv. Rettet til `size-11` FØR
|
|
|
|
|
|
browserverifisering, ikke bare typesjekk.
|
|
|
|
|
|
**Scratch-/browserverifisert grundig** (isolert DB+MinIO+API+
|
|
|
|
|
|
frontend-container, samme mønster som alltid) på en ekte 390px mobil
|
|
|
|
|
|
viewport: singel-runde (registrerte hull vs. dashet "+"-affordance
|
|
|
|
|
|
på uregistrert hull, netto/Stableford-poeng, Ut/Inn/Sum),
|
|
|
|
|
|
fourball-runde (lag-farget venstrekant, "Vant hullet"-merke lest
|
|
|
|
|
|
direkte fra ekte `formatResult`, ikke tie-brutt reimplementering),
|
|
|
|
|
|
ekte trykk-gjennom til den URØRTE `ScoringWizard`-en bekreftet (skår
|
|
|
|
|
|
registrert, veiviseren avanserte automatisk til neste spiller, kortet
|
|
|
|
|
|
bak oppdaterte seg live), og "Vis hele scorekortet" bekreftet å vise
|
|
|
|
|
|
det uendrede gamle gridet korrekt. "Spillere og runde"-fanen
|
|
|
|
|
|
(urørt) verifisert uendret.
|
|
|
|
|
|
**Rullet ut live 2026-08-03**, ren frontend-endring (ingen
|
|
|
|
|
|
migrasjon), bruker bekreftet eksplisitt før utrulling. Zip-en slettet
|
|
|
|
|
|
etter merge, per etablert rutine.
|
|
|
|
|
|
**Bevisst utenfor omfang, ikke bygget:** delt-ball-formater
|
|
|
|
|
|
(foursome/greensome/scramble/chapman -- `SideScorecardGrid`) har
|
|
|
|
|
|
ingen tilsvarende kortvisning ennå -- V0-mockupen dekket kun
|
|
|
|
|
|
spiller-rad-tilfellet (`ScorecardGrid`), og det fantes ingen
|
|
|
|
|
|
tilsvarende referanse å bygge en "side-kort"-variant mot. Fortsatt
|
|
|
|
|
|
det brede gridet for de formatene, uendret.
|
2026-08-03 17:30:57 +02:00
|
|
|
|
- **Fem separate brukerpunkter fra samme økt, ALLE BYGGET, SCRATCH-
|
|
|
|
|
|
VERIFISERT OG LIVE (2026-08-03), samme dag som score-kortrunden
|
|
|
|
|
|
over:**
|
|
|
|
|
|
1. **Ekte bug funnet via opplastet skjermopptak, rettet:** brukeren
|
|
|
|
|
|
rapporterte at "snarveien" (Administrer-dialogens "Spillere og
|
|
|
|
|
|
runde"-lenke, `?tab=manage`) ikke virket fra Score-fanen, men
|
|
|
|
|
|
virket fra Scorekort/Leaderboard. Video analysert bilde for bilde
|
|
|
|
|
|
(ffmpeg) -- bekreftet presist: URL-en endret seg riktig til
|
|
|
|
|
|
`?tab=manage`, men selve fanen ble stående på "Score". Rotårsak:
|
|
|
|
|
|
`useState(initialTab)` i round-detail.tsx leste kun
|
|
|
|
|
|
`useSearchParams()` ved FØRSTE mount -- en navigasjon FRA Score-
|
|
|
|
|
|
siden til seg selv med kun en ny query-parameter er ingen ekte
|
|
|
|
|
|
remount i Next.js App Router, så verdien ble aldri regnet på nytt.
|
|
|
|
|
|
Fra Scorekort/Leaderboard virket det fordi det ER en remount
|
|
|
|
|
|
(annen rute). Fikset med en `useEffect` som reagerer på selve
|
|
|
|
|
|
parameter-ENDRINGEN (ensrettet -- hopper TIL "manage", tvinger
|
|
|
|
|
|
aldri tilbake til "score"). Reprodusert i scratch FØR fiksen
|
|
|
|
|
|
(bekreftet feilen), verifisert etterpå fra alle tre
|
|
|
|
|
|
inngangspunkter (Score/Scorekort/Leaderboard) + at lokal
|
|
|
|
|
|
"Score"-pille fortsatt virker uendret.
|
|
|
|
|
|
2. **`EditRoundPanel` sin spilleform-bryter utvidet fra 3 til 16
|
|
|
|
|
|
format:** var blitt hengende igjen på stroke/match/stableford fra
|
|
|
|
|
|
FØR de åtte nye formatene (ADR-039) ble bygget -- en ren
|
|
|
|
|
|
forglemmelse. Rettet BÅDE i backend (`RoundUpdate.play_format`
|
|
|
|
|
|
i rounds.py hadde samme gamle 3-verdis-Literal) OG frontend (ny
|
|
|
|
|
|
`EDITABLE_PLAY_FORMATS`-liste, samme etiketter som new-round.tsx).
|
|
|
|
|
|
Verifisert live i scratch: byttet en pågående runde fra Slagspill
|
|
|
|
|
|
til Fourball midt i runden, `SidesPanel` sin eksisterende "opprett
|
|
|
|
|
|
sider først"-melding tok over korrekt uten noen krasj.
|
|
|
|
|
|
3. **`completed_at`-feltet for å overstyre fullført-tidspunkt manuelt
|
|
|
|
|
|
bekreftet FORTSATT TIL STEDE, uendret siden 2026-07-24** -- ren
|
|
|
|
|
|
kodeverifisering, ingen endring nødvendig. Feltet vises (med
|
|
|
|
|
|
hensikt) kun ETTER at "Fullfør runde" er trykket -- forvirringen
|
|
|
|
|
|
var trolig et resultat av bug (1) over (kom seg aldri til
|
|
|
|
|
|
"Spillere og runde" for å finne knappen).
|
|
|
|
|
|
4. **`round-scorecard.tsx` sitt individuelle scorekort: kolonnetekst-
|
|
|
|
|
|
overflow rettet + 8 nye detaljrader lagt til** (Fairway/Putt/
|
|
|
|
|
|
GIR/Innspill/Chip/Bunkerslag/Straffeslag/Anywayslag), alle med
|
|
|
|
|
|
forkortede radetiketter + kompakte lucide-ikoner (bøyde piler for
|
|
|
|
|
|
Fairway, rette piler+blink for Innspill, sjekkmerke for GIR) --
|
|
|
|
|
|
samme golfscore-språk som resten av appen. Data var allerede
|
|
|
|
|
|
tilgjengelig i `RoundHoleOut` (bare ikke lest av denne siden før).
|
|
|
|
|
|
Vises kun når minst ett hull faktisk har dataen (samme "vis kun
|
|
|
|
|
|
det som finnes"-prinsipp). GIR-formelen gjenbruker EKSAKT samme
|
|
|
|
|
|
`score - putts <= par - 2`-regel som round-stats.tsx/round-
|
|
|
|
|
|
detail.tsx allerede bruker.
|
|
|
|
|
|
5. **Rundeleaderboard (V0-eksport "zip 28") -- IKKE V0 sin skyld at
|
|
|
|
|
|
forrige versjon "så rart sammenskrudd ut", presisert til bruker:
|
|
|
|
|
|
forrige leaderboard var HÅNDKODET (ikke V0), bygget 2026-07-26 da
|
|
|
|
|
|
brukeren gikk tom for V0-credits. Zip 28 dekket alle hullene i
|
|
|
|
|
|
V0-prompten (skrevet samme økt) presist -- portet MANUELT mot ekte
|
|
|
|
|
|
data, IKKE en full filerstatning: gjenbrukte eksisterende
|
|
|
|
|
|
`rankEntries`/`ValueMark`/`RankBadge`/`ModeToggle`/`HoleStrip`/
|
|
|
|
|
|
matchstatus-tug-of-war-baren (allerede nær identisk med V0s egen
|
|
|
|
|
|
visuelle idé, lavere risiko å beholde enn å skrive om). Kun TRE
|
|
|
|
|
|
genuint manglende/feil biter bygget:
|
|
|
|
|
|
- **High-low-high** falt tidligere inn i den generiske to-sidede
|
|
|
|
|
|
"X UP"-matchstatusen (bekreftet FEIL for dette formatet -- HLH
|
|
|
|
|
|
er 0-2 poeng-per-hull, ikke hull-ledelse). Ny `HighLowSection`:
|
|
|
|
|
|
lav/høy-duell-rollen per spiller per hull utledes fra
|
|
|
|
|
|
`entries[].points` (allerede MIN/MAKS av lagkameratenes
|
|
|
|
|
|
Stableford-poeng den hullet, samme regel som
|
|
|
|
|
|
`handicap_engine.py` sin `high_low_high_points_for_hole` --
|
|
|
|
|
|
ingen backend-endring trengtes). Verifisert tall-for-tall i
|
|
|
|
|
|
scratch: per-hull lav/høy-dueller summerte EKSAKT til
|
|
|
|
|
|
`hlh_points_a`/`hlh_points_b` sine totaler.
|
|
|
|
|
|
- **Bingo Bango Bongo/Københavner** sitt "Poeng"-tall var
|
|
|
|
|
|
`ApiLeaderboardEntry.total_points` -- ALLTID generisk Stableford
|
|
|
|
|
|
fra backend, uansett format (bekreftet reell bug: disse to
|
|
|
|
|
|
formatene har sine EGNE poengsystem, aldri vist noe sted).
|
|
|
|
|
|
Erstattet med faktiske `bbb_points`/`copenhagen_points` fra
|
|
|
|
|
|
format-result FØR rangeringen bygges. Fanget og rettet EN egen
|
|
|
|
|
|
bug UNDER scratch-verifiseringen: en deltaker med 0 ekte
|
|
|
|
|
|
BBB/Københavner-poeng manglet fra dict-en (ikke `0`, fraværende
|
|
|
|
|
|
nøkkel) -- første versjon falt da feilaktig tilbake til generisk
|
|
|
|
|
|
Stableford i stedet for `0`. Rettet, verifisert: Københavner-
|
|
|
|
|
|
poeng summerte EKSAKT til 6 per hull (regelen), BBB viste riktig
|
|
|
|
|
|
`0` for en spiller uten noen bingo/bango/bongo-vinst.
|
|
|
|
|
|
- **Shamble/Money Ball** sitt offisielle LAGRESULTAT (hele runden
|
|
|
|
|
|
= ett lag) lå tidligere IKKE synlig noe sted -- kun spillernes
|
|
|
|
|
|
råtall. Ny `TeamResultSection`: lagets til-par (fra
|
|
|
|
|
|
`shamble_team_score`/`money_ball_team_score` + hullenes par-sum,
|
|
|
|
|
|
begge allerede i format-result), pluss en NY flight-gruppe-
|
|
|
|
|
|
sammenligning (henter søsken-rundenes EGNE format-result via et
|
|
|
|
|
|
nytt `useFlightTeamStandings`-kall) mot andre lag i samme utgang
|
|
|
|
|
|
-- tidligere fantes INGEN slik sammenligning i det hele tatt.
|
|
|
|
|
|
Verifisert tall-for-tall i scratch (Shamble beste-2-av-3, Money
|
|
|
|
|
|
Ball sin rotasjon) mot to separate flight-koblede runder.
|
|
|
|
|
|
`RoundLeaderboardMini` (forhåndsvisningen i "Spillere og runde")
|
|
|
|
|
|
oppdatert til å skjule seg for Shamble/Money Ball også (samme
|
|
|
|
|
|
"ikke vis noe fremfor å vise en misvisende rangering"-prinsipp den
|
|
|
|
|
|
allerede fulgte for to-sidede format).
|
|
|
|
|
|
Regresjonstestet: vanlig Slagspill og Fourball uendret (identisk
|
|
|
|
|
|
visning/matchstatus som før refaktoreringen som lot
|
|
|
|
|
|
`MatchStatusSection` motta `result` som prop i stedet for å hente
|
|
|
|
|
|
selv).
|
|
|
|
|
|
**Rullet ut sammen med de fire punktene over, 2026-08-03**: BÅDE
|
|
|
|
|
|
`teecup_api` (RoundUpdate.play_format-utvidelsen) OG
|
|
|
|
|
|
`teecup_frontend` bygget og restartet, begge boot-et rent,
|
|
|
|
|
|
bekreftet via kompilert bundle-grep + en faktisk backend-
|
|
|
|
|
|
spørring mot den nye Literal-en. Zip-en slettet etter merge.
|
|
|
|
|
|
- **Rundeleaderboard (zip 28) — punkt 5 over ERSTATTET, full omskriving
|
|
|
|
|
|
(2026-08-03, samme dag):** brukeren testet en vanlig Slagspill-runde
|
|
|
|
|
|
(den klart vanligste stien) etter forrige punkts utrulling og så
|
|
|
|
|
|
NULL visuell endring, sendte skjermdump og spurte hva som hadde
|
|
|
|
|
|
skjedd. Årsak: forrige punkt gjenbrukte bevisst de gamle håndkodede
|
|
|
|
|
|
`RankBadge`/`ValueMark`/`ModeToggle`/`HoleStrip`/matchstatus-baren for
|
|
|
|
|
|
individuell/to-sidet visning fordi jeg vurderte dem som "strukturelt
|
|
|
|
|
|
nære nok" V0s tegning — akkurat den stien brukeren testet var derfor
|
|
|
|
|
|
uendret. Brukerens eksplisitte, ordrette svar (bevart for ettertiden):
|
|
|
|
|
|
*"Ja, jeg vil ABSOLUTT at du implementerer V0 sin tolkning av hvordan
|
|
|
|
|
|
Leaderboardet skal se ut. Jeg vil ikke at du skal gjøre personlige
|
|
|
|
|
|
endringer eller holde tilbake på noe i det hele tatt når det gjelder
|
|
|
|
|
|
dette."* Lagret som stående feedback-memory (`v0-full-fidelity`) —
|
|
|
|
|
|
gjelder ALL fremtidig V0-integrering i dette prosjektet, ikke bare
|
|
|
|
|
|
denne filen.
|
|
|
|
|
|
Fulgte opp med en fullstendig omskriving av `round-leaderboard.tsx`,
|
|
|
|
|
|
denne gangen med HVER V0-komponent portet manuelt mot ekte data (ikke
|
|
|
|
|
|
gjenbrukt): `Badge`/`MetricPill`/`ScoreMark`+`classify`/
|
|
|
|
|
|
`IndividualBoard`/`MetricToggle`/`HoleByHole`/`Legend` (individuelt),
|
|
|
|
|
|
`TwoSidedBoard`/`SideName`/`HoleWinners` (to-sidet), `HighLowBoard`/
|
|
|
|
|
|
`DuelRow` (High-low-high), `TeamFlightBoard` (Shamble/Money Ball) — de
|
|
|
|
|
|
gamle håndkodede visningskomponentene slettet i sin helhet, ikke
|
|
|
|
|
|
beholdt ved siden av.
|
|
|
|
|
|
Fem reelle bugs funnet og rettet UNDER omskrivingen (ikke antatt
|
|
|
|
|
|
riktig fra en ren typesjekk alene — funnet ved skjermdump-inspeksjon i
|
|
|
|
|
|
scratch):
|
|
|
|
|
|
1. `courseHcp` var meningsløs plassholder-matematikk
|
|
|
|
|
|
(`strokes_received * 18` uansett hva slagindeksen faktisk var) —
|
|
|
|
|
|
V0s mockup antok klientside-utregning av spillehandicap, men
|
|
|
|
|
|
backend returnerer allerede korrekt per-hull `strokes_received`
|
|
|
|
|
|
direkte. Rettet: `IndividualPlayer` fikk et ekte
|
|
|
|
|
|
`strokesReceived`-array lest direkte derfra, netto/Stableford
|
|
|
|
|
|
regnet fra DET, ikke gjenutledet.
|
|
|
|
|
|
2. `hcpIndex` var hardkodet `null` alltid — feltet
|
|
|
|
|
|
(`handicap_index_snapshot`) fantes allerede i det hentede
|
|
|
|
|
|
`/rounds/{id}`-svaret, bare ikke lest av filens egen, snevrere
|
|
|
|
|
|
lokale type. Utvidet typen, lest verdien.
|
|
|
|
|
|
3. `TeamFlightBoard` sin råscore-pille hadde et komma-operator-uttrykk
|
|
|
|
|
|
som alltid evaluerte til hardkodet `0` uansett faktisk score.
|
|
|
|
|
|
Rettet med den allerede korrekte `grossToPar(m, holes)`.
|
|
|
|
|
|
4. CSS-trunkeringsbug i `SideName`: det ledende laget sin flagg-
|
|
|
|
|
|
ikonrad manglet `min-w-0` på det indre flex-elementet, så navnet
|
|
|
|
|
|
("Lag Bjørk") trunkerte til nesten ingenting ("L..") mens det ikke-
|
|
|
|
|
|
ledende laget viste seg fullt, selv i like brede `flex-1`-
|
|
|
|
|
|
containere. Rettet.
|
|
|
|
|
|
5. Rotårsaken til (4) var egentlig et feil datavalg: brukte
|
|
|
|
|
|
`match_status_text` (en backend-streng som ALLEREDE inkluderer
|
|
|
|
|
|
lagnavnet, designet for en annen UI-plassering) i V0s
|
|
|
|
|
|
overskrifts-slot, som var designet for en mye kortere tekst siden
|
|
|
|
|
|
lagnavn vises separat via `SideName`. Rettet ved å utlede V0s
|
|
|
|
|
|
egen korte frase ("2 opp"/"Dormie 2 opp"/"Ferdig 2&1"/"Vant 2
|
|
|
|
|
|
opp"/"Delt"/"Alt likt") direkte fra de rå numeriske feltene
|
|
|
|
|
|
(`match_lead`/`match_holes_played`/`match_holes_remaining`/
|
|
|
|
|
|
`match_is_dormie`/`match_is_closed`) — fortsatt serverens
|
|
|
|
|
|
autoritative tall, bare riktig tekstformat.
|
|
|
|
|
|
**Scratch-verifisert på alle åtte formater** (Slagspill, BBB,
|
|
|
|
|
|
Københavner, High-low-high, Fourball, Shamble, Money Ball, pluss
|
|
|
|
|
|
`RoundLeaderboardMini`-regresjon) med tall håndverifisert mot samme
|
|
|
|
|
|
testdata som resten av ADR-039-arbeidet — se punkt 5 over for selve
|
|
|
|
|
|
tallutregningen, uendret av denne omskrivingen. **Rullet ut live
|
|
|
|
|
|
2026-08-03** (kun `teecup_frontend` bygget/restartet — backend-
|
|
|
|
|
|
endringen fra punktene over var allerede live). Scratch-miljøet
|
|
|
|
|
|
(DB/rolle/MinIO/API-/frontend-containere) ryddet opp etter bruk.
|
2026-08-03 06:24:57 +02:00
|
|
|
|
|
|
|
|
|
|
Neste steg:
|
|
|
|
|
|
0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet
|
|
|
|
|
|
(2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen
|
|
|
|
|
|
konsoll-feil. Ikke hvert enkelt interaksjonsdetalj (f.eks. gjeste-
|
|
|
|
|
|
kjønnsendringens reaktive utslagsfilter) klikket gjennom stykke for
|
|
|
|
|
|
stykke, men grunnleggende rendring/lasting er bevist, ikke lenger
|
|
|
|
|
|
bare typesjekket.
|
|
|
|
|
|
0b. **Rundeleaderboard — nå FAKTISK nettleser-bekreftet (2026-07-27):**
|
|
|
|
|
|
brutto/netto/poeng-veksling testet direkte i nettleseren, viste
|
|
|
|
|
|
korrekte tall og riktig form/farge-språk.
|
|
|
|
|
|
1. **Ferdig, kun for historikk:** dashbord-redesign (ADR-035) og
|
|
|
|
|
|
venner/kategorisert deling fase 1 (ADR-036) — begge designet
|
|
|
|
|
|
2026-07-25 og siden BYGGET, SCRATCH-VERIFISERT OG RULLET UT LIVE
|
|
|
|
|
|
samme dag (se status over). Venner fase 2 (rundevisibilitet) og
|
|
|
|
|
|
fase 3 (ekte medspillere) er fortsatt ikke bygget.
|
|
|
|
|
|
3. **Frittstående rundeføring + detaljert statistikk — ADR-033, skrevet
|
|
|
|
|
|
2026-07-22 og siden BYGGET/ITERERT KONTINUERLIG hver dag fram til
|
|
|
|
|
|
2026-07-29 (se hele status-loggen over -- dette punktet er nå appens
|
|
|
|
|
|
klart mest utviklede område, ikke lenger "ikke bygget").** Beholdt her
|
|
|
|
|
|
UENDRET som historisk kontekst for selve grunnbeslutningen fra
|
|
|
|
|
|
2026-07-22 (hvorfor/hvordan ADR-033 ble designet) -- ikke som en
|
|
|
|
|
|
påstand om at arbeidet fortsatt gjenstår. Brukeren avklarte 2026-07-22
|
|
|
|
|
|
at dette skulle bli appens HOVEDFOKUS (turneringsoppsett skulle bli
|
|
|
|
|
|
ekstremt enkelt ETTER dette var på plass) — den største enkeltbeslutningen
|
|
|
|
|
|
i prosjektet siden ADR-001, og det har stemt. Fire load-bærende
|
|
|
|
|
|
delbeslutninger avklart eksplisitt
|
|
|
|
|
|
(AskUserQuestion): nytt parallelt eierskapsmønster keyet på `user_id`
|
|
|
|
|
|
(gjenbruker det allerede beviste `plain_connection()`-mønsteret fra
|
|
|
|
|
|
personlig profil/HCP-historikk/sekundær e-post — IKKE en skjult
|
|
|
|
|
|
personlig organisasjon), fast sett navngitte statistikk-felt per hull
|
|
|
|
|
|
(ikke fri slag-for-slag-logg), full WHS HCP-indeksberegning bygges NÅ
|
|
|
|
|
|
(ikke utsatt), shotgun-start blir egen separat ADR-034. De tre
|
|
|
|
|
|
HCP-PDF-ene lest i sin helhet — Score Differential/Course
|
|
|
|
|
|
Handicap/Net Double Bogey-formlene er hentet derfra (kilde, ikke
|
|
|
|
|
|
hukommelse).
|
|
|
|
|
|
**Oppdatert samme dag:** brukeren bekreftet banedata-spørsmålet (LIVE
|
|
|
|
|
|
oppslag mot teeoff, ikke import — Beslutning C) og lastet opp en
|
|
|
|
|
|
FJERDE PDF, den offisielle "WHS Rules of Handicapping 2024"
|
|
|
|
|
|
(USGA/R&A) — lest i sin helhet, løste presist det som manglet: 9-
|
|
|
|
|
|
hulls-minimumsregelen (Rule 2.2, IKKE bare "minst 9 hull" — en
|
|
|
|
|
|
9-hulls-runde krever ALLE 9 av et faktisk ratet sett, en 18-hulls-
|
|
|
|
|
|
runde krever minst 10 av 18), full HCP-indeks-pipeline (Score
|
|
|
|
|
|
Differential, Net Double Bogey, expected-score for uspilte hull, beste
|
|
|
|
|
|
8-av-20, Low Handicap Index, soft/hard cap), OG en ny, presis
|
|
|
|
|
|
9-hulls-Course-Handicap-formel som HALVERER indeksen først (Rule
|
|
|
|
|
|
6.1b) — bevisst holdt atskilt fra det eksisterende front_9/back_9-
|
|
|
|
|
|
øktoppsettet i turnering-flyten (ADR-008), som løser et annet problem
|
|
|
|
|
|
av andre grunner. ADR-033 er dermed fullt kildebelagt, ingen store
|
|
|
|
|
|
åpne HCP-regelspørsmål gjenstår.
|
|
|
|
|
|
**Bygging påbegynt samme dag:** brukeren instruerte at "Expected
|
|
|
|
|
|
Score" (Rule 3.2b, upublisert WHS-formel) erstattes gjennomgående av
|
|
|
|
|
|
WHS sin egen "Net Par"-term for uspilte hull — løser samtidig hele
|
|
|
|
|
|
9-hulls-runde-spørsmålet uten en egen separat formel (Rule 5.1b droppet
|
|
|
|
|
|
bevisst). Hele HCP-indeks-motor-komponenten (Net Double Bogey/Net Par,
|
|
|
|
|
|
Adjusted Gross Score, Score Differential, Handicap Index fra beste-
|
|
|
|
|
|
8-av-20 m/opptrappingstabell for <20 runder, Low Handicap Index, soft/
|
|
|
|
|
|
hard cap, 9-hulls Course Handicap) er BYGGET og TESTET i
|
|
|
|
|
|
`handicap_engine.py` — 41/41 tester (`test_handicap_engine.py`, kjørt
|
|
|
|
|
|
uten pytest da det ikke er installert i miljøet), flere verifisert mot
|
|
|
|
|
|
regelbokens egne tallregneeksempler (Rule 5.2a, Rule 5.1c, Diagram 5.8,
|
|
|
|
|
|
Diagram 3.1b). Ren Python, ingen DB/API/frontend rørt ennå — matcher
|
|
|
|
|
|
ADR-005s "test i isolasjon FØR resten".
|
|
|
|
|
|
**Deretter, samme dag:** full databasemigrasjon skrevet
|
|
|
|
|
|
(`020_personal_rounds.sql`, sju nye tabeller: `round`/
|
|
|
|
|
|
`round_participant`/`round_hole` + fire for en global egendefinert-
|
|
|
|
|
|
bane-katalog) og scratch-verifisert (9 sjekker som
|
|
|
|
|
|
`teecup_app_scratch`, ikke superbruker — CHECK-constraints, XOR
|
|
|
|
|
|
user_id/guest_name, kaskade-sletting, GIR-derivering bekreftet mot
|
|
|
|
|
|
ekte data). INGEN RLS på disse tabellene (Beslutning A). `test_
|
|
|
|
|
|
isolation.sql` fortsatt 12/12.
|
|
|
|
|
|
**Rullet ut mot ekte `teecup_db` 2026-07-22,** bruker bekreftet
|
|
|
|
|
|
eksplisitt: alle sju tabeller bekreftet opprettet, `test_isolation.sql`
|
|
|
|
|
|
fortsatt 12/12 mot ekte database.
|
|
|
|
|
|
**Deretter, samme dag: fullt API-lag bygget** (`app/routers/
|
|
|
|
|
|
rounds.py` — opprett/liste/hent/slett runde, legg til/fjern gjest-
|
|
|
|
|
|
deltaker, PATCH hull-for-hull-stats, fullfør-runde som kjører hele
|
|
|
|
|
|
Adjusted-Gross-Score→Score-Differential-kjeden). Fant og fikset et
|
|
|
|
|
|
reelt hull underveis: `round_participant` manglet rating-tall-
|
|
|
|
|
|
kolonner (kun tee-NAVN var snapshotet, ikke selve Course/Slope/Par) —
|
|
|
|
|
|
ny migrasjon `021_round_participant_rating_snapshot.sql`. 26 scratch-
|
|
|
|
|
|
sjekker + en egen, ekte teeoff-live-oppslag-test (Beslutning C
|
|
|
|
|
|
bekreftet: ingen `course`-rad skrives noe sted). Rullet ut mot ekte
|
|
|
|
|
|
`teecup_db`/`teecup_api` 2026-07-22, `/health`/`/dashboard` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket. `frontend/next.config.mjs` fikk `/rounds`/
|
|
|
|
|
|
`/personal-courses` lagt til i `rewrites()` proaktivt (ikke deployet
|
|
|
|
|
|
ennå, ingen frontend bruker den før neste runde).
|
|
|
|
|
|
**Notat fra bruker, IKKE designet:** planer om å måle lengde på slag +
|
|
|
|
|
|
opplyse avstand til ulike punkter på banen (golf-GPS/rangefinder-type
|
|
|
|
|
|
funksjonalitet) — krever geografiske/GPS-data ingen kilde har i dag
|
|
|
|
|
|
(verken teeoff eller `personal_course`). Se FEATURE_BACKLOG.md for
|
|
|
|
|
|
full detalj.
|
|
|
|
|
|
**Frontend bygget og rullet ut 2026-07-23, samme rekkefølge-prinsipp
|
|
|
|
|
|
(engine → skjema → API → frontend) fullført:** tre nye,
|
|
|
|
|
|
organisasjonsuavhengige endepunkter lagt til i `rounds.py`
|
|
|
|
|
|
(`GET /rounds/official-search`/`{slug}` — samme mønster som
|
|
|
|
|
|
`courses.py` sitt org-scopede søk, men uten org-kontekst, siden
|
|
|
|
|
|
frittstående runder ikke har noen; `GET /personal-courses/{id}` —
|
|
|
|
|
|
detalj med utslag+kjønn, manglet fra søk-runden) og en fjerde,
|
|
|
|
|
|
nødvendig tilføyelse oppdaget UNDER frontend-designet: `RoundOut` bar
|
|
|
|
|
|
aldri hull-nivå-data i det hele tatt — ny
|
|
|
|
|
|
`GET .../participants/{id}/holes`. Hull-PATCH-endepunktet endret til å
|
|
|
|
|
|
returnere hele den oppdaterte raden i stedet for `{"ok": true}`.
|
|
|
|
|
|
**Reelt kontraktsfunn, bekreftet i scratch FØR frontend stolte på det:**
|
|
|
|
|
|
hull-PATCH er IKKE et ekte delvis-PATCH — den skriver ALLE felt ved
|
|
|
|
|
|
hvert kall, så et utelatt felt (f.eks. putts) nullstilles stille hvis
|
|
|
|
|
|
frontend ikke sender det. Løst ved at `round-detail.tsx` alltid slår
|
|
|
|
|
|
sammen med gjeldende hull-data før hver PATCH, aldri sender et isolert
|
|
|
|
|
|
feltnavn alene — verifisert eksplisitt med en egen scratch-test som
|
|
|
|
|
|
FØRST beviste nullstillings-oppførselen, DERETTER beviste at
|
|
|
|
|
|
merge-mønsteret unngår den.
|
|
|
|
|
|
Nye sider: `/rounds` (liste), `/rounds/new` (bane-kilde teeoff/egen,
|
|
|
|
|
|
søk-før-opprett for egen bane samme idé som org-banene, utslag filtrert
|
|
|
|
|
|
på brukerens registrerte kjønn, dato/starthull/hull-antall), `/rounds/
|
|
|
|
|
|
[id]` (deltaker-faner, hull-navigasjon fra starthull, slag/putt-
|
|
|
|
|
|
tallvelgere i samme stil som `session-scorecard.tsx` sin `StrokePicker`,
|
|
|
|
|
|
kølle/retning/innspill/chip/bunker/straffeslag bak en «flere detaljer»-
|
|
|
|
|
|
utvidelse, GIR utledet og vist klientside — aldri lagret, kun beregnet
|
|
|
|
|
|
fra `approach_result`+slag+putt ved lesing, fullfør-runde med HCP-
|
|
|
|
|
|
differensial-sammendrag). Lenket fra dashbordet som «Egne runder»
|
|
|
|
|
|
(bevisst adskilt navn fra det eksisterende «Mine runder», som gjelder
|
|
|
|
|
|
turnering-deltakelse — samme ord, to ulike konsepter).
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-container, samme mønster som hele
|
|
|
|
|
|
økten ellers): 22 sjekker som dekker egendefinert-bane-opprettelse med
|
|
|
|
|
|
to utslag/kjønn, søk, detalj, PATCH-kontrakten (inkl. det bevisste
|
|
|
|
|
|
nullstillings-beviset over), GIR-derivering, gjest-fjerning,
|
|
|
|
|
|
kryss-bruker-autorisasjon (403), og full fullføring med differensial
|
|
|
|
|
|
— PLUSS en egen, separat test av hele teeoff-baserte opprettelsesløpet
|
|
|
|
|
|
mot den ekte kjørende `teeoff_api`-containeren (Borregaard Golfklubb),
|
|
|
|
|
|
som bekreftet course_handicap ble beregnet riktig. Ekte typesjekket
|
|
|
|
|
|
PRODUKSJONSBUILD (`docker build --target builder`, samme steg som
|
|
|
|
|
|
`Dockerfile` faktisk bruker) kjørt og bekreftet — alle nye ruter listet.
|
|
|
|
|
|
**Rullet ut live 2026-07-23**, bruker bekreftet eksplisitt: ingen
|
|
|
|
|
|
migrasjon, `docker compose up -d --build teecup_api teecup_frontend`,
|
|
|
|
|
|
begge containere boot-et rent, `/health`/`/dashboard`/`/rounds` → 200,
|
|
|
|
|
|
`teeoff.no` upåvirket. **Frittstående rundeføring har dermed backend
|
|
|
|
|
|
OG frontend live** — se ADR-033 i ARCHITECTURE_DECISIONS.md for full
|
|
|
|
|
|
detalj om alle fire lagene (engine/skjema/API/frontend).
|
|
|
|
|
|
**Frontend ERSTATTET med V0-designet versjon, samme dag:** brukeren
|
|
|
|
|
|
påpekte berettiget at de tre skjermene over var hånd-kodet av meg i
|
|
|
|
|
|
stedet for designet i V0, som er prosjektets etablerte mønster for
|
|
|
|
|
|
ALL øvrig frontend. Bekreftet arbeidsmåten uendret (jeg skriver
|
|
|
|
|
|
V0-prompten, brukeren kjører den og sender koden tilbake, jeg
|
|
|
|
|
|
integrerer). Tre prompter skrevet (liste/opprett/hull-registrering,
|
|
|
|
|
|
inkl. eksplisitt tilgjengelighetskrav i hver), tre zip-eksporter
|
|
|
|
|
|
mottatt og diffet mot levende tre (samme "full re-eksport"-mønster som
|
|
|
|
|
|
alltid — kun `round-card.tsx`/`own-rounds.tsx`/`new-round.tsx`/
|
|
|
|
|
|
`round-detail.tsx` + rutene var reelt nye). Samme kjente V0-feil dukket
|
|
|
|
|
|
opp igjen og ble hoppet over (`app/clubs/[id]/page.tsx`, feilnavngitt
|
|
|
|
|
|
slug-parameter — identisk feil som ble rettet under ADR-018). Mine
|
|
|
|
|
|
hånd-bygde komponenter ERSTATTET (ikke supplert), datalag skrevet om
|
|
|
|
|
|
fra V0s mock til ekte fetch — samme mønster som enhver annen skjerm.
|
|
|
|
|
|
Full detalj (inkl. de fire reelle tilpasningene utover ren om-kabling)
|
|
|
|
|
|
i ADR-033. Ekte typesjekket produksjonsbuild kjørt og bekreftet, ingen
|
|
|
|
|
|
ny interaktiv nettleser-test (intet slikt verktøy tilgjengelig,
|
|
|
|
|
|
flagget eksplisitt). **Rullet ut live 2026-07-23**, bruker bekreftet
|
|
|
|
|
|
eksplisitt: `docker compose up -d --build teecup_frontend` (gjenskapte
|
|
|
|
|
|
også `teecup_api` som vanlig bivirkning), begge containere boot-et
|
|
|
|
|
|
rent, `/health`/`/dashboard`/`/rounds`/`/rounds/new` → 200, `teeoff.no`
|
|
|
|
|
|
upåvirket. V0-zip-ene slettet fra prosjektroten.
|
|
|
|
|
|
**Reell produksjonsbug rapportert av bruker (2 skjermbilder) og
|
|
|
|
|
|
FIKSET samme dag:** `/rounds` var samtidig frontend-listesidens sti OG
|
|
|
|
|
|
backend-APIets ressursprefiks — en verre, begge-veier-variant av
|
|
|
|
|
|
ADR-016s medlemsside-felle. Statisk side vant over rewrite for det
|
|
|
|
|
|
eksakte `/rounds`-treffet (klientens `fetch("/rounds")`/`POST /rounds`
|
|
|
|
|
|
traff aldri backend, fikk Next sin egen HTML tilbake — derav "Klarte
|
|
|
|
|
|
ikke å hente rundene dine"/"Klarte ikke å opprette runden"), mens
|
|
|
|
|
|
rewriten vant over den DYNAMISKE `/rounds/[id]`-siden i motsatt
|
|
|
|
|
|
retning (selve rundedetalj-siden var dermed fullstendig uoppnåelig,
|
|
|
|
|
|
bekreftet direkte med `curl` før fiksen). Skjermbildets andre detalj —
|
|
|
|
|
|
utslagsnavn "55/50/44/32" — ble sjekket direkte mot ekte `teeoff_api`
|
|
|
|
|
|
og bekreftet Å IKKE VÆRE EN BUG (Tjøme Golfklubb sine faktiske
|
|
|
|
|
|
utslagsnavn, lengde i hundremeter). Fikset ved å flytte alle tre
|
|
|
|
|
|
frontend-rutene til et nytt, ikke-overlappende prefiks `/my-rounds/*`
|
|
|
|
|
|
— API-et uendret på `/rounds`. `next.config.mjs` sin advarsel utvidet
|
|
|
|
|
|
med denne nye varianten. Verifisert med `curl` mot ekte produksjon
|
|
|
|
|
|
BÅDE før og etter (anonymt `GET /rounds` → nå korrekt backend-JSON,
|
|
|
|
|
|
anonymt `GET /my-rounds/<uuid>` → nå korrekt `text/html`, altså
|
|
|
|
|
|
endelig oppnåelig). Rullet ut live 2026-07-23, bruker bekreftet
|
|
|
|
|
|
eksplisitt, kun `teecup_frontend` (+ vanlig `teecup_api`-bivirkning),
|
|
|
|
|
|
`teeoff.no` upåvirket. Full detalj i ADR-033.
|
|
|
|
|
|
4. **Del 1 (fri, ukrevd sekundær-e-post) er nå BYGGET OG LIVE** (2026-07-21,
|
|
|
|
|
|
se status over). **Del 2 (ekte konto-sammenslåing) fortsatt IKKE
|
|
|
|
|
|
designet:** hva skjer hvis den ønskede adressen ALLEREDE tilhører en
|
|
|
|
|
|
annen, eksisterende konto (i dag avvist tydelig med 409 DUPLICATE i
|
|
|
|
|
|
stedet for gjettet på)? Trenger egen, separat designrunde — se
|
|
|
|
|
|
FEATURE_BACKLOG.md for full analyse av hvorfor dette er vesentlig
|
|
|
|
|
|
vanskeligere enn del 1.
|
|
|
|
|
|
5. **Deltaker-tilgang til lag-chat/scorekort OG HCP-historikk er nå BEGGE
|
|
|
|
|
|
BYGGET OG LIVE** (2026-07-21, se status over) — ADR-031s tidligere
|
|
|
|
|
|
noterte "naturlig neste steg"-punkter er dermed alle tettet, unntatt
|
|
|
|
|
|
notifikasjons-/aktivitetsfeed og "Mine runder" for rene påmeldinger
|
|
|
|
|
|
(begge fortsatt IKKE bygget, se FEATURE_BACKLOG.md).
|
|
|
|
|
|
6. ~~Oppfølgingspunkt fra PWA-runden: offline-scoreregistrering er ALDRI
|
|
|
|
|
|
browser-testet i praksis~~ — **ferdig 2026-07-28** (se status-punkt
|
|
|
|
|
|
samme dag, "Turnering-scorekortets offline-flyt (ADR-028) FAKTISK
|
|
|
|
|
|
browserverifisert"). Beholdt her kun for historikk.
|
|
|
|
|
|
7. Fortsatt åpne beslutninger fra FEATURE_BACKLOG.md: kode-regenerering for
|
|
|
|
|
|
ADR-020, korrigering-godkjenning fra motpart, video/1-til-1-meldinger
|
|
|
|
|
|
(bevisst utsatt i ADR-025).
|
|
|
|
|
|
8. Flere turneringsformater utover Ryder Cup (Københavner/High-low-high/
|
|
|
|
|
|
Robbins/Try all, notert 2026-07-19) — ingen ADR-runde startet ennå.
|
|
|
|
|
|
9. Ved fremtidige nye V0-skjermer/-design i V0 (fortsett i samme prosjekt),
|
|
|
|
|
|
FORVENT en full re-eksport hver gang — diff mot live-treet i et
|
|
|
|
|
|
scratch-område før noe pakkes ut over eksisterende filer, og sjekk om
|
|
|
|
|
|
V0-skjermen bygger inn handlinger backend ikke støtter ennå FØR
|
|
|
|
|
|
integrering.
|
|
|
|
|
|
10. **Resterende hull fra 2026-07-28-gjennomgangen av frittstående runder**
|
|
|
|
|
|
(det viktigste — faktisk HCP — er tettet, se ADR-038 over; offline-kø,
|
|
|
|
|
|
punkt (a), er nå OGSÅ tettet, se status 2026-07-28 over — punktet
|
|
|
|
|
|
beholdes her kun for historikk): (a) ~~offline-kø (ADR-028) er kun
|
|
|
|
|
|
koblet til turnering-scorekortet~~ — ferdig, browserverifisert og
|
|
|
|
|
|
live; (b) ~~ingen Stableford-poengberegning for frittstående runder
|
|
|
|
|
|
(kun rå slag/differensial), inkl. en uløst "plukket opp ballen"-
|
|
|
|
|
|
tilstand~~ — ferdig, scratch-/browserverifisert og live 2026-07-29,
|
|
|
|
|
|
se status over (`play_format='stableford'` + `round_hole.picked_up`,
|
|
|
|
|
|
migrasjon 038); (c) ~~rundedeling/visibility (ADR-036 fase 2, public/private/friends)
|
|
|
|
|
|
fortsatt ikke bygget~~ — ferdig, browser-/scratch-verifisert og live
|
|
|
|
|
|
2026-07-28, se status over (ADR-036 er dermed HELT ferdig, alle tre
|
|
|
|
|
|
faser); (d) ~~flere flighter i én frittstående runde fortsatt kun
|
|
|
|
|
|
drøftet~~ — ferdig, retning 1 (løs gruppering) bygget, scratch-/
|
|
|
|
|
|
browserverifisert og live 2026-07-28, se status over; (e) ~~varsler
|
|
|
|
|
|
koblet til venneforespørsler, ikke til rundehendelser ennå~~ —
|
|
|
|
|
|
ferdig, scratch-verifisert og live 2026-07-28, se status over
|
|
|
|
|
|
(medspiller lagt til/venn ser synlig runde/tilkoblet runde fullført).
|
|
|
|
|
|
Alle fem punktene (a)-(e) i denne listen er dermed ferdig.
|
|
|
|
|
|
11. **Ferdig, kun for historikk:** ekte spillformer (match/skins/fourball/
|
|
|
|
|
|
foursome/greensome/scramble) for frittstående runder — backend
|
|
|
|
|
|
(ADR-039 + minimums-spiller-håndhevelse) OG frontend (sideoppsett,
|
|
|
|
|
|
skins-konfig i `/my-rounds/new`, matchstatus-/skins-tavle-visning,
|
|
|
|
|
|
delt-ball-scorekort/-veiviser, `setup_complete`-gating) er nå BEGGE
|
|
|
|
|
|
bygget, browserverifisert og live (se status 2026-07-28). Mulig
|
|
|
|
|
|
fremtidig finpuss (ikke bedt om ennå): redigere et sidenavn i
|
|
|
|
|
|
etterkant (kun opprett/slett finnes i dag), en tydeligere
|
|
|
|
|
|
skins-poeng-forklaring i UI-et.
|
|
|
|
|
|
12. **Ferdig, kun for historikk:** de tre organisator-oppfølgingspunktene
|
|
|
|
|
|
(flytte spiller mellom lag, individuell rangering per økt, midlertidige
|
|
|
|
|
|
spillere + etter-runde-invitasjon) — alle bygget, scratch-/
|
|
|
|
|
|
browserverifisert og live 2026-07-28, se status over. Ingen nye åpne
|
|
|
|
|
|
spørsmål igjen fra denne runden.
|
|
|
|
|
|
13. **Ferdig, kun for historikk:** PWA-installasjonsoppfordring, flere
|
|
|
|
|
|
flighter i én frittstående runde (retning 1), scramble/greensome-
|
|
|
|
|
|
statistikk over valgt utslag (frittstående runder), og push-varsler
|
|
|
|
|
|
til telefonens OS (Web Push/VAPID) — alle fire bygget, scratch-/
|
|
|
|
|
|
browserverifisert og live 2026-07-28, se status over for full detalj.
|
|
|
|
|
|
Gjenstående, IKKE bygget: samme scramble-utslagsstatistikk for
|
|
|
|
|
|
org-scopede turneringer (bevisst utenfor omfang, se
|
|
|
|
|
|
FEATURE_BACKLOG.md), ekte OS-nivå push-levering ikke bevist med en
|
|
|
|
|
|
reell innvilget tillatelse (automatisert testmiljø hadde
|
|
|
|
|
|
`Notification.permission` forhåndssatt til "denied") — bruker bør
|
|
|
|
|
|
selv teste push på en ekte enhet.
|
|
|
|
|
|
14. **Åtte nye turneringsformater (Chapman/Nassau/Københavner/Bingo Bango
|
|
|
|
|
|
Bongo/Flaggturnering/Shamble/Money Ball/High-low-high) — BACKEND
|
|
|
|
|
|
FERDIG, SCRATCH-VERIFISERT OG LIVE (2026-07-30, migrasjoner 041-050),
|
2026-08-03 18:13:21 +02:00
|
|
|
|
se status over.** ~~Eneste gjenstående steg: FRONTEND for samtlige
|
|
|
|
|
|
åtte~~ — **RETTET 2026-08-03: dette punktet stod utdatert.** Frontend
|
|
|
|
|
|
ble faktisk bygget i sesjonene mellom 2026-07-30 og 2026-08-02 (bl.a.
|
|
|
|
|
|
synlig i "Det store grepet"-rettingene 2026-08-02 over, som allerede
|
|
|
|
|
|
forutsetter `tournament-program.tsx` sin `CreateSessionCard` med alle
|
|
|
|
|
|
10 to-sidede format), men punkt 14 her ble aldri oppdatert til å si
|
|
|
|
|
|
det. Bekreftet direkte mot koden 2026-08-03 (samme dag som
|
|
|
|
|
|
leaderboardets V0-omskriving, se status over): spilleform-valg finnes
|
|
|
|
|
|
i `new-round.tsx` (frittstående), `tournament-program.tsx`
|
|
|
|
|
|
(org-lag), `individual-tournament-detail.tsx` (org-individuell,
|
|
|
|
|
|
`scoring_method`); resultatvisninger finnes for Nassau (`NassauPanel`/
|
|
|
|
|
|
`session-scorecard.tsx`), Københavner/BBB (`individual-tournament-
|
|
|
|
|
|
detail.tsx`), Shamble/Money Ball (`round-leaderboard.tsx`s
|
|
|
|
|
|
`TeamFlightBoard`) og High-low-high (`round-leaderboard.tsx`s
|
|
|
|
|
|
`HighLowBoard`/`session-scorecard.tsx`). Migrasjoner bekreftet live
|
|
|
|
|
|
t.o.m. 052. Samme rettelse lagt inn i FEATURE_BACKLOG.md samme dag.
|
Legg til turnering-presentasjon: hero-bilde, sponsorer, synlighet, påmelding
Backend for dette (hero_image_key, sponsor-CRUD, visibility/description/
registrering) har vært klart og live siden ADR-018 (2026-07-18), men ingen
organisator-skjerm satte noensinne disse feltene - alt var 100% API-only.
Ny tournament-presentation.tsx dekker begge turneringstyper (delt
tournament-tabell, ikke format_type-spesifikt): full side for
lagturneringer (ny rute), embeddet som fjerde fane for individuelle
turneringer. Lagt til i alle fire eksisterende nav-rader.
To små backend-tillegg uten migrasjon: hero_image_url/logo_url som
beregnede felt (samme mønster som avatar_url), og en DELETE-endepunkt for
hero-bilde (fantes fra før kun opplasting).
Fant og rettet en ekte mobil-layoutbug under scratch-verifisering:
sponsor-raden klemte navnet til nesten ingenting på 390px viewport -
samme klasse trunkeringsdefekt som leaderboard-omskrivingen tidligere
denne uken, men på et annet sted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 19:03:53 +02:00
|
|
|
|
~~**Reell, fortsatt åpen gap (urelatert til de åtte formatene):**
|
2026-08-03 18:13:21 +02:00
|
|
|
|
organisator-vendt opplasting av hero-/sponsorbilder for
|
|
|
|
|
|
turnering-landingssiden (ADR-018) — backend/lagring finnes, ingen
|
Legg til turnering-presentasjon: hero-bilde, sponsorer, synlighet, påmelding
Backend for dette (hero_image_key, sponsor-CRUD, visibility/description/
registrering) har vært klart og live siden ADR-018 (2026-07-18), men ingen
organisator-skjerm satte noensinne disse feltene - alt var 100% API-only.
Ny tournament-presentation.tsx dekker begge turneringstyper (delt
tournament-tabell, ikke format_type-spesifikt): full side for
lagturneringer (ny rute), embeddet som fjerde fane for individuelle
turneringer. Lagt til i alle fire eksisterende nav-rader.
To små backend-tillegg uten migrasjon: hero_image_url/logo_url som
beregnede felt (samme mønster som avatar_url), og en DELETE-endepunkt for
hero-bilde (fantes fra før kun opplasting).
Fant og rettet en ekte mobil-layoutbug under scratch-verifisering:
sponsor-raden klemte navnet til nesten ingenting på 390px viewport -
samme klasse trunkeringsdefekt som leaderboard-omskrivingen tidligere
denne uken, men på et annet sted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 19:03:53 +02:00
|
|
|
|
dra-og-slipp-skjerm bygget ennå.~~ — **lukket samme dag, se punkt 16.**
|
2026-08-03 06:24:57 +02:00
|
|
|
|
15. **Ferdig, kun for historikk:** «Det store grepet» (ADR-040) — alle
|
|
|
|
|
|
5 steg (backend/allowance_override, lag-sortering, rundeoppsett-
|
|
|
|
|
|
veiviseren, delt Score/Scorekort/Leaderboard-fane-rad, integrering)
|
|
|
|
|
|
BYGGET, VERIFISERT OG LIVE 2026-08-02, se status over. **Ett bevisst
|
|
|
|
|
|
utsatt, fortsatt åpent spørsmål:** trenger lag-grupperingen i
|
|
|
|
|
|
scorekortet mer enn celle-fargelegging for å skille lagene tydelig
|
|
|
|
|
|
nok (utover selve sorteringen, som allerede er fikset) — ikke stilt
|
|
|
|
|
|
til V0 ennå, egen liten vurdering om ønskelig. Se FEATURE_BACKLOG.md.
|
Legg til turnering-presentasjon: hero-bilde, sponsorer, synlighet, påmelding
Backend for dette (hero_image_key, sponsor-CRUD, visibility/description/
registrering) har vært klart og live siden ADR-018 (2026-07-18), men ingen
organisator-skjerm satte noensinne disse feltene - alt var 100% API-only.
Ny tournament-presentation.tsx dekker begge turneringstyper (delt
tournament-tabell, ikke format_type-spesifikt): full side for
lagturneringer (ny rute), embeddet som fjerde fane for individuelle
turneringer. Lagt til i alle fire eksisterende nav-rader.
To små backend-tillegg uten migrasjon: hero_image_url/logo_url som
beregnede felt (samme mønster som avatar_url), og en DELETE-endepunkt for
hero-bilde (fantes fra før kun opplasting).
Fant og rettet en ekte mobil-layoutbug under scratch-verifisering:
sponsor-raden klemte navnet til nesten ingenting på 390px viewport -
samme klasse trunkeringsdefekt som leaderboard-omskrivingen tidligere
denne uken, men på et annet sted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 19:03:53 +02:00
|
|
|
|
16. **Turnering-presentasjon: organisator-vendt hero-bilde/sponsor-
|
|
|
|
|
|
opplasting + beskrivelse/synlighet/påmeldingsinnstillinger — BYGGET,
|
|
|
|
|
|
SCRATCH-VERIFISERT OG LIVE 2026-08-03.** Brukeren spurte om
|
|
|
|
|
|
presentasjonssider var på plass; svaret avdekket at backenden for
|
|
|
|
|
|
dette (`hero_image_key`/sponsor-CRUD/`visibility`/`description`/
|
|
|
|
|
|
påmeldingsfelt) hadde vært klar og LIVE siden ADR-018 (2026-07-18),
|
|
|
|
|
|
men INGEN organisator-skjerm noensinne satte disse feltene — alt var
|
|
|
|
|
|
100% API-only. Bekreftet omfanget eksplisitt med bruker (fullt
|
|
|
|
|
|
presentasjons-panel, ikke bare bilder) før bygging.
|
|
|
|
|
|
Ny `components/tournament-presentation.tsx`: `TournamentPresentation`
|
|
|
|
|
|
(full side, egen rute `/tournaments/[id]/presentation`, brukt av
|
|
|
|
|
|
lagturneringer) + `TournamentPresentationPanel` (samme innhold uten
|
|
|
|
|
|
header/nav, bygget inn som en fjerde in-page-fane i
|
|
|
|
|
|
`individual-tournament-detail.tsx` -- denne filen har sitt eget
|
|
|
|
|
|
fanesystem, ikke egne ruter som lagturneringene). Feltene er delt
|
|
|
|
|
|
mellom lag- og individuelle turneringer (samme `tournament`-tabell,
|
|
|
|
|
|
ikke `format_type`-spesifikke), så ÉN komponent dekker begge.
|
|
|
|
|
|
"Presentasjon"-fanen lagt til i alle fire eksisterende nav-rader
|
|
|
|
|
|
(`tournament-detail.tsx`/`tournament-program.tsx`/`tournament-
|
|
|
|
|
|
leaderboard.tsx`/`individual-tournament-detail.tsx`) -- disse fire er
|
|
|
|
|
|
fortsatt hver sin duplikat (samme mønster/samme kjente ulempe som
|
|
|
|
|
|
2026-08-02-bug-runden over), ikke en delt komponent denne runden.
|
|
|
|
|
|
La samtidig til `overflow-x-auto` på alle fire nav-rader (fjerde fane
|
|
|
|
|
|
presset bredden over det tidligere 3-faners layoutet tålte på smale
|
|
|
|
|
|
mobilskjermer).
|
|
|
|
|
|
**To små, bevisst minimale backend-tillegg** (ingen migrasjon --
|
|
|
|
|
|
rene response-modell-/endepunkt-tillegg):
|
|
|
|
|
|
- `Tournament.hero_image_url`/`Sponsor.logo_url`: nye beregnede felt
|
|
|
|
|
|
(samme mønster som `auth.py` sin `avatar_url`) -- organisator-
|
|
|
|
|
|
frontend skal aldri selv måtte kjenne MinIO-bucket/base-URL.
|
|
|
|
|
|
`_tournament_from_row()`/`_sponsor_from_row()`-hjelpere lagt til,
|
|
|
|
|
|
alle 8 tidligere `Tournament(**dict(row))`/`Sponsor(**dict(row))`-
|
|
|
|
|
|
kallsteder erstattet.
|
|
|
|
|
|
- Ny `DELETE /orgs/{id}/tournaments/{id}/hero-image` (samme mønster
|
|
|
|
|
|
som eksisterende `DELETE /auth/profile/avatar`) -- fantes ikke fra
|
|
|
|
|
|
før, kun opplasting.
|
|
|
|
|
|
**Én reell bug funnet og rettet UNDER scratch-verifisering** (ikke
|
|
|
|
|
|
antatt riktig fra ren typesjekk): sponsor-radens layout (logo-
|
|
|
|
|
|
miniatyr + navn + "Last opp logo"-knapp med full tekst + slett-ikon,
|
|
|
|
|
|
alle på én rad) klemte sponsornavnet til nesten ingenting på en ekte
|
|
|
|
|
|
390px mobil-viewport ("Tjø…" for "Tjøme Rørlegger AS"). Rettet ved å
|
|
|
|
|
|
la navn/lenke ligge på egen rad, handlingsknappene på en egen rad
|
|
|
|
|
|
under (`flex-col` på mobil, `sm:flex-row` fra small breakpoint) --
|
|
|
|
|
|
samme klasse defekt (for lite bredde satt av til tekst i en trang
|
|
|
|
|
|
flex-rad) som `SideName`-trunkeringsbugen i leaderboard-omskrivingen
|
|
|
|
|
|
tidligere denne uken, men et annet konkret sted.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-/frontend-container, ekte
|
|
|
|
|
|
nettleser-innlogging inkl. reell 2FA-e-post-oppsett): full
|
|
|
|
|
|
PATCH-rundtur (beskrivelse/synlighet/godkjenning/venteliste-
|
|
|
|
|
|
policy lagret og lest tilbake korrekt fra ekte DB), hero-bilde
|
|
|
|
|
|
lastet opp og bekreftet i MinIO (`image/avif`, riktig nøkkel i DB),
|
|
|
|
|
|
hero-bilde fjernet (nøkkel nullstilt), sponsor lagt til, sponsor-
|
|
|
|
|
|
logo lastet opp og bekreftet i MinIO, sponsor slettet -- alt testet
|
|
|
|
|
|
på BÅDE en lagturnering og en individuell turnering, pluss bekreftet
|
|
|
|
|
|
at den offentlige siden (`/t/{id}`) viser organisatorens satte
|
|
|
|
|
|
beskrivelse. **Rullet ut live 2026-08-03**, bruker bekreftet
|
|
|
|
|
|
eksplisitt: `docker compose up -d --build teecup_api teecup_frontend`,
|
|
|
|
|
|
begge containere boot-et rent, `/health`/`/dashboard` → 200,
|
|
|
|
|
|
ny `/tournaments/[id]/presentation`-rute bekreftet i build-outputen.
|
|
|
|
|
|
**Ikke klikket gjennom i selve produksjonen** -- ingen ekte turnering
|
|
|
|
|
|
finnes ennå i `teecup_db` ("Ingen turneringer ennå" på dashbordet),
|
|
|
|
|
|
så dette er samme build som scratch-verifisert, ikke i tillegg
|
|
|
|
|
|
egenhendig bekreftet mot ekte produksjonsdata. Bekreft ved neste
|
|
|
|
|
|
faktiske turnering.
|
2026-08-04 03:49:41 +02:00
|
|
|
|
17. **Konkurranseklasser ("Damer fra 44, Herrer fra 50+") — BYGGET,
|
|
|
|
|
|
SCRATCH-VERIFISERT OG LIVE 2026-08-04, migrasjon 053.** Brukeren
|
|
|
|
|
|
spurte om det var mulig å sette opp runder i en turnering slik at
|
|
|
|
|
|
f.eks. damer spiller fra utslag 44 mens herrer spiller fra 50+.
|
|
|
|
|
|
Svaret var at DELER allerede virket (hver deltaker i en match/runde
|
|
|
|
|
|
fikk allerede sitt eget `tee_id`, backend validerte allerede at
|
|
|
|
|
|
utslaget hadde rating for spillerens kjønn) -- men et manuelt valg
|
|
|
|
|
|
per spiller hver gang, ingen gjenbrukbar "klasse". Brukeren ba om en
|
|
|
|
|
|
ekte klasse-mekanisme, "samt andre parametre som er relevante i en
|
|
|
|
|
|
slik problemstilling."
|
|
|
|
|
|
Full plan-modus-runde med to `AskUserQuestion`-avklaringer FØR
|
|
|
|
|
|
bygging (samme disiplin som ADR-037/ADR-039): (1) klasser er FRITT
|
|
|
|
|
|
NAVNGITTE (ikke bare kjønn) med et valgfritt standardutslag, ikke en
|
|
|
|
|
|
fast kjønn+alder-modell -- dekker kjønn/alder/HCP eller annet uten at
|
|
|
|
|
|
systemet må forstå forskjellen; (2) klasse gir EGEN RESULTATLISTE --
|
|
|
|
|
|
men KUN i individuelle turneringer (ADR-037, flatt felt). I
|
|
|
|
|
|
lagturneringer (ADR-011, to lag) er poeng knyttet til hele kamper,
|
|
|
|
|
|
ikke enkeltspillere -- brukeren bekreftet eksplisitt at klasse der
|
|
|
|
|
|
KUN skal foreslå utslag, ingen leaderboard-splitting (det finnes fra
|
|
|
|
|
|
før et smalt per-økt individuelt leaderboard for singles/fourball-
|
|
|
|
|
|
slagspill, `fetch_individual_leaderboard` -- IKKE noe denne runden
|
|
|
|
|
|
bygger videre på); (3) gjelder org-lagturneringer OG org-individuelle
|
|
|
|
|
|
turneringer, ikke frittstående personlige runder.
|
|
|
|
|
|
**Skjema** (`053_tournament_classes.sql`): ny delt `tournament_class`-
|
|
|
|
|
|
tabell (id/organization_id/tournament_id/name/default_tee_id,
|
|
|
|
|
|
`UNIQUE(tournament_id, name)`) -- delt mellom begge turneringstyper
|
|
|
|
|
|
siden begge peker til samme `tournament`-tabell (ADR-037s
|
|
|
|
|
|
`format_type`-mønster), unngår duplisering. To nye NULLABLE FK-
|
|
|
|
|
|
kolonner (`ON DELETE SET NULL`, samme mønster som `tournament_round_
|
|
|
|
|
|
bbb_hole`): `team_roster.class_id` og `tournament_participant.
|
|
|
|
|
|
class_id`. Samme `org_isolation`-RLS-loop som migrasjon 040.
|
|
|
|
|
|
**Backend**: ny klasse-CRUD (`GET/POST/PATCH/DELETE .../classes`) i
|
|
|
|
|
|
`tournaments.py` (delt fil, siden `tournament`-tabellen er delt).
|
|
|
|
|
|
`RosterEntryCreate`/`RosterEntry` fikk `class_id`/`class_name`;
|
|
|
|
|
|
`RosterEntryUpdate` (tidligere KUN `is_captain: bool` påkrevd)
|
|
|
|
|
|
lagt om til `exclude_unset`-PATCH-semantikk (samme mønster som
|
|
|
|
|
|
`TournamentUpdate`/`SponsorUpdate` fra presentasjons-panel-runden)
|
|
|
|
|
|
slik at klasse kan settes uten å tvinge et samtidig kaptein-valg.
|
|
|
|
|
|
`TournamentParticipantCreate`/`Out` (individual_tournaments.py) fikk
|
|
|
|
|
|
samme felt, pluss en NY `PATCH .../participants/{id}` (fantes ikke
|
|
|
|
|
|
før -- kun POST/GET/DELETE). `individual_leaderboard` utvidet med
|
|
|
|
|
|
`class_id`/`class_name` i SELECT+respons -- selve summerings-/
|
|
|
|
|
|
sorteringslogikken UENDRET, frontend grupperer den allerede sorterte
|
|
|
|
|
|
listen visuelt (stabil gruppering bevarer riktig rangering per
|
|
|
|
|
|
klasse, ingen ny motorlogikk i `handicap_engine.py` -- verifisert:
|
|
|
|
|
|
98/98 eksisterende enhetstester fortsatt grønne, ingen regresjon).
|
|
|
|
|
|
**Frontend**: "Klasser"-kort (opprett/slett, kaskaderende bane→
|
|
|
|
|
|
utslag-velger for standardutslag) i BÅDE `tournament-detail.tsx`
|
|
|
|
|
|
("Lag og spillere"-fanen) og `individual-tournament-detail.tsx`
|
|
|
|
|
|
(Oppsett-fanen). Utslag-forhåndsutfylling i to steder --
|
|
|
|
|
|
`session-blind-draw.tsx`s `AddSlotForm` (lagturneringer) og
|
|
|
|
|
|
`individual-tournament-detail.tsx`s `AssignRoundParticipantControl`
|
|
|
|
|
|
(individuelle turneringer) -- begge en `useEffect` som slår opp valgt
|
|
|
|
|
|
spillers klasse → standardutslag når spilleren velges, KUN hvis det
|
|
|
|
|
|
utslaget faktisk finnes på DENNE øktens/rundens bane (klassens
|
|
|
|
|
|
standardutslag kan tilhøre en annen bane), fortsatt fritt
|
|
|
|
|
|
overstyrbart. Ren frontend-bekvemmelighet, samme "gjenbruk et
|
|
|
|
|
|
eksisterende felt, ikke en ny backend-mekanisme"-prinsipp som
|
|
|
|
|
|
`new-round.tsx`s eksisterende kjønnsfilter/-default. `LeaderboardTab`
|
|
|
|
|
|
(individuelle turneringer) grupperer den mottatte, allerede sorterte
|
|
|
|
|
|
listen på `class_id`, hver klasse får egen seksjonsoverskrift +
|
|
|
|
|
|
egen 1/2/3-rangering INNAD i klassen -- ingen klasser opprettet =
|
|
|
|
|
|
identisk med tidligere flat visning, bakoverkompatibelt uten
|
|
|
|
|
|
migrasjonsflagg.
|
|
|
|
|
|
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
|
|
|
|
|
isolert scratch-MinIO + engangs API-/frontend-container, ekte
|
|
|
|
|
|
nettleser-innlogging inkl. 2FA): full API-rundtur (klasse-CRUD,
|
|
|
|
|
|
roster-/deltaker-PATCH med class_id, `ON DELETE SET NULL` bekreftet
|
|
|
|
|
|
ved klasseslettelse), ekte nettleser-klikk gjennom BEGGE
|
|
|
|
|
|
turneringstyper -- lagturnering: opprettet klasser via UI-skjemaet
|
|
|
|
|
|
(kaskaderende bane→utslag bekreftet), tildelte klasse via
|
|
|
|
|
|
dropdown-menyen, bekreftet utslag forhåndsvalgt korrekt idet en
|
|
|
|
|
|
spiller med klasse ble valgt i `AddSlotForm` (Kari→44, Ola→56);
|
|
|
|
|
|
individuell turnering: samme mønster i `AssignRoundParticipantControl`
|
|
|
|
|
|
(Bjørn Ege→56), pluss det klasse-delte leaderboardet bekreftet
|
|
|
|
|
|
visuelt korrekt -- "DAMER"-seksjon viste Kari (72 slag, rang 1) foran
|
|
|
|
|
|
Siv (108 slag, rang 2), "HERRER" viste Ola alene (90 slag, rang 1),
|
|
|
|
|
|
tallene håndregnet og stemte eksakt (par/+18/+36 mot faktisk
|
|
|
|
|
|
innsendt score). `test_isolation.sql` 12/12 uendret, ekte
|
|
|
|
|
|
typesjekket produksjonsbuild (fanget og rettet ETT reelt
|
|
|
|
|
|
TypeScript-funn under selve verifiseringen: `classes`-propen manglet
|
|
|
|
|
|
på `TeamColumn` i `session-blind-draw.tsx` -- `AddSlotForm` ligger i
|
|
|
|
|
|
en underkomponent som ikke automatisk arver overordnet komponents
|
|
|
|
|
|
state, måtte tres eksplisitt gjennom).
|
|
|
|
|
|
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: migrasjon
|
|
|
|
|
|
053 mot ekte `teecup_db`, `docker compose up -d --build teecup_api
|
|
|
|
|
|
teecup_frontend`, begge containere boot-et rent, `/health`/
|
|
|
|
|
|
`/dashboard` → 200.
|