Søk-og-koble-UI-en for GolfAPI (ADR-064) var kun koblet inn i "endre
bane"-skjemaet, ikke i /my-rounds/new (components/ny-runde/) -- den
faktiske primære inngangen. Lagt til der (international-search-
delstilstand + InternationalSearch-komponent).
Fant og fikset en reell 500-feil under verifisering: GET
/personal-courses/{personal_course_id} var registrert før den nye,
mer spesifikke GET /personal-courses/international-search -- FastAPI
matcher ruter i registreringsrekkefølge, så den generiske ruten fanget
"international-search" som en ugyldig UUID. Flyttet de nye
endepunktene foran.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
10805 lines
688 KiB
Markdown
10805 lines
688 KiB
Markdown
# 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.
|
||
- **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.
|
||
- **Score-fanens gjennomgang endte i to runder etter samme rapport
|
||
(2026-08-03):** bruker sendte skjermbilde av det brede
|
||
ScorecardGrid-et og påpekte at det ikke var intuitivt hvor man skal
|
||
trykke for å registrere score. Første prompt til V0 var snevert
|
||
(kun synlig trykk-affordance på tomme celler). Bruker ba om en
|
||
friere runde 2: "Kan det være mulig å presentere scoreføringsvinduet
|
||
på en helt annen måte, uten at informasjon [...] blir borte" +
|
||
"det ser generelt ikke særlig bra ut på mobil [...] tar for stor
|
||
plass i bredden. Gi V0 et friere spillerom." Ny, åpen prompt skrevet
|
||
(mål+datainventar-sjekkliste i stedet for layoutdiktat, eksplisitt
|
||
IKKE rørt ved den allerede gode fullskjerm-registreringsveiviseren
|
||
-- kun oversiktsgridet). Grundig kodeutforskning FØR prompten ble
|
||
skrevet bekreftet at `ScoringWizard` (fullskjerm-veiviser) allerede
|
||
matcher spec §2 og ikke skulle røres, og at `ScorecardGrid` (§1) sin
|
||
hver-celle-er-en-knapp-logikk allerede fungerte -- kun tomme cellers
|
||
manglende visuelle "trykk her"-signal var det faktiske problemet i
|
||
runde 1.
|
||
**V0-eksport "zip 27" mottatt og portet, LIVE (2026-08-03):**
|
||
`components/score-overview.tsx` fra zip-en var en velfungerende,
|
||
selvstendig mock-datavisning (hull-fokuserte spillerkort, dashet
|
||
"+"-affordance for uregistrerte hull, sammenleggbart fullt
|
||
scorekort, hurtig-hopp-hullstripe) som eksplisitt respekterte
|
||
omfanget -- V0 gjenkjente selv at registreringsvinduet ikke skulle
|
||
røres og mocket kun et representativt stedfortreder-vindu for det.
|
||
Portet MANUELT mot ekte data i stedet for å ta filen direkte (samme
|
||
rutine som alltid): ny `PlayerHoleCards`-komponent i
|
||
round-detail.tsx gjenbruker ekte `Player`/`ApiHole`/`ApiFormatResult`
|
||
-typene, ekte `goPrev`/`goNext` (wrap-around, ikke V0s klampede
|
||
prev/next), ekte `sumForPlayer`/`stablefordPoints`/`golfTermForScore`
|
||
-hjelpere, og EKSAKT samme match-fargelegging/ikke-tellende-partner-
|
||
logikk som `ScorecardGrid` allerede brukte (`formatResult.holes[].
|
||
entries[].counted`) -- ingen egen reimplementert kopi av denne
|
||
logikken. `ScorecardGrid` selv er UENDRET, bare flyttet til en ny
|
||
"Vis hele scorekortet"-bryter (default lukket) i stedet for å være
|
||
standardvisningen -- ingen informasjon fjernet, kun omprioritert.
|
||
`ScorecardCell` fikk en ny valgfri `size`-prop ("sm"/"lg") for å la
|
||
det store spillerkortet gjenbruke SAMME form-/fargekomponent som det
|
||
kompakte gridet, i stedet for en divergerende kopi.
|
||
**Én reell avviksfeil funnet og rettet FØR utrulling:** V0s
|
||
hurtig-hopp-hullstripe brukte `size-9` (36px) -- under CLAUDE.md sitt
|
||
ufravikelige 44px-trykkgulv. Rettet til `size-11` FØR
|
||
browserverifisering, ikke bare typesjekk.
|
||
**Scratch-/browserverifisert grundig** (isolert DB+MinIO+API+
|
||
frontend-container, samme mønster som alltid) på en ekte 390px mobil
|
||
viewport: singel-runde (registrerte hull vs. dashet "+"-affordance
|
||
på uregistrert hull, netto/Stableford-poeng, Ut/Inn/Sum),
|
||
fourball-runde (lag-farget venstrekant, "Vant hullet"-merke lest
|
||
direkte fra ekte `formatResult`, ikke tie-brutt reimplementering),
|
||
ekte trykk-gjennom til den URØRTE `ScoringWizard`-en bekreftet (skår
|
||
registrert, veiviseren avanserte automatisk til neste spiller, kortet
|
||
bak oppdaterte seg live), og "Vis hele scorekortet" bekreftet å vise
|
||
det uendrede gamle gridet korrekt. "Spillere og runde"-fanen
|
||
(urørt) verifisert uendret.
|
||
**Rullet ut live 2026-08-03**, ren frontend-endring (ingen
|
||
migrasjon), bruker bekreftet eksplisitt før utrulling. Zip-en slettet
|
||
etter merge, per etablert rutine.
|
||
**Bevisst utenfor omfang, ikke bygget:** delt-ball-formater
|
||
(foursome/greensome/scramble/chapman -- `SideScorecardGrid`) har
|
||
ingen tilsvarende kortvisning ennå -- V0-mockupen dekket kun
|
||
spiller-rad-tilfellet (`ScorecardGrid`), og det fantes ingen
|
||
tilsvarende referanse å bygge en "side-kort"-variant mot. Fortsatt
|
||
det brede gridet for de formatene, uendret.
|
||
- **Fem separate brukerpunkter fra samme økt, ALLE BYGGET, SCRATCH-
|
||
VERIFISERT OG LIVE (2026-08-03), samme dag som score-kortrunden
|
||
over:**
|
||
1. **Ekte bug funnet via opplastet skjermopptak, rettet:** brukeren
|
||
rapporterte at "snarveien" (Administrer-dialogens "Spillere og
|
||
runde"-lenke, `?tab=manage`) ikke virket fra Score-fanen, men
|
||
virket fra Scorekort/Leaderboard. Video analysert bilde for bilde
|
||
(ffmpeg) -- bekreftet presist: URL-en endret seg riktig til
|
||
`?tab=manage`, men selve fanen ble stående på "Score". Rotårsak:
|
||
`useState(initialTab)` i round-detail.tsx leste kun
|
||
`useSearchParams()` ved FØRSTE mount -- en navigasjon FRA Score-
|
||
siden til seg selv med kun en ny query-parameter er ingen ekte
|
||
remount i Next.js App Router, så verdien ble aldri regnet på nytt.
|
||
Fra Scorekort/Leaderboard virket det fordi det ER en remount
|
||
(annen rute). Fikset med en `useEffect` som reagerer på selve
|
||
parameter-ENDRINGEN (ensrettet -- hopper TIL "manage", tvinger
|
||
aldri tilbake til "score"). Reprodusert i scratch FØR fiksen
|
||
(bekreftet feilen), verifisert etterpå fra alle tre
|
||
inngangspunkter (Score/Scorekort/Leaderboard) + at lokal
|
||
"Score"-pille fortsatt virker uendret.
|
||
2. **`EditRoundPanel` sin spilleform-bryter utvidet fra 3 til 16
|
||
format:** var blitt hengende igjen på stroke/match/stableford fra
|
||
FØR de åtte nye formatene (ADR-039) ble bygget -- en ren
|
||
forglemmelse. Rettet BÅDE i backend (`RoundUpdate.play_format`
|
||
i rounds.py hadde samme gamle 3-verdis-Literal) OG frontend (ny
|
||
`EDITABLE_PLAY_FORMATS`-liste, samme etiketter som new-round.tsx).
|
||
Verifisert live i scratch: byttet en pågående runde fra Slagspill
|
||
til Fourball midt i runden, `SidesPanel` sin eksisterende "opprett
|
||
sider først"-melding tok over korrekt uten noen krasj.
|
||
3. **`completed_at`-feltet for å overstyre fullført-tidspunkt manuelt
|
||
bekreftet FORTSATT TIL STEDE, uendret siden 2026-07-24** -- ren
|
||
kodeverifisering, ingen endring nødvendig. Feltet vises (med
|
||
hensikt) kun ETTER at "Fullfør runde" er trykket -- forvirringen
|
||
var trolig et resultat av bug (1) over (kom seg aldri til
|
||
"Spillere og runde" for å finne knappen).
|
||
4. **`round-scorecard.tsx` sitt individuelle scorekort: kolonnetekst-
|
||
overflow rettet + 8 nye detaljrader lagt til** (Fairway/Putt/
|
||
GIR/Innspill/Chip/Bunkerslag/Straffeslag/Anywayslag), alle med
|
||
forkortede radetiketter + kompakte lucide-ikoner (bøyde piler for
|
||
Fairway, rette piler+blink for Innspill, sjekkmerke for GIR) --
|
||
samme golfscore-språk som resten av appen. Data var allerede
|
||
tilgjengelig i `RoundHoleOut` (bare ikke lest av denne siden før).
|
||
Vises kun når minst ett hull faktisk har dataen (samme "vis kun
|
||
det som finnes"-prinsipp). GIR-formelen gjenbruker EKSAKT samme
|
||
`score - putts <= par - 2`-regel som round-stats.tsx/round-
|
||
detail.tsx allerede bruker.
|
||
5. **Rundeleaderboard (V0-eksport "zip 28") -- IKKE V0 sin skyld at
|
||
forrige versjon "så rart sammenskrudd ut", presisert til bruker:
|
||
forrige leaderboard var HÅNDKODET (ikke V0), bygget 2026-07-26 da
|
||
brukeren gikk tom for V0-credits. Zip 28 dekket alle hullene i
|
||
V0-prompten (skrevet samme økt) presist -- portet MANUELT mot ekte
|
||
data, IKKE en full filerstatning: gjenbrukte eksisterende
|
||
`rankEntries`/`ValueMark`/`RankBadge`/`ModeToggle`/`HoleStrip`/
|
||
matchstatus-tug-of-war-baren (allerede nær identisk med V0s egen
|
||
visuelle idé, lavere risiko å beholde enn å skrive om). Kun TRE
|
||
genuint manglende/feil biter bygget:
|
||
- **High-low-high** falt tidligere inn i den generiske to-sidede
|
||
"X UP"-matchstatusen (bekreftet FEIL for dette formatet -- HLH
|
||
er 0-2 poeng-per-hull, ikke hull-ledelse). Ny `HighLowSection`:
|
||
lav/høy-duell-rollen per spiller per hull utledes fra
|
||
`entries[].points` (allerede MIN/MAKS av lagkameratenes
|
||
Stableford-poeng den hullet, samme regel som
|
||
`handicap_engine.py` sin `high_low_high_points_for_hole` --
|
||
ingen backend-endring trengtes). Verifisert tall-for-tall i
|
||
scratch: per-hull lav/høy-dueller summerte EKSAKT til
|
||
`hlh_points_a`/`hlh_points_b` sine totaler.
|
||
- **Bingo Bango Bongo/Københavner** sitt "Poeng"-tall var
|
||
`ApiLeaderboardEntry.total_points` -- ALLTID generisk Stableford
|
||
fra backend, uansett format (bekreftet reell bug: disse to
|
||
formatene har sine EGNE poengsystem, aldri vist noe sted).
|
||
Erstattet med faktiske `bbb_points`/`copenhagen_points` fra
|
||
format-result FØR rangeringen bygges. Fanget og rettet EN egen
|
||
bug UNDER scratch-verifiseringen: en deltaker med 0 ekte
|
||
BBB/Københavner-poeng manglet fra dict-en (ikke `0`, fraværende
|
||
nøkkel) -- første versjon falt da feilaktig tilbake til generisk
|
||
Stableford i stedet for `0`. Rettet, verifisert: Københavner-
|
||
poeng summerte EKSAKT til 6 per hull (regelen), BBB viste riktig
|
||
`0` for en spiller uten noen bingo/bango/bongo-vinst.
|
||
- **Shamble/Money Ball** sitt offisielle LAGRESULTAT (hele runden
|
||
= ett lag) lå tidligere IKKE synlig noe sted -- kun spillernes
|
||
råtall. Ny `TeamResultSection`: lagets til-par (fra
|
||
`shamble_team_score`/`money_ball_team_score` + hullenes par-sum,
|
||
begge allerede i format-result), pluss en NY flight-gruppe-
|
||
sammenligning (henter søsken-rundenes EGNE format-result via et
|
||
nytt `useFlightTeamStandings`-kall) mot andre lag i samme utgang
|
||
-- tidligere fantes INGEN slik sammenligning i det hele tatt.
|
||
Verifisert tall-for-tall i scratch (Shamble beste-2-av-3, Money
|
||
Ball sin rotasjon) mot to separate flight-koblede runder.
|
||
`RoundLeaderboardMini` (forhåndsvisningen i "Spillere og runde")
|
||
oppdatert til å skjule seg for Shamble/Money Ball også (samme
|
||
"ikke vis noe fremfor å vise en misvisende rangering"-prinsipp den
|
||
allerede fulgte for to-sidede format).
|
||
Regresjonstestet: vanlig Slagspill og Fourball uendret (identisk
|
||
visning/matchstatus som før refaktoreringen som lot
|
||
`MatchStatusSection` motta `result` som prop i stedet for å hente
|
||
selv).
|
||
**Rullet ut sammen med de fire punktene over, 2026-08-03**: BÅDE
|
||
`teecup_api` (RoundUpdate.play_format-utvidelsen) OG
|
||
`teecup_frontend` bygget og restartet, begge boot-et rent,
|
||
bekreftet via kompilert bundle-grep + en faktisk backend-
|
||
spørring mot den nye Literal-en. Zip-en slettet etter merge.
|
||
- **Rundeleaderboard (zip 28) — punkt 5 over ERSTATTET, full omskriving
|
||
(2026-08-03, samme dag):** brukeren testet en vanlig Slagspill-runde
|
||
(den klart vanligste stien) etter forrige punkts utrulling og så
|
||
NULL visuell endring, sendte skjermdump og spurte hva som hadde
|
||
skjedd. Årsak: forrige punkt gjenbrukte bevisst de gamle håndkodede
|
||
`RankBadge`/`ValueMark`/`ModeToggle`/`HoleStrip`/matchstatus-baren for
|
||
individuell/to-sidet visning fordi jeg vurderte dem som "strukturelt
|
||
nære nok" V0s tegning — akkurat den stien brukeren testet var derfor
|
||
uendret. Brukerens eksplisitte, ordrette svar (bevart for ettertiden):
|
||
*"Ja, jeg vil ABSOLUTT at du implementerer V0 sin tolkning av hvordan
|
||
Leaderboardet skal se ut. Jeg vil ikke at du skal gjøre personlige
|
||
endringer eller holde tilbake på noe i det hele tatt når det gjelder
|
||
dette."* Lagret som stående feedback-memory (`v0-full-fidelity`) —
|
||
gjelder ALL fremtidig V0-integrering i dette prosjektet, ikke bare
|
||
denne filen.
|
||
Fulgte opp med en fullstendig omskriving av `round-leaderboard.tsx`,
|
||
denne gangen med HVER V0-komponent portet manuelt mot ekte data (ikke
|
||
gjenbrukt): `Badge`/`MetricPill`/`ScoreMark`+`classify`/
|
||
`IndividualBoard`/`MetricToggle`/`HoleByHole`/`Legend` (individuelt),
|
||
`TwoSidedBoard`/`SideName`/`HoleWinners` (to-sidet), `HighLowBoard`/
|
||
`DuelRow` (High-low-high), `TeamFlightBoard` (Shamble/Money Ball) — de
|
||
gamle håndkodede visningskomponentene slettet i sin helhet, ikke
|
||
beholdt ved siden av.
|
||
Fem reelle bugs funnet og rettet UNDER omskrivingen (ikke antatt
|
||
riktig fra en ren typesjekk alene — funnet ved skjermdump-inspeksjon i
|
||
scratch):
|
||
1. `courseHcp` var meningsløs plassholder-matematikk
|
||
(`strokes_received * 18` uansett hva slagindeksen faktisk var) —
|
||
V0s mockup antok klientside-utregning av spillehandicap, men
|
||
backend returnerer allerede korrekt per-hull `strokes_received`
|
||
direkte. Rettet: `IndividualPlayer` fikk et ekte
|
||
`strokesReceived`-array lest direkte derfra, netto/Stableford
|
||
regnet fra DET, ikke gjenutledet.
|
||
2. `hcpIndex` var hardkodet `null` alltid — feltet
|
||
(`handicap_index_snapshot`) fantes allerede i det hentede
|
||
`/rounds/{id}`-svaret, bare ikke lest av filens egen, snevrere
|
||
lokale type. Utvidet typen, lest verdien.
|
||
3. `TeamFlightBoard` sin råscore-pille hadde et komma-operator-uttrykk
|
||
som alltid evaluerte til hardkodet `0` uansett faktisk score.
|
||
Rettet med den allerede korrekte `grossToPar(m, holes)`.
|
||
4. CSS-trunkeringsbug i `SideName`: det ledende laget sin flagg-
|
||
ikonrad manglet `min-w-0` på det indre flex-elementet, så navnet
|
||
("Lag Bjørk") trunkerte til nesten ingenting ("L..") mens det ikke-
|
||
ledende laget viste seg fullt, selv i like brede `flex-1`-
|
||
containere. Rettet.
|
||
5. Rotårsaken til (4) var egentlig et feil datavalg: brukte
|
||
`match_status_text` (en backend-streng som ALLEREDE inkluderer
|
||
lagnavnet, designet for en annen UI-plassering) i V0s
|
||
overskrifts-slot, som var designet for en mye kortere tekst siden
|
||
lagnavn vises separat via `SideName`. Rettet ved å utlede V0s
|
||
egen korte frase ("2 opp"/"Dormie 2 opp"/"Ferdig 2&1"/"Vant 2
|
||
opp"/"Delt"/"Alt likt") direkte fra de rå numeriske feltene
|
||
(`match_lead`/`match_holes_played`/`match_holes_remaining`/
|
||
`match_is_dormie`/`match_is_closed`) — fortsatt serverens
|
||
autoritative tall, bare riktig tekstformat.
|
||
**Scratch-verifisert på alle åtte formater** (Slagspill, BBB,
|
||
Københavner, High-low-high, Fourball, Shamble, Money Ball, pluss
|
||
`RoundLeaderboardMini`-regresjon) med tall håndverifisert mot samme
|
||
testdata som resten av ADR-039-arbeidet — se punkt 5 over for selve
|
||
tallutregningen, uendret av denne omskrivingen. **Rullet ut live
|
||
2026-08-03** (kun `teecup_frontend` bygget/restartet — backend-
|
||
endringen fra punktene over var allerede live). Scratch-miljøet
|
||
(DB/rolle/MinIO/API-/frontend-containere) ryddet opp etter bruk.
|
||
|
||
Neste steg:
|
||
0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet
|
||
(2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen
|
||
konsoll-feil. Ikke hvert enkelt interaksjonsdetalj (f.eks. gjeste-
|
||
kjønnsendringens reaktive utslagsfilter) klikket gjennom stykke for
|
||
stykke, men grunnleggende rendring/lasting er bevist, ikke lenger
|
||
bare typesjekket.
|
||
0b. **Rundeleaderboard — nå FAKTISK nettleser-bekreftet (2026-07-27):**
|
||
brutto/netto/poeng-veksling testet direkte i nettleseren, viste
|
||
korrekte tall og riktig form/farge-språk.
|
||
1. **Ferdig, kun for historikk:** dashbord-redesign (ADR-035) og
|
||
venner/kategorisert deling fase 1 (ADR-036) — begge designet
|
||
2026-07-25 og siden BYGGET, SCRATCH-VERIFISERT OG RULLET UT LIVE
|
||
samme dag (se status over). Venner fase 2 (rundevisibilitet) og
|
||
fase 3 (ekte medspillere) er fortsatt ikke bygget.
|
||
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),
|
||
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.
|
||
~~**Reell, fortsatt åpen gap (urelatert til de åtte formatene):**
|
||
organisator-vendt opplasting av hero-/sponsorbilder for
|
||
turnering-landingssiden (ADR-018) — backend/lagring finnes, ingen
|
||
dra-og-slipp-skjerm bygget ennå.~~ — **lukket samme dag, se punkt 16.**
|
||
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.
|
||
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.
|
||
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.
|
||
18. **Augusta-stil resultattavle for slagspill-turneringer (POS/PLAYER/
|
||
TODAY/THRU/TOTAL/R1-Rn) — BYGGET, SCRATCH-VERIFISERT OG LIVE
|
||
2026-08-04, ingen migrasjon.** Brukeren viste et bilde av en fysisk
|
||
leaderboard-tavle fra Augusta National og ba om samme kolonneoppsett
|
||
for individuelle turneringer med brutto/netto/stableford-scoring —
|
||
med lederen alltid øverst, valgfri automatisk rulling, og eksplisitt
|
||
krav om at det skal se bra ut på storskjerm (ikke bare mobil). Bygget
|
||
via V0 (zip 29, `stroke-play-leaderboard.tsx`) etter etablert mønster:
|
||
ren visuell komponent med mock-data først, ekte backend-kobling
|
||
etterpå.
|
||
**V0-prompten** (skrevet FØR eksporten) spesifiserte eksplisitt at
|
||
fargevalget (grønn=under par/oransje=over par) skal følge appens EGEN
|
||
etablerte konvensjon, IKKE referansebildets amerikanske rød-for-
|
||
under-par-tradisjon — V0 traff dette presist. Eksporten matchet
|
||
datakontrakten i prompten nesten ordrett (`RoundCell`/`LeaderboardRow`
|
||
-typene er identiske), inkludert korrekt implementert lederrad
|
||
fastspent i gull, av/på-knapp for automatisk rulling som respekterer
|
||
`prefers-reduced-motion`, frossen POS/PLAYER-kolonne på mobil, og en
|
||
egen, betydelig større "storskjerm"-skalering (`lg:`-brekkpunkt) --
|
||
første skjerm i appen bygget eksplisitt for dette. **Én reell,
|
||
forventet justering gjort ved integrering** (prompten dekket ikke
|
||
dette presist nok selv): rundekolonnenes eksakt-par-tilfelle
|
||
("E") fikk feilaktig samme kvadrat+oransje-behandling som over par --
|
||
rettet til appens egen "E → ren tekst, ingen ramme"-regel
|
||
(DESIGN_SYSTEM.md) ved å utvide `RoundCell.isUnderPar: boolean|null`
|
||
til en eksplisitt `tone: "under"|"even"|"over"|null`, bekreftet
|
||
visuelt riktig i scratch (72 mot par 72 vises nå som ren tekst, ingen
|
||
ramme).
|
||
**Backend** (`individual_tournaments.py`): ingen skjemaendring --
|
||
`individual_leaderboard`-endepunktet utvidet med nye, valgfrie felt
|
||
(`position`/`is_leader`/`today_label`/`thru_label`/`total_label`/
|
||
`rounds[]`), KUN populert for scoring_method brutto/netto/stableford
|
||
(uendret `null`/tom liste for København/BBB, som fortsatt bruker den
|
||
enkle listevisningen). "I dag" (TODAY/THRU) utledes som runden med
|
||
høyest sekvensnummer som NOEN deltaker har påbegynt (bekreftet med
|
||
bruker: ingen eksplisitt "aktiv runde"-markering finnes eller trengs
|
||
-- runder spilles sekvensielt i praksis). Par-for-spilte-hull regnes
|
||
per (runde, deltaker) via en ny korrelert subquery mot
|
||
`tournament_round_hole`, gjenbruker de allerede cachede
|
||
`gross_total`/`net_total`/`stableford_points`-verdiene fra
|
||
`tournament_round_score` uendret.
|
||
**Reell rangeringsbug funnet OG rettet UNDER selve scratch-
|
||
verifiseringen** (ikke antatt riktig fra koden alene): den
|
||
EKSISTERENDE sorteringslogikken (uendret siden ADR-037) rangerte på
|
||
RÅ `gross_total`/`stableford_total` -- riktig for en enkelt-rundes
|
||
sammenligning, men i en flerrunde-turnering der deltakere har spilt
|
||
ULIKT antall hull til enhver tid, er rå sum ikke sammenlignbar (en
|
||
deltaker med kun 18 hull spilt fikk et lavere rått slagtall enn
|
||
deltakere med -5 til par over 27-36 hull, og rangerte dermed FORAN
|
||
dem som leder — funnet ved at test-scriptets egne håndregnede
|
||
assert-sjekker feilet). Rettet ved å innføre en til-par-normalisert
|
||
rangeringsnøkkel (`gross_total/net_total − par_for_spilte_hull`,
|
||
negert for stableford siden flere poeng er bedre der) brukt BÅDE til
|
||
sortering og uavgjort-deteksjon, i stedet for rå totaler. Ny
|
||
`entries.sort(...)`-plassering flyttet inn i denne funksjonen
|
||
(erstatter den gamle grenen kun for disse tre metodene — København/
|
||
BBB/default-brutto beholder sin uendrede, allerede riktige rå-sum-
|
||
sortering, siden alle deltakere der alltid har likt antall
|
||
hull/runder tilgjengelig i de formatene).
|
||
**Frontend** (`individual-tournament-detail.tsx`): `LeaderboardTab`
|
||
grener nå på `scoring_method` -- `StrokePlayLeaderboard` (ny
|
||
komponent) for brutto/netto/stableford, uendret gammel flat/klasse-
|
||
gruppert liste for København/BBB/Flagg. Klasse-gruppering (2026-08-04
|
||
tidligere samme dag) virker uendret -- komponenten instansieres én
|
||
gang per klasse-seksjon, akkurat som den gamle listen ble.
|
||
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
||
engangs API-/frontend-container, ekte nettleser-innlogging inkl.
|
||
2FA): en 4-spiller, 2-rundes turnering med et bevisst konstruert
|
||
scenario -- ekte uavgjort i toppen (to spillere -5 til par, ulikt
|
||
antall hull spilt hver), én spiller midt i runde 2 (thru 9), én
|
||
spiller som IKKE hadde startet runde 2 i det hele tatt, én ferdig
|
||
begge runder. ALLE håndregnede tall (POS med "T1"-uavgjort, TODAY,
|
||
THRU inkl. "F"/"-"/hull-tall, TOTAL, R1/R2 med riktig form+farge per
|
||
Golfscore-språket) bekreftet eksakt riktige, både via rå API-JSON og
|
||
i ekte nettleser på BÅDE 390px mobil (frossen POS/PLAYER, resten
|
||
skrollbar) og 1920px storskjerm (alt synlig uten skrolling, betydelig
|
||
større tekst). Rull-automatisk-knappen bekreftet å veksle av/på.
|
||
Regresjonstestet: en BBB-turnering bekreftet fortsatt å returnere
|
||
tomme/`null`-verdier for de nye feltene (gammel visning uendret,
|
||
ingen ny kode-vei berørt for de formatene). 98/98 eksisterende
|
||
`handicap_engine.py`-enhetstester uendret (ingen motorendring), ekte
|
||
typesjekket produksjonsbuild.
|
||
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: INGEN
|
||
migrasjon (rene response-felt-tillegg), `docker compose up -d --build
|
||
teecup_api teecup_frontend`, begge containere boot-et rent,
|
||
`/health`/`/dashboard` → 200. V0-zip-en slettet etter merge, per
|
||
etablert rutine.
|
||
|
||
19. **Baneoppsett i turneringer: rekkefølge, TeeOff-import og delt
|
||
bane-mal-bibliotek — BYGGET OG SCRATCH-VERIFISERT GRUNDIG 2026-08-04,
|
||
migrasjon 054, IKKE ENNÅ RULLET UT MOT ekte `teecup_db`.** Brukeren
|
||
oppdaget, mens hen brukte den live turneringsmodulen, at "Klasser"
|
||
(med sin Standardutslag-velger, ADR-041) vises FØR banen/runden i det
|
||
hele tatt er satt opp -- meningsløst å velge standardutslag for en
|
||
klasse før man vet hvilke utslag banen har. Bemerket samtidig at
|
||
TeeOff-henting av baner ikke så ut til å være tilgjengelig i
|
||
turneringsmodulen, og ba om en helt ny funksjon: la BÅDE
|
||
turneringsmodulen OG single-runde-modulen tilby "bruk en eksisterende
|
||
bane som mal" (fra TeeOff, eller fra en annens custom-bane) når man
|
||
oppretter en manuell bane.
|
||
|
||
**Del I -- rekkefølge + TeeOff-import-gap (ingen skjemaendring):**
|
||
Explore-agent bekreftet klagen presist og avdekket at det var verre
|
||
for lagturneringer enn antatt: for individuelle turneringer lå
|
||
`ClassesCard` FØR `RoundsCard` i samme Oppsett-fane
|
||
(`individual-tournament-detail.tsx`); for lagturneringer lå Klasser
|
||
på selve FØRSTE fanen ("Lag og spillere"), mens bane-/rundeoppsett
|
||
krevde en hel sidenavigering til "Program". TeeOff offisiell-import
|
||
(ADR-019, ferdig bygget) var KUN koblet til lagturnerings-økt-UI-et,
|
||
fullstendig fraværende fra den individuelle turneringsflyten.
|
||
Fikset: `ClassesCard` flyttet til å rendres ETTER `RoundsCard` i
|
||
individuelle turneringer; Klasser-kortet flyttet fysisk fra
|
||
`tournament-detail.tsx` ("Lag og spillere") til `tournament-program.tsx`
|
||
("Program"), rett etter økt-/rundeoppsettet -- `classes`-state (brukt
|
||
av `TeamPanel` til roster-klassevisning) ble værende i
|
||
`tournament-detail.tsx`, men create/delete-handlerne og selve
|
||
`ClassesCard`-komponentdefinisjonen flyttet med til `tournament-program.tsx`.
|
||
`OfficialCourseSearch`-komponentmønsteret (allerede i
|
||
`tournament-program.tsx`) kopiert inn i `individual-tournament-detail.tsx`
|
||
(samme duplisering-mellom-turneringstype-filer-konvensjon som
|
||
`ClassesCard` allerede fulgte), koblet til `NewRoundForm` sitt
|
||
eksisterende "+ Ny bane"-felt.
|
||
|
||
**Det største funnet (Explore-agent, avdekket FØR bygging):**
|
||
turneringsmodulens "opprett manuell bane" var i praksis ubrukelig --
|
||
KUN et navnefelt (`POST /orgs/{id}/courses` tok bare imot `{name}`),
|
||
ingen vei til hull/par/hcp-indeks/utslag i det hele tatt. To
|
||
sub-ressurs-endepunkter for å legge til dette i etterkant fantes
|
||
(`courses.py` sine `create_tee`/`create_holes`), men hadde ALDRI fått
|
||
noe frontend-kallsted -- funksjonen ble aldri fullført.
|
||
|
||
**Del II -- mal-basert baneoppretting + delt offentlig bane-bibliotek.**
|
||
Et nøkkelfunn forenklet løsningen betraktelig: `personal_course`
|
||
(020_personal_rounds.sql, "frittstående runder") er, til tross for
|
||
navnet, ALLEREDE et globalt, plattform-omfattende bibliotek -- `GET
|
||
/personal-courses` søker på tvers av ALLE brukeres baner uten eier-
|
||
eller org-filtrering, og `POST /personal-courses` tar allerede imot
|
||
full hull-/utslagdata i ett atomisk kall. Ingen ny tabell trengtes --
|
||
kun én kolonne (`forked_from_id`, migrasjon 054) for
|
||
proveniens/attribusjon.
|
||
Tre `AskUserQuestion`-runder avklarte omfanget presist FØR bygging
|
||
(samme disiplin som ADR-037/039/041): (1) ren reorder, ingen
|
||
blokkering; (2) et helt NYTT, kompakt hull-/utslag-editorskjema for
|
||
turneringsmodulen (speiler single-runde-modulens `OwnCreateStep` i
|
||
funksjon, egen stil), med "bruk som mal"-vei fra BÅDE TeeOff og
|
||
offentlig custom-bane; (3) alle tre moduler (individuell,
|
||
lagturnering, frittstående runde) skal ha funksjonen.
|
||
Et oppfølgingsspørsmål fra brukeren ("disse custom banene bør lagres,
|
||
og de må være offentlige... er det noe jeg ikke har tenkt på?") ble
|
||
tatt til en fjerde avklaringsrunde: (a) "offentlig" betyr HELE
|
||
TeeCup-plattformen (ikke bare egen org) -- eneste måte
|
||
single-runde-modulen faktisk kan dele samme mal-bibliotek som
|
||
turneringsmodulen, siden den ikke har noe org-begrep; (b) "Dupliser"
|
||
beholdes som egen, eksplisitt handling i tillegg til at redigering av
|
||
en ANNENS bane automatisk forker en kopi (redigering av DIN EGEN bane
|
||
endrer den i stedet, ingen fork).
|
||
|
||
**Backend:**
|
||
- `054_personal_course_provenance.sql`: `personal_course.forked_from_id`
|
||
(selvreferende FK, `ON DELETE SET NULL`). Ingen RLS-endring --
|
||
tabellen har aldri vært org-scopet (bevisst siden migrasjon 020).
|
||
- `rounds.py`: `PersonalCourseOut`/`PersonalCourseDetail` utvidet med
|
||
`is_mine`/`created_by_display_name` (attribusjon i mal-søket, FULLT
|
||
navn per navneformat-regelen -- dette er en administrasjonsvisning,
|
||
ikke direkte adressering) og (kun Detail) `holes`/`full_tees`/
|
||
`forked_from_id` (forhåndsutfylling). Nye endepunkter: `PATCH
|
||
/personal-courses/{id}` (eier: oppdaterer i sted; IKKE eier: forker
|
||
automatisk -- ett endepunkt dekker begge casene uten at frontend må
|
||
forgrene på eierskap), `POST .../duplicate` (eksplisitt, uavhengig
|
||
av eierskap), `DELETE ...` (kun eier, 403 ellers; fanger opp
|
||
`asyncpg.ForeignKeyViolationError` fra `round.personal_course_id`
|
||
sin allerede-eksisterende `NO ACTION`-FK og gir en vennlig 409
|
||
"IN_USE" -- IKKE via den delte `translate_db_errors()`, som ville
|
||
gitt feil retning/melding for akkurat denne casen). `GET
|
||
/personal-courses` fikk en `mine=true`-parameter (for
|
||
"Mine baner"-administrasjonen, unngår den vanlige 20-treffs-
|
||
begrensningen på navnesøket).
|
||
- `courses.py`: `POST /orgs/{id}/courses` utvidet til valgfritt å ta
|
||
imot samme `holes`/`tees`-form som `PersonalCourseCreate` --
|
||
populert setter den BÅDE org-`course`-raden (med hull/utslag,
|
||
samme SQL-mønster som de eksisterende men ubrukte sub-ressurs-
|
||
endepunktene) OG en tilsvarende `personal_course`-rad (eid av
|
||
innlogget bruker, "offentliggjøringen" brukeren ba om) i SAMME
|
||
transaksjon -- ingen vedvarende kobling mellom de to radene etterpå
|
||
(samme "engangs-kopi"-filosofi som offisiell TeeOff-import,
|
||
ADR-019). `official-import`-endepunktet selv er UENDRET.
|
||
- `rounds.py` sin `GET /rounds/official-search/{slug}` (personlig
|
||
rundemodul) utvidet med fulle `holes`/`full_tees` PER TeeOff-bane
|
||
(kun populert når teeoff-dataene er komplette nok -- samme
|
||
fullstendighetskrav som ADR-019-importen allerede håndhever) --
|
||
nødvendig fordi frittstående runder ALDRI persisterer teeoff-baner
|
||
(ADR-033 Beslutning C, live oppslag), så "bruk som mal" der må få
|
||
dataene tilbake i selve søkesvaret i stedet for å runde-trippe
|
||
gjennom en lagret rad slik org-siden kan.
|
||
|
||
**Frontend:**
|
||
- Ny delt fil `course-template-editor.tsx`: `CourseTemplateEditor`
|
||
(kompakt hull-/utslag-skjema, forhåndsutfyllbar via `initial`-prop,
|
||
ren UI + lokal validering -- kalleren avgjør hvor data persisteres,
|
||
matcher prosjektets etablerte adaptermønster) + `CourseTemplatePicker`
|
||
(velger: "Fra bunnen av" / "TeeOff-bane som mal" / "Offentlig bane
|
||
som mal" -- sistnevnte med BÅDE "Bruk direkte" og "Tilpass før
|
||
bruk"). Brukt fra `tournament-program.tsx` (både ny økt OG endre
|
||
eksisterende økts bane) og `individual-tournament-detail.tsx`
|
||
(`NewRoundForm`).
|
||
- `new-round.tsx`: `OwnCreateStep` utvidet med valgfrie
|
||
`initial`/`forkedFromId`-props (samme prefyll-mekanisme). Nye
|
||
"Bruk som mal for egen bane"-knapper i BÅDE `CourseList` (TeeOff-
|
||
baner, kun synlig når komplette maldata finnes) og `OwnSearch`
|
||
(offentlige custom-baner, nå med attribusjon "Opprettet av X" /
|
||
"Opprettet av deg" -- samme navneformat-regel som backend).
|
||
- `account-settings.tsx`: ny "Mine baner"-seksjon (`MyCoursesSection`)
|
||
-- liste over EGNE `personal_course`-rader (`?mine=true`), med
|
||
Rediger (gjenbruker `CourseTemplateEditor`, PATCH), Dupliser
|
||
(POST duplicate) og Slett (DELETE, med vennlig 409/i-bruk-melding)
|
||
per rad. Andres baner vises IKKE her -- kun i mal-søket i de
|
||
respektive modulene.
|
||
|
||
**Reell bug funnet og rettet UNDER scratch-verifisering** (ikke
|
||
antatt riktig fra koden alene): `CourseTemplateEditor` ble først
|
||
bygget med sitt eget `<form onSubmit>` -- men komponenten rendres
|
||
INNI et allerede eksisterende `<form>` (`NewRoundForm`/
|
||
`CreateSessionCard`), og nestede `<form>`-elementer er ugyldig HTML.
|
||
"Opprett bane"-knappen submittet i praksis det YTRE runde-/økt-
|
||
skjemaet i stedet, en ekte side-navigasjon som vasket bort
|
||
`?org=...`-parameteren fra URL-en -- funnet ved en ekte klikk-
|
||
gjennom-test i nettleser (fetch-kallet nådde aldri serveren, bekreftet
|
||
ved å sjekke databasen). Samme feilklasse som `OfficialCourseSearch`
|
||
allerede hadde en kommentar om fra en tidligere runde. Rettet: `<form>`
|
||
→ `<div>`, `type="submit"` → `type="button"` med eksplisitt
|
||
`onClick`-håndtering.
|
||
|
||
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
||
isolert scratch-MinIO + engangs API-/frontend-container, ekte
|
||
nettleser-innlogging som TO forskjellige brukere på tvers av TO
|
||
forskjellige organisasjoner):
|
||
- API-nivå: eget Python-testskript, 34/34 håndregnede sjekker bestått
|
||
(publisering-ved-opprettelse, kryss-bruker/kryss-org synlighet,
|
||
attribusjon, fork-ved-ikke-eier-redigering med verifisert uendret
|
||
original, eier-redigering-i-sted, eksplisitt duplisering,
|
||
eierskaps-gatede 403-er, og slette-blokkert-mens-i-bruk med riktig
|
||
409/IN_USE-kode).
|
||
- Ekte nettleser: rekkefølge-fiksen bekreftet i begge turneringstyper;
|
||
"Opprett bane med hull/utslag" fra bunnen av OG fra TeeOff-mal
|
||
(ekte Bergen Golfklubb-data, redigert par på hull 1 før lagring,
|
||
bekreftet BÅDE org-`course`- og `personal_course`-raden ble
|
||
opprettet riktig i databasen); den nye offentlige banen dukket
|
||
umiddelbart opp i single-runde-modulens banesøk MED riktig
|
||
attribusjon; "bruk som mal"-broen fra der forhåndsutfylte
|
||
`OwnCreateStep` korrekt fra en ANNEN brukers bane og forket en ny,
|
||
egen-eid rad ved lagring (bekreftet i databasen: ny rad eid av
|
||
innlogget bruker, `forked_from_id` pekende på originalen, originalen
|
||
selv uendret); "Mine baner" i kontoinnstillinger bekreftet å vise
|
||
KUN egne baner, Rediger (i sted, verifisert i databasen), Dupliser
|
||
og Slett (inkl. `confirm()`-dialoghåndtering) alle fungerende.
|
||
- `test_isolation.sql` 12/12 bestått (additiv migrasjon, ingen
|
||
RLS-endring). Ekte typesjekket produksjonsbuild (`docker build`,
|
||
samme steg som `Dockerfile` faktisk bruker) kjørt flere ganger
|
||
gjennom byggerunden, null TypeScript-feil.
|
||
Scratch-miljøet (database, rolle, MinIO, containere, images,
|
||
midlertidige hemmeligheter) fullstendig ryddet opp etter
|
||
verifisering.
|
||
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt ("Ja
|
||
takk"): migrasjon 054 kjørt mot ekte `teecup_db` (kun
|
||
`forked_from_id`-kolonnen, bekreftet tom/nullable, ingen
|
||
databrudd), `docker compose up -d --build teecup_api
|
||
teecup_frontend`, begge containere boot-et rent, `/health` og
|
||
`/dashboard` → 200 over https.
|
||
|
||
20. **Order of Merit (sesong-sammenlagt rangering på tvers av
|
||
turneringer) — BYGGET, GRUNDIG SCRATCH-VERIFISERT OG LIVE 2026-08-04
|
||
(ADR-043, migrasjon 055).**
|
||
Brukeren ba om en vurdering av GolfBox sin Order of Merit-funksjon
|
||
(opplastet PDF) og tok deretter designet til en full plan-runde (to
|
||
`AskUserQuestion`-runder + et oppfølgingsspørsmål som avklarte at
|
||
"lag" i en OOM er et sesong-par satt opp DIREKTE i OOM-en, ikke
|
||
hentet fra noen turnering -- unngår GolfBox sin egen identifiserte
|
||
svakhet, strengmatching på lagnavn, siden TeeCup allerede har ekte
|
||
`player_id`-identitet på tvers av organisasjonen).
|
||
|
||
**Skjema** (migrasjon 055): `order_of_merit`/`order_of_merit_link`/
|
||
`order_of_merit_team`/`order_of_merit_team_member`, org-isolert RLS
|
||
(samme mønster som migrasjon 040/053). To uavhengige akser --
|
||
`result_type` (poeng-etter-plassering/Stableford-sum/brutto/netto/
|
||
pengeliste, HVA som telles) og `aggregation_mode` (sum/snitt/
|
||
eclectic, HVORDAN det summeres over en sesong).
|
||
|
||
**Motor** (`handicap_engine.py`): `order_of_merit_points_for_position`
|
||
(uavgjort deler SAMME poengverdi ved sin felles plassering, ikke
|
||
gjennomsnitt) og `order_of_merit_aggregate` (sum/snitt, valgfri
|
||
behold-N-beste, "best" er alltid størst-er-bedre -- kalleren negerer
|
||
brutto/netto-verdier). 9 nye enhetstester skrevet FØR resten av
|
||
bygget, alle grønne (107/107 totalt i `test_handicap_engine.py`).
|
||
|
||
**Backend** (nytt `app/routers/order_of_merit.py`): full CRUD for
|
||
selve OOM-en og dens lenkede turneringer, pluss et "regn ut ved
|
||
lesing"-leaderboard-endepunkt (samme filosofi som Nassau/
|
||
High-low-high -- ingen cache-tabell). `_compute_individual_standings`
|
||
faktorisert ut av `individual_tournaments.py` sin `individual_
|
||
leaderboard` (som nå kun er en tynn wrapper rundt den) for gjenbruk
|
||
her uten å duplisere posisjon-/til-par-beregningen -- `LeaderboardEntry`
|
||
fikk samtidig et nytt `player_id`-felt (manglet fra før, kun
|
||
`tournament_participant_id` fantes, utilstrekkelig for å summere én
|
||
ekte spiller på tvers av FLERE turneringers deltaker-rader).
|
||
`kind`/`result_type` låses etter opprettelse (PATCH-endepunktet
|
||
eksponerer dem bevisst ikke i det hele tatt). Denne runden dekker
|
||
spiller-OOM med alle fem resultattyper, sum/snitt-aggregering, og
|
||
behold-N-beste/minimum-resultater/aldersgrense-grenser (fødselsår
|
||
hentet fra `player.birth_date`, migrasjon 007). Eclectic-aggregering
|
||
og lag-OOM sin faktiske leaderboard-beregning er bevisst IKKE bygget
|
||
denne runden (schema og CRUD for lag finnes, men `GET .../leaderboard`
|
||
avviser eksplisitt `kind='team'` med en tydelig feilmelding) -- se
|
||
FEATURE_BACKLOG.md.
|
||
|
||
**Frontend** (`order-of-merit-list.tsx`/`order-of-merit-detail.tsx`,
|
||
nye ruter under `/organizations/[id]/order-of-merit`): håndkodet, IKKE
|
||
via V0 -- brukeren gikk tom for V0-credits midt i planleggingen av
|
||
frontend-tilnærmingen og ba om at det håndkodes "så lekkert som V0
|
||
får til" i stedet. Bygget til å gjenbruke etablerte visuelle mønstre
|
||
presist (samme header-/kort-stil som `org-members.tsx`, samme
|
||
gull-ledertrøye-behandling -- `bg-gold`/`text-gold-foreground` -- som
|
||
`stroke-play-leaderboard.tsx`). Detaljsiden har fire seksjoner i en
|
||
bevisst rekkefølge (lenkede turneringer → innstillinger →
|
||
resultatliste, siden resultater ikke gir mening før turneringer er
|
||
lenket), inkl. en egen "Ikke kvalifisert"-seksjon for spillere
|
||
ekskludert av aldersgrense/minimum-resultater med forklarende tekst
|
||
per rad. Ny inngangslenke lagt til i `org-members.tsx` (ingen egen
|
||
"org-hub"-side fantes fra før -- medlemssiden er i praksis allerede
|
||
det, samme mønster som appen ellers navigerer dit fra dashbordets
|
||
org-nedtrekksmeny).
|
||
|
||
**Scratch-verifisert grundig** (isolert `teecup_app_scratch`-rolle +
|
||
isolert scratch-MinIO + engangs API-/frontend-container, ekte
|
||
nettleser-innlogging inkl. 2FA):
|
||
- API-nivå: eget Python-testskript, 63/63 håndregnede sjekker bestått
|
||
(bevisst konstruert 3-spiller/2-turnering-scenario med kjent
|
||
Stableford/brutto/poeng/penge-utfall for hver av de fem
|
||
resultattypene, inkl. en ekte uavgjort i pengeliste-testen som
|
||
bekreftet "T1"/"T1"/"3"-skip-rank-oppførselen, behold-de-1-beste,
|
||
minimum-2-resultater-ekskludering, og et eksplisitt aldersfilter-
|
||
scenario der to av tre spillere korrekt ble ekskludert).
|
||
- RLS-isolasjon eksplisitt testet på de fire nye tabellene utover
|
||
`test_isolation.sql` (som uendret består 12/12): kryss-org-lesing
|
||
av `order_of_merit`/`order_of_merit_link` gir 0 rader under feil
|
||
`app.current_org`, kryss-org-innsetting blokkeres av `WITH CHECK`.
|
||
- Ekte nettleser: hele flyten klikket gjennom som innlogget
|
||
organisasjonseier -- opprett OOM (alle fem resultattyper/kind-valg
|
||
i skjemaet), lenk en turnering med poeng-tabell-radeditoren (inkl.
|
||
å faktisk lenke en tredje turnering og se den dukke opp i listen
|
||
med riktig "Poeng: 10-6-3"-oppsummering), utvid/kollaps
|
||
innstillinger-seksjonen og bekreft alle feltverdier viste riktig
|
||
(inkl. aldersgrense-feltene 1985/2000), og bekreftet resultatlisten
|
||
viste EKSAKT de håndregnede tallene fra API-testen (gull-fremhevet
|
||
leder, riktig ekskluderte spillere med "Utenfor aldersgrensen."-
|
||
forklaring).
|
||
- Ekte typesjekket produksjonsbuild (`docker build`, samme steg som
|
||
`Dockerfile` faktisk bruker) -- måtte kjøres på nytt med lengre
|
||
timeout enn standard 2 minutter, selve bygget var uendret vellykket.
|
||
Scratch-miljøet (database, rolle, MinIO, containere, images,
|
||
midlertidige hemmeligheter) fullstendig ryddet opp etter
|
||
verifisering, TO GANGER (én gang etter backend-verifisering, én gang
|
||
etter frontend-verifisering).
|
||
|
||
**Kjent selvrettet feil under bygging:** funksjonen ble først
|
||
kodekommentert som "ADR-042", som viste seg allerede å være tatt
|
||
(bane-mal-biblioteket fra samme dag, punkt 19 over) -- oppdaget og
|
||
rettet til ADR-043 gjennomgående (migrasjon, motor, router,
|
||
frontend) FØR dokumentasjon ble skrevet, ingen funksjonell påvirkning
|
||
(kun kommentarer/docstrings, ingen kode leser ADR-nummeret).
|
||
|
||
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt ("Ja
|
||
takk"): migrasjon 055 kjørt mot ekte `teecup_db` (fire nye tabeller,
|
||
bekreftet tomme/riktig strukturert, ingen databrudd), `docker
|
||
compose up -d --build teecup_api teecup_frontend`, begge containere
|
||
boot-et rent, `/health` og `/dashboard` → 200 over https. Gjenstår
|
||
fra opprinnelig plan (se plan-loggen 2026-08-04): Eclectic-
|
||
aggregering, lag-OOM sin faktiske leaderboard-beregning.
|
||
|
||
21. **Bugfiks: "Fullfør runde" i header-modalen ("Administrer runde")
|
||
svelget feil stille og lukket dialogen uansett utfall — 2026-08-04.**
|
||
Brukeren rapporterte (skjermbilder fra en ekte runde på
|
||
teecup.teeoff.no) at en Københavner-runde med kun 1 av påkrevde 3
|
||
spillere så ut til å la seg "fullføre" via "Administrer"-knappens
|
||
modal (bekreftelsesdialogen dukket opp, ble klikket OK på), men
|
||
runden forble ufullført -- ingen feilmelding vist noe sted.
|
||
|
||
**Rotårsak, `round-header.tsx`/`round-page-shell.tsx`:** rundens
|
||
sticky header (ADR-040 Beslutning C/D, brukt på alle tre
|
||
Score/Scorekort/Leaderboard-fanene) har sin EGEN, uavhengige
|
||
"Fullfør runde"-vei via "Administrer"-modalen -- parallell til, men
|
||
ikke delt med, `round-detail.tsx` sin allerede korrekte inline
|
||
"Spillere og runde"-fane (som riktig deaktiverer knappen og viser
|
||
`setup_message` når `setup_complete` er usann). Modal-veien hadde
|
||
ALDRI blitt koblet til `setup_complete`-gaten, OG knappens
|
||
`onClick` kalte `onClose()` rett etter å ha startet
|
||
`onFinishRound()` UTEN å vente på den asynkrone
|
||
bekreftelse+`fetch`-kjeden -- dialogen lukket seg altså alltid
|
||
umiddelbart, uavhengig av om `POST /rounds/{id}/complete` faktisk
|
||
lyktes (backend-en avviste korrekt med 409 `SETUP_INCOMPLETE` +
|
||
forklarende melding -- det var ren frontend som aldri fulgte opp
|
||
svaret).
|
||
|
||
**Fiks:** `onFinishRound`-kontrakten endret fra `() => void` til
|
||
`() => Promise<{ok, message} | null>` (`null` = brukeren avbrøt
|
||
bekreftelsen, dialogen blir stående). Modalen venter nå på svaret
|
||
før den lukker seg -- ved `ok: false` vises `message` inline i
|
||
dialogen i stedet. Ny `finishDisabledReason`-prop deaktiverer
|
||
knappen og viser samme `setup_message` som den inline fanen,
|
||
fjerner selve muligheten til å trykke seg forbi en ufullstendig
|
||
formatoppsett via denne veien. Ingen backend-endring -- valideringen
|
||
fantes allerede og var korrekt, kun frontend fulgte den aldri opp.
|
||
|
||
**Scratch-verifisert** (isolert `teecup_scratch`-DB, alle 55
|
||
migrasjoner kjørt friskt, isolert `teecup_app_scratch`-rolle,
|
||
isolert scratch-MinIO, engangs API-/frontend-container bygget fra
|
||
de faktiske endrede filene, ekte nettleser via magic-link-innlogging):
|
||
reproduserte brukerens NØYAKTIGE scenario (Københavner-runde, 1 av 3
|
||
spillere) -- bekreftet modalens "Fullfør runde" nå viser seg
|
||
deaktivert med "Københavner krever nøyaktig 3 spillere -- runden har
|
||
1." synlig i dialogen (samme tekst som den inline fanen). La til to
|
||
gjestespillere (nå 3/3), bekreftet knappen ble aktiv, klikket
|
||
gjennom bekreftelsesdialogen, og bekreftet både at modalen lukket
|
||
seg OG at `completed_at` faktisk ble satt server-side. Full
|
||
typesjekket produksjonsbuild av frontend kjørt som del av samme
|
||
Docker-bygg. Scratch-miljøet ryddet opp fullstendig etterpå.
|
||
|
||
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: `docker
|
||
compose up -d --build teecup_frontend` (ingen migrasjon involvert),
|
||
containeren boot-et rent, `/health` og `/dashboard` → 200 over
|
||
https.
|
||
|
||
22. **Nytt format: "Scramble mot enkeltspiller" (`scramble_solo`),
|
||
slagspill-varianten — 2026-08-05 (migrasjon 056).** Første
|
||
ASYMMETRISKE to-siders-format i prosjektet: et scramble-lag
|
||
(variabel størrelse, N>=2, delt ball) mot ÉN individuell spiller
|
||
(egen ball), avgjort ved netto SLAGSPILL-totalsum (ikke matchspill)
|
||
-- laveste nettosum vinner. Bevisst utenfor omfang denne runden:
|
||
match-play-varianten av formatet (egen runde senere), org-
|
||
lagturneringer (kun frittstående runder nå).
|
||
|
||
**Kilder undersøkt FØR design** (CLAUDE.md sin regel for nye
|
||
formater): de tre HCP-PDF-ene dekker IKKE lag-vs-individuell eller
|
||
vilkårlig lagstørrelse -- bekreftet ved grundig lesing. Lagets
|
||
Playing Handicap er derfor en bevisst NY, enkel TeeCup-regel
|
||
(`TeamAverage` -- rent snitt av medlemmenes course handicap, for
|
||
N>=2), bekreftet eksplisitt med bruker, IKKE en kildebelagt formel
|
||
som `scramble_2`/`scramble_4` sine faste `RankedSplit`-tabeller
|
||
(som forutsetter fast lagstørrelse og derfor bevisst IKKE gjenbrukt
|
||
her). Individualspillerens allowance er en konfigurerbar prosent
|
||
(`scramble_solo_individual_pct`, std 100 %, arrangør-satt) --
|
||
egen bespoke kolonne, IKKE den generiske `allowance_override`-JSON-
|
||
en (som forutsetter ÉN delt strategi for hele runden, mens dette
|
||
formatet trenger to uavhengige strategier).
|
||
|
||
**Skjema** (migrasjon 056): `round.scramble_solo_individual_pct`
|
||
(smallint, std 100) og `round_side.side_role`
|
||
(`'team'`/`'individual'`, unik per rolle per runde). `round_hole`
|
||
sin eksisterende XOR-struktur (migrasjon 031) trengte INGEN endring
|
||
-- lagsiden bruker `round_side_id`-veien (samme som foursome/
|
||
scramble), individualsiden bruker `round_participant_id`-veien
|
||
(samme som singles), begge coexisterer allerede fritt i samme
|
||
tabell.
|
||
|
||
**Motor** (`handicap_engine.py`): ny `TeamAverage`-strategi og ny,
|
||
domeneagnostisk `net_stroke_play_margin(net_total_a, net_total_b)`
|
||
-- eksplisitt SØSKEN til, ikke del av, `compute_match_state`
|
||
(ingen hull-for-hull-tilstand for dette formatet). Selve per-side-
|
||
nettosummen gjenbruker eksisterende `allocate_strokes_by_index`/
|
||
`stroke_play_net_total`, ingen ny funksjon trengtes der. 8 nye
|
||
enhetstester (115/115 totalt, opp fra 107).
|
||
|
||
**Backend** (`app/routers/rounds.py`): ny `_ASYMMETRIC_SIDE_FORMATS`-
|
||
konstant (bevisst adskilt fra `_TWO_SIDED_FORMATS`, siden formatet
|
||
verken er symmetrisk eller matchspill). Asymmetrisk sidekapasitet i
|
||
`_check_side_capacity` (laget: ingen øvre grense, individualsiden:
|
||
maks 1) -- kan IKKE uttrykkes av det eksisterende
|
||
`_SIDE_PLAYER_COUNT`-oppslaget, som forutsetter ett fast tall for
|
||
HELE formatet. Ny `_individual_round_hole_needed`-hjelpefunksjon
|
||
(avgjør delt-ball vs. individuell-ball-lagring per DELTAKER, ikke
|
||
per format, siden de to sidene har ulik struktur). Ny
|
||
`_recompute_scramble_solo_handicaps` (søsterfunksjon til
|
||
`_recompute_side_handicaps`, som ikke kunne gjenbrukes -- to
|
||
uavhengige strategier, ikke én). Ny `_build_scramble_solo_result`
|
||
(samme "regn ut ved lesing, ingen cache"-filosofi som Nassau/
|
||
High-low-high), dispatchet FØR den generiske to-sidede grenen i
|
||
`_build_format_result`.
|
||
|
||
**Frontend -- ALT promptet til V0** (brukerens eksplisitte instruks
|
||
denne runden, gjentatt: "Alt av frontend skal promptes mot V0" --
|
||
ingen håndkoding/design-beslutning av meg). To V0-prompter skrevet
|
||
(forankret i `DESIGN_SYSTEM.md` + tilgjengelighetskravet), levert
|
||
som zip 30 (oppsett-steget) og zip 31 (resultatvisning, bygget på
|
||
30). Integrert -- IKKE blindt kopiert (V0 sin egen full-app-eksport
|
||
bruker fortsatt mock-data for alt utenom det som faktisk ble
|
||
promptet) -- kun de faktiske diff-relevante stykkene ble portet inn
|
||
i de allerede API-koblede filene:
|
||
- `new-round.tsx`: ny `ScrambleVsSoloSides`-komponent (Steg 4,
|
||
asymmetrisk sideoppsett -- "Laget" åpen liste/"minst 2"-hint,
|
||
"Individuell motstander" låst til 1), ny `SoloAllowanceControl`
|
||
(Steg 2, 0-100 %-stepper). `submitWizard()` fikk en egen
|
||
side-opprettelses-gren for formatet (sender `side_role`, som
|
||
TWO_SIDED-formatenes gren ikke gjør). "Avanserte handicap-
|
||
innstillinger"-panelet skjules helt for formatet (ingen av
|
||
feltene der har noen effekt siden formatet aldri bruker
|
||
`allowance_override`) -- funnet og rettet UNDER integrasjonen,
|
||
ikke en del av V0-eksporten.
|
||
- `scramble-solo-result.tsx`: NY fil, ren V0-eksport uendret
|
||
(selvstendig presentasjonskomponent, `ScrambleSoloResultView`).
|
||
Adapter fra `ApiFormatResult` til komponentens props-form skrevet
|
||
lokalt i `round-leaderboard.tsx` (`buildScrambleSoloResult`) og
|
||
`round-detail.tsx` (lokal kopi, prosjektets vanlige mønster) --
|
||
foretrekker hull-entryens `label`-felt (deltakerens faktiske
|
||
navn) over sidens egen (faste) label, med fallback.
|
||
- `round-scorecard.tsx`: eneste sted som trengte GENUINT ny logikk
|
||
(ikke bare registrering) -- formatet har BLANDEDE enhetstyper i
|
||
samme fane-liste (laget = side, individualspilleren =
|
||
participant). Løst ved å slå opp faktisk enhetstype fra selve
|
||
`unitId` (`round.sides.some(s => s.id === unitId)`) i stedet for
|
||
et format-bredt `isSharedBall`-flagg, som ikke kan uttrykke en
|
||
blanding -- denne oppslags-tilnærmingen forble samtidig korrekt
|
||
uendret for alle eksisterende rene formater.
|
||
- `round-detail.tsx`: INGEN endring i selve scoreføringen
|
||
(`SideScoreWizard` for laget, vanlig per-deltaker-veiviser for
|
||
individualspilleren fungerer begge uendret) -- kun
|
||
`FormatResultPanel` fikk en ny gren (MÅ avskjæres FØR den
|
||
generiske to-sidede fallback-koden, ellers ville den krasjet/vist
|
||
feil for dette asymmetriske formatet).
|
||
- `FORMAT_LABELS` lagt til i 8 filer (`round-card.tsx`,
|
||
`round-leaderboard.tsx`, `round-detail.tsx`, `round-scorecard.tsx`,
|
||
`round-stats.tsx`, `friend-profile.tsx`, `watch-round.tsx`) --
|
||
`public-live.tsx` bevisst IKKE (den nøkler på org-turneringers
|
||
`session.format`, utenfor omfang).
|
||
|
||
**Scratch-verifisert grundig, TO RUNDER** (samme "backend alene,
|
||
så full stack"-disiplin som Order of Merit-runden):
|
||
1. Backend alene: isolert `teecup_scratch`-DB (alle 56 migrasjoner
|
||
friskt), isolert `teecup_app_scratch`-rolle, isolert scratch-
|
||
MinIO, engangs API-container. Håndregnet Python-testskript, 45/45
|
||
sjekker bestått -- 3-manns lag (HCP 8/14/20, snitt 14) mot
|
||
individualspiller (HCP 10), begge 100 % og 50 % allowance,
|
||
inkl. eksplisitt bevis på at en hull-SI mellom de to
|
||
allowance-tersklene (SI 7) korrekt skifter fra 1 til 0 mottatte
|
||
slag. Kapasitetsgrenser bekreftet begge veier (laget avviser
|
||
ALDRI en 4. spiller, individualsiden avviser alltid en 2.).
|
||
"Fullfør runde"-gating eksplisitt testet med et ufullstendig lag
|
||
(1 spiller) -- avvist med riktig forklarende melding, samme
|
||
type sjekk som avdekket punkt 21 sin bug.
|
||
2. Full stack: ny isolert scratch-runde (DB/rolle/MinIO/API-
|
||
container pluss et ekte `docker build` av frontend-imaget --
|
||
den faktiske typesjekkede produksjonsbuilden, ikke bare lokal
|
||
`tsc`), ekte nettleser via magic-link-innlogging
|
||
(`TEECUP_DEV_LOG_MAGIC_LINKS`). Opprettet en ekte runde gjennom
|
||
hele veiviseren (inkl. V0 sin `ScrambleVsSoloSides`-UI), satte
|
||
score på tre hull via ekte PATCH-kall, og bekreftet at
|
||
`ScrambleSoloResultView` viste EKSAKT de håndregnede tallene
|
||
("Laget leder med 1 slag", 12 mot 13 netto) med riktig
|
||
grønn/oransje leder-fremheving per hull. `test_isolation.sql`
|
||
uendret 12/12 (migrasjonen er rent additiv). Scratch-miljøet
|
||
ryddet opp fullstendig etter BEGGE runder.
|
||
|
||
**Rullet ut live 2026-08-05**, bruker bekreftet eksplisitt: migrasjon
|
||
056 kjørt mot ekte `teecup_db` (`round.scramble_solo_individual_pct`,
|
||
`round_side.side_role` + `round_side_role_unique`-indeksen, bekreftet
|
||
tilstede etterpå), `docker compose up -d --build teecup_api
|
||
teecup_frontend`, begge containere boot-et rent, `/health` og
|
||
`/my-rounds/new` → 200 over https, `round_play_format_check` bekreftet
|
||
å inkludere `'scramble_solo'`.
|
||
|
||
23. **Matchspill-varianten av samme format (`scramble_solo_match`) —
|
||
2026-08-05 (migrasjon 057), PLUSS en kritisk regresjonsbug funnet og
|
||
rettet i samme runde som rammet BEGGE variantene (se eget punkt
|
||
under).** Samme asymmetriske lag-mot-individualspiller-struktur som
|
||
punkt 22, men avgjort hull-for-hull (matchspill) i stedet for netto
|
||
slagspill-totalsum. Vesentlig mindre bygg enn punkt 22 -- nesten alt
|
||
gjenbrukes uendret.
|
||
|
||
**Motor -- ingen endring:** `match_play_strokes`/`compute_match_state`
|
||
(`handicap_engine.py`) var allerede domeneagnostiske og brukes uendret
|
||
-- konverterer lagets `TeamAverage`/individualspillerens
|
||
`PerPlayerPercentage(pct/100)` (samme `_recompute_scramble_solo_
|
||
handicaps` som punkt 22, uendret) til RELATIVE slag (laveste side
|
||
spiller "av scratch"), i motsetning til slagspill-variantens
|
||
ABSOLUTTE, uavhengige allowance per side -- bevisst forskjell, riktig
|
||
fordi matchspill per definisjon betyr relativ slagfordeling.
|
||
|
||
**Skjema** (migrasjon 057): ren CHECK-utvidelse, INGEN nye kolonner
|
||
-- `round.scramble_solo_individual_pct`/`round_side.side_role`
|
||
(migrasjon 056) er generiske nok til å gjenbrukes uendret.
|
||
|
||
**Backend:** ny delt konstant `_SCRAMBLE_SOLO_FORMATS = {"scramble_
|
||
solo", "scramble_solo_match"}`, widened seks eksakte
|
||
`== "scramble_solo"`-strengsjekker i `app/routers/rounds.py` til
|
||
settmedlemskap. Ny `_build_scramble_solo_match_result` gjenbruker det
|
||
eksisterende blandede hull-oppslaget fra `_build_scramble_solo_result`
|
||
(samme to queries: `round_hole` via `round_side_id` for laget, via
|
||
`round_participant_id` for individualspilleren), bygger en
|
||
`list[HoleResult]` fra netto-sammenligning per hull, mater
|
||
`compute_match_state` -- gir lead/holes_remaining/is_dormie/
|
||
is_closed/`describe()` gratis.
|
||
|
||
**Frontend -- maksimalt gjenbruk, bekreftet eksplisitt med bruker
|
||
(ingen ny V0-prompt for denne varianten):** `new-round.tsx` sin
|
||
oppsett-UI (`ScrambleVsSoloSides`/`SoloAllowanceControl`) gjenbrukt
|
||
HELT uendret. Resultatvisning gjenbruker `TwoSidedBoard`
|
||
(round-leaderboard.tsx) og den generiske to-sidede
|
||
`sideIdentity`/`LeadZone`-grenen (round-detail.tsx) -- begge allerede
|
||
korrekte for en N-mot-1-side siden de grener på FAKTISK ANTALL
|
||
SPILLERE per side, ikke på rolle. Én kritisk, selv-oppdaget
|
||
korrekthetsbug FØR noen verifisering i det hele tatt: første utkast
|
||
av `_build_scramble_solo_match_result` brukte en FAST konvensjon
|
||
(laget alltid "a", individualspilleren alltid "b") -- resten av appen
|
||
(inkl. `TwoSidedBoard`) forutsetter derimot at "a" = `round.sides[0]`
|
||
SORTERT ETTER UUID, ikke rolle. Rettet til `team_is_a = team_side["id"]
|
||
< individual_side["id"]`, tredd gjennom hull-sammenligningen og
|
||
`FormatHoleEntry`-side-tagger. `round-scorecard.tsx` sin
|
||
`MatchScorecardGrid` fikk en liten, dedikert (ikke-V0) gren for å vise
|
||
ÉN rad per SIDE for dette formatet (samme mønster som `isSharedBall`-
|
||
grenen der) -- uten den ville laget vist N dupliserte rader, siden
|
||
alle lagmedlemmer deler samme `TeamAverage`-tall.
|
||
|
||
**KRITISK REGRESJONSBUG funnet under full nettleser-verifisering,
|
||
rammet OGSÅ den allerede live slagspill-varianten (punkt 22) --
|
||
ikke bare den nye matchspill-varianten:** `round-detail.tsx` sin
|
||
"Score"-fane (`SHARED_BALL_FORMATS`) inneholdt aldri `scramble_solo`/
|
||
`scramble_solo_match` (kun ekte delt-ball-formater som foursome/
|
||
scramble_2/scramble_4/chapman) -- rimelig nok isolert sett, siden
|
||
scramble mot enkeltspiller er HALVPARTEN delt-ball (laget) og
|
||
HALVPARTEN individuell (motstanderen), ikke rent det ene eller det
|
||
andre. Konsekvens: et klikk på en LAGSPILLERS scorekort rutet
|
||
(feilaktig) til den vanlige per-deltaker `ScoringWizard`/
|
||
`wizardPlayerId`-stien i stedet for den delte `SideScoreWizard`/
|
||
`wizardSideId`-stien. Siden lagspilleres hull ALDRI lagres per
|
||
`round_participant_id` (kun per `round_side_id`), returnerte
|
||
`GET .../participants/{id}/holes` alltid `[]` for dem -- og siden
|
||
SPINNER-porten i samme fil (`holes.length === 0`) også var avledet
|
||
fra akkurat DENNE deltakerens egen (evig tomme) liste, satt den seg
|
||
fast i en evig spinner, permanent, uten noen feilmelding. Siden
|
||
`SHARED_BALL_FORMATS` aldri var endret for `scramble_solo` i punkt
|
||
22 sin utrulling heller, var dette allerede live og reelt ødelagt
|
||
for ekte lagspillere på scramble_solo-runder FØR denne rettelsen.
|
||
Rettet: `hole`/spinner-porten hentes nå fra HVILKEN SOM HELST kilde
|
||
som allerede har lastet inn hull (deltaker- ELLER side-basert,
|
||
uavhengig av hvem som er "aktiv spiller") i stedet for kun aktiv
|
||
spillers egen liste; ny `openPlayerOrTeamSideEntry`-klikkhåndterer
|
||
ruter lagets spillere til `wizardSideId`, individualspilleren
|
||
fortsatt til `wizardPlayerId`; `advanceWizardPlayer`/`advanceWizardSide`
|
||
justert til å ikke lenger kunne "vandre inn i" den andre kjeden.
|
||
Funnet OG rettet FØR migrasjon 057/redeploy -- ikke en kjent,
|
||
ureparert feil et øyeblikk i produksjon.
|
||
|
||
**Verifisering:**
|
||
1. Håndregnet API-testskript (`test_scramble_solo_match.py`, 31/31
|
||
bestått): 3-manns lag (HCP 8/14/20 → TeamAverage 14) mot
|
||
individualspiller (HCP 10, 100 %), `match_play_strokes([14,10])`,
|
||
5 spilte hull → "2 UP", `hole_results`/`strokes_received` stemte
|
||
hånd-for-hånd mot forventet forløp. Dynamisk `team_is_a`-utledning
|
||
i selve testen (ikke antatt fast) for å faktisk teste sorterings-
|
||
konvensjonen, ikke bare den ene retningen.
|
||
2. Full stack, ekte nettleser (fjerde scratch-miljø, ekte
|
||
`docker build` av frontend-imaget): opprettet en runde gjennom
|
||
hele veiviseren, satte team/individualspiller opp, entret score
|
||
via BEGGE veiviserne (fant og rettet bug-en HER, se over), bekreftet
|
||
`LeadZone` viste korrekt "X UP" og skiftet leder korrekt, bekreftet
|
||
`MatchScorecardGrid` viste eksakt ÉN rad for laget (ikke tre) og
|
||
riktig "N UP"-progresjon per hull, fullførte runden via ekte
|
||
"Fullfør runde"-knapp.
|
||
3. Regresjonsbekreftet SEPARAT at samme fiks løser samme bug for den
|
||
allerede-live slagspill-varianten (`scramble_solo`): opprettet en
|
||
ny runde med det formatet i samme scratch-miljø, bekreftet at
|
||
`SideScoreWizard` nå åpner korrekt for lagsiden (før fiksen: samme
|
||
evige spinner).
|
||
4. `test_isolation.sql` uendret 12/12. Scratch-miljøet (DB/rolle/
|
||
MinIO/API-/frontend-containere/images) ryddet opp fullstendig
|
||
etter bruk.
|
||
|
||
**Rullet ut live 2026-08-05** (samme økt som punkt 22): migrasjon 057
|
||
kjørt mot ekte `teecup_db` (`round_play_format_check` bekreftet å nå
|
||
inkludere `'scramble_solo_match'`), `docker compose up -d --build
|
||
teecup_api teecup_frontend` (inkluderer BÅDE matchspill-formatet OG
|
||
bug-fiksen over -- fiksen var derfor allerede live for ekte
|
||
`scramble_solo`-lagspillere idet denne utrullingen fullførte), begge
|
||
containere boot-et rent, `/my-rounds/new` → 200 over https.
|
||
|
||
24. **"Bruk som mal for egen bane" viste seg for bredt — 2026-08-05,
|
||
rent frontend, ingen skjema-/backend-endring.** Brukeren rapporterte
|
||
at knappen (fork en eksisterende personlig bane til utgangspunkt for
|
||
en NY) dukket opp i det vanlige "Egen bane"-søket i opprett-runde-
|
||
veiviseren, uansett om målet var å VELGE en eksisterende bane å
|
||
spille på eller å opprette en helt ny -- feil sted for en
|
||
mal-handling som kun gir mening når brukeren faktisk har sagt at
|
||
målet er å opprette.
|
||
|
||
**Avklart med bruker (tre alternativer presentert)**: knappen skal
|
||
KUN vises når brukeren eksplisitt har trykket "Opprett ny bane" og
|
||
derfra velger å basere den nye banen på en eksisterende, ikke i det
|
||
vanlige søket.
|
||
|
||
**Fiks (`frontend/components/new-round.tsx`):** ny `S1Sub`-verdi
|
||
`"own-search-template"`, distinkt fra det vanlige `"own-search"`.
|
||
`OwnSearch`s `onUseAsTemplate`-prop gjort valgfri (var påkrevd) --
|
||
"Bruk som mal for egen bane" rendres nå kun `{onUseAsTemplate &&
|
||
(...)}`, akkurat som den allerede korrekt gaterte offisiell-bane-
|
||
varianten i `CourseList`. Det vanlige `"own-search"`-kallet (nådd fra
|
||
Step1 sitt "Egen bane"-kort) slutter å sende med `onUseAsTemplate` i
|
||
det hele tatt. Ny lenke "Basér på en eksisterende bane i stedet" lagt
|
||
til øverst i `OwnCreateStep`s blanke skjema (kun synlig når
|
||
`!initial`, altså før noen mal er valgt) -- trykk navigerer til
|
||
`"own-search-template"`, som gjenbruker `OwnSearch` UENDRET, denne
|
||
gangen MED `onUseAsTemplate` satt (samme forhåndsutfyllings-bro som
|
||
allerede fantes for offisielle baner). Ny `ownCreateOrigin`-state
|
||
(løftet til toppnivå-hook-en, ved siden av `s1Sub`) sporer hvor
|
||
"own-create" skal gå TILBAKE til (vanlig søk, mal-søk, eller broen
|
||
fra offisielle baner) -- uten denne ville tilbake-knappen fra
|
||
mal-søket hoppet feil sted.
|
||
|
||
**Scratch-verifisert i ekte nettleser** (femte scratch-miljø, ekte
|
||
`docker build` av frontend-imaget): bekreftet at "Bruk som mal for
|
||
egen bane" IKKE lenger vises i det vanlige "Egen bane"-søket;
|
||
bekreftet at "Basér på en eksisterende bane i stedet"-lenken vises i
|
||
det blanke opprett-skjemaet; trykket den, bekreftet at knappen NÅ
|
||
vises i det samme søket; trykket "Bruk som mal", bekreftet at
|
||
opprett-skjemaet forhåndsfylte navn/18 hull/utslag korrekt fra den
|
||
valgte banen; bekreftet at tilbake-knappen fra opprett-skjemaet gikk
|
||
til mal-søket (ikke det vanlige søket eller helt til `source`).
|
||
Ingen konsoll-feil. Scratch-miljøet (DB/rolle/MinIO/API-/frontend-
|
||
containere/images) ryddet opp fullstendig etter bruk.
|
||
|
||
**Rullet ut live 2026-08-05** (kun `teecup_frontend` bygget/
|
||
restartet -- ren frontend-endring, ingen skjema-/API-endring).
|
||
`/my-rounds/new` → 200 over https etterpå.
|
||
|
||
**Oppfølging samme kveld, 2026-08-06:** brukeren sendte skjermdump og
|
||
rapporterte at knappen FORTSATT dukket opp -- men i en ANNEN gren enn
|
||
den akkurat rettet: `CourseList` (offisielle baner, "Tjøme Golfklubb"
|
||
→ "Hovedbanen") viste den for enhver offisiell bane, uansett om målet
|
||
var å spille eller å opprette. Punktet over rettet kun `OwnSearch`
|
||
(egne baner) -- samme feilmønster fantes uavhengig i den offisielle
|
||
grenen, siden begge alltid hadde vist knappen unconditionalt bortsett
|
||
fra en `templateHoles.length > 0`-sjekk.
|
||
|
||
**Utvidet fiks:** erstattet den forrige, EGEN-bane-spesifikke
|
||
`"own-search-template"`-S1Sub-verdien med en generell boolsk
|
||
`templateMode`-state (løftet til toppnivå, ved siden av `s1Sub`).
|
||
`CourseList`s `onUseAsTemplate` gjort valgfri på samme måte som
|
||
`OwnSearch`s allerede var -- begge rendrer nå kun
|
||
`{onUseAsTemplate && ...}`, styrt av `templateMode`. Ny mellomskjerm
|
||
`"template-source"` (samme to `SourceCard`-valg som det aller første
|
||
Bane&tid-steget, men med egen tittel "Basér på en eksisterende bane")
|
||
lar brukeren velge OFFISIELL eller EGEN bane som malkilde -- nådd fra
|
||
samme "Basér på en eksisterende bane i stedet"-lenke i det blanke
|
||
opprett-skjemaet. `templateMode` settes eksplisitt `false` i det
|
||
aller første Bane&tid-steget (fresh start) og ved "Avbryt" fra
|
||
opprett-skjemaet, `true` kun fra `"template-source"`.
|
||
|
||
**Scratch-verifisert på nytt** (sjette scratch-miljø): gjenskapte
|
||
brukerens eksakte scenario (offisiell-bane-søk → "Tjøme Golfklubb" →
|
||
"Hovedbanen") mot ekte teeoff-data, bekreftet at "Bruk som mal for
|
||
egen bane" IKKE lenger vises der; bekreftet at "template-source"-
|
||
mellomskjermen vises korrekt fra opprett-skjemaets lenke; valgte
|
||
"Offisiell bane" derfra, søkte samme klubb, bekreftet at knappen NÅ
|
||
vises (med justert beskrivelsestekst: "Velg hvilken bane du vil
|
||
bruke som mal..."); trykket den, bekreftet at opprett-skjemaet
|
||
forhåndsfylte navn/18 ekte hull/4 ekte utslag korrekt fra Hovedbanen.
|
||
Ingen konsoll-feil. Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
**Rullet ut live 2026-08-06** (kun `teecup_frontend`).
|
||
`/my-rounds/new` → 200 over https etterpå.
|
||
|
||
25. **Kommentarer/bilder på frittstående runder + samlet `/my-feed`-side
|
||
(ADR-044, migrasjon 058) — BYGGET OG SCRATCH-VERIFISERT 2026-08-06,
|
||
venter på utrulling mot ekte `teecup_db`.** Brukeren ba om samme
|
||
"Banter Board"-mulighet (bilde+tekst) som org-turneringer allerede har
|
||
(ADR-025), for en enkelt frittstående runde, pluss en sentral feed-side
|
||
som samler dette på tvers av runder. Underveis avdekket research at
|
||
rundevisibilitet (ADR-036 fase 2 -- `round.visibility_mode`/
|
||
`round_visible_category`) allerede var bygget og live siden 2026-07-28
|
||
-- en gammel "Neste steg"-seksjon i denne loggen sa fortsatt "ikke
|
||
bygget", aldri rettet opp. Denne runden gjenbrukte den infrastrukturen
|
||
uendret; omfanget ble dermed smalere enn først antatt.
|
||
|
||
**To beslutninger avklart eksplisitt med bruker (AskUserQuestion) FØR
|
||
planlegging:** (1) skriverett = alle som kan SE runden kan også POSTE
|
||
(ikke kun eier/medspillere -- overstyrer et snevrere utkast notert
|
||
tidligere samme dag i FEATURE_BACKLOG.md), (2) feed på en ny, dedikert
|
||
side (`/my-feed`), ikke en dashbord-seksjon. Fullstendig plan skrevet
|
||
og godkjent i Plan Mode før noe ble bygget (to Explore-runder + én
|
||
Plan-agent-syntese, grundig kodegrunnet mot eksisterende `_can_view_
|
||
round`/`list_friends_on_course`/`messaging.py`-mønstre).
|
||
|
||
**Datamodell:** ny, EGEN `round_message`-tabell (migrasjon 058), IKKE
|
||
en utvidelse av org sin `message`-tabell -- `round` har verken
|
||
`organization_id` eller RLS (ADR-033 Beslutning A). Samme feltform
|
||
(forfatter-snapshot, valgfri tekst, valgfri `image_key`) og samme
|
||
MinIO+AVIF-pipeline (`app/storage.py`) gjenbrukt uendret, egen
|
||
`prefix="round_messages"`. Scratch-verifisert alene først: skjema,
|
||
indekser, grants bekreftet, `test_isolation.sql` 12/12.
|
||
|
||
**Backend:** ny fil `app/routers/round_messages.py` (`rounds.py` er
|
||
allerede 5500+ linjer) -- `GET`/`POST /rounds/{id}/messages`, `DELETE
|
||
/rounds/{id}/messages/{message_id}` (forfatter ELLER rundeeier kan
|
||
slette, samme "forfatter-eller-admin"-modell som org-feedens
|
||
`delete_feed_message`), og en ny `GET /feed`-aggregering modellert på
|
||
`list_friends_on_course` sin reverserte retning (viewer → synlige
|
||
runder), justert til å inkludere egne runder uansett `visibility_mode`
|
||
og uten "spiller nå"-filteret (all-time, keyset-paginert). Gjenbruker
|
||
`_can_view_round`/`_get_viewable_round_or_404` fra `rounds.py` uendret
|
||
som BÅDE lese- og post-sjekk. Ingen ny WebSocket-kanal -- gjenbruker
|
||
eksisterende `broadcast_round_update`, `RoundMessages`-komponenten
|
||
henter på nytt via et nytt `messagesRefreshTick`-signal i
|
||
`round-detail.tsx` sin allerede eksisterende WS-lytter. Liten
|
||
refaktor i samme runde: `messaging.py` sin lokale `_read_optional_
|
||
image` flyttet til `app/storage.py` som delt `read_optional_image`,
|
||
brukt av begge routerne nå.
|
||
|
||
**En reell bug funnet OG rettet FØR noen verifisering:** `GET /feed`
|
||
sin `before`-parameter krasjet med 500 (`asyncpg.exceptions.DataError`)
|
||
-- asyncpg godtar ikke en ren tekststreng mot en `timestamptz`-cast i
|
||
SQL-en, krever et faktisk `datetime`-objekt. Rettet ved å la Pydantic
|
||
parse query-parameteren som `datetime` direkte i stedet for `str`.
|
||
|
||
**Håndregnet auth-testskript, 30/30 sjekker bestått:** eier poster/
|
||
sletter egen (private) runde; lenket medspiller (ikke eier) poster,
|
||
en ANNEN ikke-eier-medspiller kan IKKE slette den (403); eieren KAN
|
||
slette andres innlegg på egen runde (moderasjon); en fremmed uten
|
||
innsyn avvist på både GET og POST (403/404) for en privat runde; en
|
||
`friends`-synlig runde med RIKTIG venne-kategori gir GET+POST; med
|
||
FEIL/manglende kategori avvises selv GET; `GET /feed` bekreftet riktig
|
||
sett per bruker (eier ser alle egne uansett visibility, en venn ser
|
||
kun offentlige + riktig-kategoriserte `friends`-runder, en fremmed ser
|
||
kun offentlige), paginering (`before`-cursor) uten hopp/duplikat.
|
||
Ekte bildeopplasting testet direkte mot scratch-MinIO (utenfor HTTP,
|
||
`storage._client.stat_object`) -- bekreftet 488 byte AVIF,
|
||
`content_type=image/avif`.
|
||
|
||
**Frontend bygget via V0** (brukerens eksplisitte, stående
|
||
arbeidsfordeling -- se CLAUDE.md-notat): to prompter skrevet og
|
||
levert i chat (kommentarseksjon, feed-side), begge forankret i
|
||
`DESIGN_SYSTEM.md` sine fargetokens/tilgjengelighetsregler og eksakte
|
||
data-kontrakter fra det allerede bygde API-et. Zip 32 (`round-
|
||
messages.tsx`) og zip 33 (samme + ny `feed.tsx`) diffet ordrett mot
|
||
hverandre FØR integrering -- ingen utilsiktet drift utover den ene
|
||
nye filen. To små, bevisste integrasjonsjusteringer: fjernet en
|
||
ubrukt `cn`-import, og lot `Feed`-komponenten selv hente `/auth/me`
|
||
(samme mønster som `round-detail.tsx`) i stedet for å kreve
|
||
`currentUserId` som ekstern prop -- konsekvent med at alle andre sider
|
||
i appen er tynne wrappere som selv henter egen brukeridentitet.
|
||
`RoundMessages` montert som en ny seksjon NEDERST på `round-detail.
|
||
tsx` sin `pageTab === "score"` (ikke en ny rundefane, ikke inni
|
||
admin-"manage"-fanen -- se ADR-044 for begrunnelsen). Ny `/my-feed`-
|
||
side + inngangspunkter: `SeeAllLink` lagt til dashbordets "Venner på
|
||
banen"-seksjon, ny lenke i `/more`-menyen.
|
||
|
||
**KRITISK rute-/rewrite-kollisjon funnet under full-stack-
|
||
verifisering, samme klasse som `/rounds`→`/my-rounds` og
|
||
`/friends`→`/my-friends`:** frontend-siden ble først lagt på `/feed`
|
||
-- nøyaktig samme streng som det flate API-endepunktet. Uten en
|
||
eksplisitt rewrite-regel vant Next.js sin egen side-rute, så
|
||
`fetch("/feed")` fra klienten fikk appens egen HTML tilbake (stille
|
||
feilet JSON-parsing, "Kunne ikke laste feeden"-feilmelding i UI-et).
|
||
Rettet ved å (1) flytte siden til `/my-feed` OG (2) legge til en
|
||
manglende, eksplisitt `/feed`-rewrite-regel i `next.config.mjs` (som
|
||
rett og slett ikke fantes fra før -- retting nummer to var nødvendig
|
||
uavhengig av sideflyttingen). Ekte typesjekket produksjonsbuild fanget
|
||
IKKE denne feilen (kun kjøretid avslørte den) -- funnet ved faktisk
|
||
nettleser-testing, ikke ved bygging.
|
||
|
||
**En andre reell bug funnet under samme verifiseringsrunde:**
|
||
`RoundMessages` hentet meldinger KUN ved mount -- `round-detail.tsx`
|
||
sin eksisterende `/ws/rounds/{id}/live`-lytter (allerede der fra
|
||
tidligere, brukt av scorekort/format-resultat) var aldri koblet til
|
||
den nye kommentarseksjonen. Rettet med et nytt `messagesRefreshTick`-
|
||
signal, samme mønster som det eksisterende `formatResultRefreshTick`.
|
||
Verifisert med en EGEN, minimal scratch-Caddy (`handle /ws/* {
|
||
reverse_proxy ... }`, samme regel som den ekte `/opt/teeoff/deploy/
|
||
Caddyfile`) satt opp spesifikt for denne testen -- uten den ville
|
||
WebSocket-oppgraderingen aldri nådd API-et i det hele tatt (Next.js
|
||
sitt eget rewrite-lag proxyer ikke WS pålitelig i standalone-modus,
|
||
dokumentert allerede i den ekte Caddyfilen). Bekreftet: en kommentar
|
||
postet via et separat API-kall dukket opp automatisk i en åpen
|
||
nettleserfane, ingen interaksjon eller reload nødvendig.
|
||
|
||
**Scratch-miljøet ryddet opp fullstendig** etter bruk (DB/rolle/MinIO/
|
||
API-/frontend-/Caddy-containere og -images, V0-eksport-zip-ene fjernet
|
||
fra prosjektroten uten å bli spurt, samme rutine som alltid for merget
|
||
V0-eksport).
|
||
|
||
**Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt: migrasjon
|
||
058 kjørt mot ekte `teecup_db` (`round_message`-tabellen bekreftet
|
||
tilstede etterpå), `docker compose up -d --build teecup_api
|
||
teecup_frontend`, begge containere boot-et rent. `/health`/`/my-rounds/
|
||
new`/`/my-feed` → 200 over https, `GET https://teecup.golf/feed`
|
||
bekreftet å proxye korrekt til backend (401 JSON `NOT_AUTHENTICATED`,
|
||
IKKE frontend-sidens HTML -- bekrefter rewrite-fiksen virker i
|
||
produksjon, ikke bare i scratch), `teeoff.no` upåvirket.
|
||
|
||
26. **To bugfikser, 2026-08-06, rullet ut samme dag:**
|
||
- **"Venner på banen"-kortet lenket til feil rute.** Brukeren
|
||
rapporterte (video vedlagt) at et klikk på en venns pågående runde
|
||
på dashbordet ga "Denne runden finnes ikke, eller du har ikke
|
||
tilgang til den" -- selv om runden faktisk var synlig for
|
||
vedkommende (`GET /friends/on-course` viste den korrekt). Rotårsak:
|
||
`dashboard.tsx` sin `LiveFriends`-komponent lenket til
|
||
`/my-rounds/{id}` (eier/medspiller-only-visningen,
|
||
`_get_accessible_round_or_404`) i stedet for `/watch/{id}` (den
|
||
allerede eksisterende tredjeparts-visningen bygget for nettopp
|
||
dette, ADR-036 fase 2). Samme feilmønster funnet OG rettet i den
|
||
nybygde `/my-feed`-siden (punkt 25) -- `FeedCard` lenket likeens
|
||
alltid til `/my-rounds/{id}` uansett eierskap. Begge rettet til å
|
||
route riktig basert på om runden faktisk er brukerens egen.
|
||
- **Kamera lot seg ikke aktivere ved bildeopplasting på mobil**, seks
|
||
steder i appen (rundekommentarer, org-feed, avatar, lag-chat,
|
||
turnering-hero/sponsor-bilder). Rotårsak: `<input type="file"
|
||
accept="image/jpeg,image/png,image/webp,image/gif">` -- flere
|
||
eksplisitte MIME-typer i `accept` gjør at mange mobilnettlesere kun
|
||
tilbyr galleri, ikke kamera, i filvelgeren. Rettet til
|
||
`accept="image/*"` alle seks steder (server-siden validerer uansett
|
||
strengt mot `storage.ALLOWED_INPUT_CONTENT_TYPES`, så dette svekker
|
||
ingen kontroll).
|
||
|
||
Begge ren frontend, ingen migrasjon. Ekte typesjekket produksjonsbuild
|
||
kjørt før utrulling. **Rullet ut live 2026-08-06** (kun
|
||
`teecup_frontend` bygget/restartet), `/dashboard` → 200, `teeoff.no`
|
||
upåvirket.
|
||
|
||
27. **Ekte TeeCup-logo tatt i bruk som app-ikon, erstatter den midlertidige
|
||
grønne golfflagg-placeholderen — 2026-08-06.** Brukeren lastet opp
|
||
`TeeCup logo.svg` (ikon+ordmerke i én fil, Inkscape-eksport med
|
||
raster-baserte masker/filtre, ikke rene vektor-tekstobjekter) og
|
||
spurte om splitting av ikon fra tekst var mulig uten flere opplastede
|
||
varianter.
|
||
|
||
**Splitting løst programmatisk, ikke manuelt:** brukte nettleserens
|
||
`getBBox()` på hver topp-nivå-`<g>` i den innlastede SVG-en for å
|
||
måle faktiske posisjoner -- avslørte at ikonet (ball/pokal/tee) består
|
||
av tre atskilte grupper (x: 0–169 av 656 totalt) mens "TeeCup"-
|
||
ordmerket er seks separate bokstav-grupper (x: 179–407) -- ingen
|
||
gjetning nødvendig. Beskåret til et eget ikon-SVG via et rent
|
||
`viewBox`-endring på en kopi av originalfilen (ingen gruppe fjernet
|
||
eller XML-struktur røsket i -- alle filter-/maske-/clipPath-
|
||
definisjoner ligger nestet INNI selve gruppene, bekreftet empirisk:
|
||
et forsøk på å trimme bort de ubrukte tekst-gruppene for å spare
|
||
filstørrelse brøt renderingen umiddelbart, samme "hele filen eller
|
||
ingenting"-begrensning som Inkscape sin raster-trace-eksport gir --
|
||
reverserte til den fullstendige, verifisert fungerende versjonen i
|
||
stedet for å risikere en ødelagt fil for en fil-størrelse-optimering).
|
||
`icon.svg` er dermed 416 KB (arves fra kildefilens embedded
|
||
raster-masker) -- fungerer korrekt, men en ekte vektor-reeksport fra
|
||
designer ville gitt en vesentlig lettere fil om ønskelig senere.
|
||
|
||
**Ekte nettleser brukt til rasterisering** (ingen lokal SVG->PNG-
|
||
rasterizer tilgjengelig i miljøet): Chrome DevTools sitt
|
||
skjermbilde-verktøy, med små HTML-wrapper-sider satt til eksakt
|
||
mål-pikselstørrelse per format, generert alle seks filene appen
|
||
faktisk bruker: `icon.svg` (transparent, favicon), `icon-light-
|
||
32x32.png`/`icon-dark-32x32.png` (transparent, samme innhold --
|
||
fungerer i begge fargetema), `apple-icon.png` (180×180, opak hvit
|
||
bakgrunn -- iOS støtter ikke transparens), `icons/icon-192.png` og
|
||
`icons/icon-512.png` (PWA-manifest, opak hvit bakgrunn), `icons/
|
||
icon-maskable-512.png` (ikonet holdt innenfor sentrale ~63 % --
|
||
trygt innenfor W3C sin ~80 %-sikre-sone for maskable-ikoner som
|
||
OS-en klipper med egen maske). Hvert format visuelt bekreftet
|
||
(`Read` på hver genererte PNG) før plassering.
|
||
|
||
Hele ordmerket (ikon+"TeeCup"-tekst) lagret uendret som `public/
|
||
teecup-wordmark.svg` for senere bruk (f.eks. innloggingsside),
|
||
ikke koblet inn noe sted ennå -- kun selve app-ikon-oppgaven løst nå.
|
||
`app/manifest.ts` sin kommentar oppdatert til å reflektere at
|
||
placeholder-fasen er over.
|
||
|
||
Ren frontend, ingen migrasjon. Ekte typesjekket produksjonsbuild
|
||
kjørt. **Rullet ut live 2026-08-06** (kun `teecup_frontend`),
|
||
`/icon.svg`/`/apple-icon.png`/`/icons/icon-512.png` → 200 over https,
|
||
`teeoff.no` upåvirket.
|
||
|
||
28. **Tilskuer-visning (`/watch/[id]`): tre faner + "Spillere og runde",
|
||
full paritet med eiersiden (ADR-045) — BYGGET OG SCRATCH-VERIFISERT
|
||
2026-08-06, venter på utrulling.** Direkte oppfølging av punkt 26 sin
|
||
bug (feil lenke fra "Venner på banen") -- brukeren ba samtidig om at
|
||
selve tilskuer-siden (fram til da ÉN enkelt side, kun kompakt status)
|
||
skulle få samme tre faner som eiersiden (Score/Scorekort/Leaderboard)
|
||
og "Spillere og runde" (deltakerliste, HCP/utslag/tildelte slag,
|
||
flight-gruppering), uten eier-kun-handlingene. Bekreftet med bruker
|
||
(AskUserQuestion, to spørsmål): full formatspesifikk detalj på
|
||
Scorekort-fanen (ikke en forenklet fellesvisning), og
|
||
flight-gruppering bygget nå (ikke utsatt).
|
||
|
||
**Nøkkelbeslutning, funnet under research (Explore-agent + egen
|
||
lesning av begge filene):** `round-scorecard.tsx` (`RoundScorecard`)
|
||
og `round-leaderboard.tsx` (`RoundLeaderboard`) sin faktiske rutenett-
|
||
/tavle-rendring var ALLEREDE ren, skrivefri visning -- redigering
|
||
skjer et helt annet sted. Det eneste som hindret gjenbruk for
|
||
tilskuere var at alle interne data-hentinger (fire hooks/effekter
|
||
per fil, pluss WS-tilkoblingen) hardkodet `/rounds/*`-prefikset. I
|
||
stedet for å bygge nye, forenklede tilskuer-komponenter (ville gitt
|
||
UFULLSTENDIG formatparitet eller duplisert ~1000+ linjer formatlogikk),
|
||
la til en valgfri `publicMode`-prop på begge de EKSISTERENDE
|
||
komponentene -- default `false` (INGEN endring i eiersidens
|
||
oppførsel), `true` bytter alle interne URL-er til `/public/rounds/*`
|
||
og WS-kanalen til den offentlige. Samme kode, to datakilder, ekte
|
||
full formatparitet (alle 17 formater) uten duplisert vedlikehold.
|
||
|
||
**Backend:** ett nytt endepunkt, `GET /public/rounds/{id}/
|
||
flight-group` (`app/routers/rounds.py`) -- speiler den eksisterende
|
||
autentiserte varianten, men med en ny `_viewable_flight_rows`
|
||
(bruker `_can_view_round` PER søsken-flight uavhengig, siden en
|
||
flight-gruppe kan ha søsken med ulik `visibility_mode`). Ingen
|
||
migrasjon.
|
||
|
||
**Frontend, ny fil-for-fil:** `watch-tabs.tsx` (ny, liten fane-stripe
|
||
-- BEVISST ikke en gjenbruk av `RoundHeader`, som alltid render et
|
||
"Administrer"-tannhjul/admin-dialog uansett hvilke handlere som gis
|
||
inn -- den nye komponenten inneholder rett og slett ingen slik kode).
|
||
`watch-players.tsx` (ny "Spillere og runde", IKKE et forsøk på å
|
||
gjøre `round-detail.tsx` sin lokale, ikke-eksporterte `PlayerList`
|
||
gjenbrukbar via `canManage=false` -- egen, read-only-only komponent,
|
||
ingen +Medspiller-/Rediger-/Fullfør-/Slett-kode i det hele tatt).
|
||
`watch-round.tsx` omskrevet: Score-fanen viser nå fane-stripen +
|
||
kompakt status + "Spillere og runde" -- selve matchstatus-/skins-/
|
||
slagspill-visningen FLYTTET til den nye Leaderboard-fanen (erstattet
|
||
av `RoundLeaderboard publicMode`, som dekker alle formater -- lukker
|
||
et eksisterende gap der den gamle `watch-round.tsx` sin lokale
|
||
`TWO_SIDED_FORMATS`-liste kun dekket 8 av 17 formater). To nye tynne
|
||
ruter, `/watch/[id]/scorecard` og `/watch/[id]/leaderboard`
|
||
(`watch-scorecard.tsx`/`watch-leaderboard.tsx`, hver bare fane-stripe
|
||
+ `RoundScorecard`/`RoundLeaderboard` med `embedded publicMode`).
|
||
|
||
**Verifisering, samme scratch-Caddy-disiplin som ADR-044:** 6/6
|
||
håndregnede sjekker for det nye flight-group-endepunktet (anonym ser
|
||
kun offentlige søsken-flighter, venn med riktig kategori ser i
|
||
tillegg `friends`-synlige, eier ser alle, fremmed uten vennskap ser
|
||
kun offentlige, privat anker-runde avvist helt). `tsc --noEmit` rent
|
||
på hele frontend-prosjektet. Ekte nettleser: **regresjon FØRST** --
|
||
eiersidens `/my-rounds/{id}/scorecard`/`/leaderboard` bekreftet
|
||
PIKSEL-IDENTISK (samme tall, samme layout) FØR noe nytt ble testet.
|
||
Deretter tilskuer-sidene: alle tre faner, "Spillere og runde" uten
|
||
eier-knapper, ETT allerede-dekket format (`stroke`) OG ETT tidligere
|
||
udekket format (`flag`) begge bekreftet rendret korrekt gjennom
|
||
`/watch/{id}/leaderboard` -- beviser formatparitet-lukkingen direkte.
|
||
En privat rundes tilskuer-sider (alle tre) bekreftet korrekt avvist
|
||
for en ANONYM leser i en egen, isolert nettleser-kontekst (ikke bare
|
||
et API-kall) med riktig feilmelding og "Tilbake til dashbordet"-
|
||
lenke. `test_isolation.sql` uendret 12/12. Scratch-miljøet (DB/rolle/
|
||
MinIO/API-/frontend-/Caddy-containere og -images) ryddet opp
|
||
fullstendig etter bruk.
|
||
|
||
**Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt: `docker
|
||
compose up -d --build teecup_api teecup_frontend` (ingen migrasjon),
|
||
begge containere boot-et rent. `/health` → 200, `/dashboard` → 200,
|
||
nytt endepunkt bekreftet (`/public/rounds/{tilfeldig-id}/flight-group`
|
||
→ 404 for en ikke-eksisterende runde), alle tre nye/endrede
|
||
`/watch/[id]`-ruter → 200, `teeoff.no` upåvirket.
|
||
|
||
29. **Bugfiks: kommentarer/bilder (ADR-044) fantes ikke i tilskuer-
|
||
visningen — 2026-08-06.** Brukeren rapporterte at en person en runde
|
||
er delt med ikke kunne se selve samtalen. Rotårsak: `RoundMessages`
|
||
ble aldri montert på `/watch/[id]` -- kun på `round-detail.tsx` sin
|
||
egen Score-fane (eier/deltaker). Backend var allerede riktig
|
||
(`GET`/`POST /rounds/{id}/messages` bruker `_get_viewable_round_or_
|
||
404`, synlighets-gatet, ikke eier/deltaker-only, siden ADR-044 ble
|
||
bygget) -- ren frontend-mangel.
|
||
|
||
**Én reell backend-mangel funnet underveis:** `PublicRoundOut`
|
||
eksponerte `owner_display_name` men ALDRI eierens faktiske
|
||
`user_id` -- `RoundMessages` sin "forfatter ELLER rundeeier kan
|
||
slette"-sjekk (`roundOwnerUserId === currentUserId`) hadde dermed
|
||
ingen korrekt verdi å sammenligne mot fra tilskuer-siden. Lagt til
|
||
`owner_user_id: str` på `PublicRoundOut` (samme personvernsnivå som
|
||
det allerede eksponerte visningsnavnet -- en ugjennomsiktig UUID
|
||
avslører ingenting nytt).
|
||
|
||
**Rettet:** `watch-round.tsx` monterer nå `<RoundMessages>` på
|
||
Score-fanen, med `roundOwnerUserId={round.owner_user_id}` og
|
||
`currentUserId` fra en ny `/auth/me`-henting (tolerant for anonym,
|
||
samme mønster som resten av appen).
|
||
|
||
**Verifisert, isolert scratch-miljø:** 6/6 håndregnede sjekker
|
||
(`/public/rounds/{id}` eksponerer korrekt `owner_user_id`, en venn
|
||
kan poste via synlighet -- ikke eier/deltaker, begge kan lese, eier
|
||
kan moderere/slette vennens innlegg). `tsc --noEmit` rent. Ekte
|
||
nettleser: kommentarseksjonen bekreftet synlig på `/watch/{id}`,
|
||
ingen konsollfeil utover forventet 401 på `/auth/me` for en anonym
|
||
leser (allerede tolerert i koden). Scratch-miljøet ryddet opp.
|
||
|
||
30. **Bugfiks: kamera manglet helt ved bildeopplasting, ekte "prøv igjen"-
|
||
feil skjult bak en generisk melding — 2026-08-06.** Brukeren
|
||
rapporterte (skjermdump fra ekte enhet, Pixel 8 Pro, TeeCup installert
|
||
som PWA i Chrome) at "Legg til bilde" ALDRI viste noe kamera-
|
||
alternativ, kun galleri -- og at selve posten deretter feilet med
|
||
"Kunne ikke poste kommentaren. Prøv igjen." uten noen forklaring.
|
||
|
||
**To atskilte, reelle rotårsaker, begge bekreftet:**
|
||
1. Siden Android 13 bruker Chrome (og andre nettlesere) systemets
|
||
eget "Photo Picker" for `<input type=file accept="image/*">` UTEN
|
||
`capture`-attributt -- denne velgeren viser KUN galleri, ALDRI et
|
||
kamera-alternativ, uansett hvilken `accept`-verdi som er satt
|
||
(den tidligere fiksen, `accept="image/*"` alene, var altså
|
||
nødvendig, men ikke tilstrekkelig for Android 13+). Rettet ved å
|
||
splitte den ene filvelgeren i to atskilte inputs/knapper -- én
|
||
med `capture="environment"` (rett til kamera), én uten (rett til
|
||
galleri) -- i `round-messages.tsx`, `team-chat.tsx`, og
|
||
`public-tournament.tsx` sin `TournamentFeed` (de tre stedene som
|
||
faktisk brukes aktivt for bildeopplasting fra mobil; `account-
|
||
settings.tsx`/`tournament-presentation.tsx` sine engangs-avatar-/
|
||
hero-bilde-opplastinger har fortsatt kun galleri -- lavere
|
||
prioritet, tas senere om ønskelig).
|
||
2. Selve feilmeldingen ved en mislykket post var en blindt generisk
|
||
`catch { setPostError("Kunne ikke poste... Prøv igjen.") }` --
|
||
den faktiske, spesifikke backend-feilen (f.eks. "Bildet er for
|
||
stort (maks 8 MB)") ble aldri lest fra svaret og dermed aldri
|
||
vist. Rettet til å faktisk lese `{"detail":{"code","message"}}`-
|
||
kontrakten (samme form som resten av API-et, `app/errors.py`) og
|
||
vise den ekte meldingen, med en generisk norsk fallback kun hvis
|
||
svaret mot formodning ikke er JSON. Lagt til et klient-side
|
||
størrelsessjekk (samme 8 MB-grense som `storage.MAX_UPLOAD_BYTES`)
|
||
for umiddelbar, presis feilmelding uten en unødvendig tur-retur
|
||
til serveren for noe som uansett ville blitt avvist -- sannsynlig
|
||
den FAKTISKE årsaken til brukerens opprinnelige feil, siden
|
||
moderne telefonkamera-bilder lett kan overstige 8 MB.
|
||
|
||
**Oppfølging samme dag:** brukeren spurte -- helt riktig -- om selve
|
||
8 MB-grensen egentlig var et praktisk problem, siden alle bilder
|
||
uansett konverteres til AVIF (mye mindre) før lagring. Bekreftet i
|
||
`app/storage.py`: `MAX_UPLOAD_BYTES`-sjekken skjer på RÅ input, FØR
|
||
konverteringen -- den beskytter altså ikke lagringsstørrelsen (det
|
||
gjør AVIF-steget, uavhengig av inputstørrelse), den var bare en
|
||
vilkårlig øvre grense som kunne avvise et helt normalt telefonbilde
|
||
før det fikk sjansen til å konverteres ned. Hevet til 20 MB (server-
|
||
siden `app/storage.py` OG de tre nye klient-side sjekkene over,
|
||
samt feilteksten) -- rikelig for moderne telefonkamera-JPEG-er,
|
||
fortsatt en reell grense mot noe genuint urimelig stort.
|
||
|
||
Ren frontend + denne ene backend-konstanten, ingen migrasjon.
|
||
`tsc --noEmit` rent, `py_compile` rent. **Rullet ut live 2026-08-06**,
|
||
bruker bekreftet eksplisitt: `docker compose up -d --build teecup_api
|
||
teecup_frontend`, begge containere boot-et rent. `/health` → 200,
|
||
`/dashboard` → 200, `teeoff.no` upåvirket.
|
||
|
||
31. **Kommentarstrømmen på en runde sortert om: nyeste øverst — 2026-08-06.**
|
||
Brukeren ba om dette rett etter forrige punkt -- `round_message`-
|
||
listen (`round-messages.tsx`) var kronologisk eldst-først (samme
|
||
mønster som en vanlig chat), brukeren ville ha nyeste øverst i
|
||
stedet (en oppdateringsstrøm, ikke en samtale man leser fra start).
|
||
Endret `ORDER BY created_at` → `created_at DESC` i `GET /rounds/{id}/
|
||
messages` (`app/routers/round_messages.py`), og en ny post legges nå
|
||
øverst i listen lokalt (`[created, ...prev]` i stedet for `[...prev,
|
||
created]`). Lag-chatten (`messaging.py`) og org-oppslagstavlen
|
||
(`public-tournament.tsx`) er BEVISST uendret -- en ekte samtale
|
||
leses fortsatt kronologisk.
|
||
|
||
Verifisert i isolert scratch-miljø (postet tre meldinger, bekreftet
|
||
`GET`-rekkefølgen er nyeste-først). `tsc`/`py_compile` rent.
|
||
**Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt (samme
|
||
utrulling som punkt 30). `teeoff.no` upåvirket.
|
||
|
||
32. **"Nyeste først" utvidet til ALLE meldingsstrømmer — 2026-08-06,
|
||
overstyrer punkt 31 sin "bevisst uendret"-begrunnelse.** Brukeren:
|
||
"Nyeste først. Over alt." -- en eksplisitt, direkte instruks om at
|
||
lag-chatten og org-oppslagstavlen (som punkt 31 bevisst lot forbli
|
||
kronologiske, med en UX-begrunnelse om at "en ekte samtale leses
|
||
fortsatt kronologisk") OGSÅ skulle bli nyeste-først, på tvers av min
|
||
egen tidligere vurdering.
|
||
|
||
Endret `ORDER BY created_at` → `created_at DESC` for BÅDE
|
||
`list_team_messages` og `get_feed` (`app/routers/messaging.py`).
|
||
`team-chat.tsx`: WS-mottak og lokal post-oppdatering endret fra
|
||
append (`[...prev, msg]`) til prepend (`[msg, ...prev]`); fjernet
|
||
`bottomRef`-baserte auto-scroll-til-bunn-`useEffect`en (unødvendig
|
||
når nyeste alltid er øverst, synlig uten scrolling).
|
||
`public-tournament.tsx` sin `TournamentFeed`: samme prepend-endring i
|
||
BÅDE WS-mottak og `send()`.
|
||
|
||
Verifisert i isolert scratch-DB (fra `teecup_db`-mal): satte inn tre
|
||
meldinger med forskjøvne tidsstempler direkte i `message`-tabellen
|
||
for både `scope='team'` og `scope='tournament_feed'`, kjørte de
|
||
EKSAKTE SELECT-spørringene fra koden, bekreftet nyeste-først-
|
||
rekkefølge for begge. `tsc --noEmit`/`py_compile` rent.
|
||
`test_isolation.sql` uendret 12/12. Scratch-DB/rolle ryddet opp.
|
||
|
||
**Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt ("Ja
|
||
takk."): `docker compose up -d --build teecup_api teecup_frontend`,
|
||
begge containere boot-et rent (ingen migrasjon). `teeoff.no`
|
||
upåvirket.
|
||
|
||
33. **Emoji-reaksjoner + trådede kommentarer på innlegg — 2026-08-06, se
|
||
ADR-046 for full begrunnelse/beslutningsdetalj.** Samme melding som
|
||
punkt 32 ("... Husk også at man skal kunne like (eller bruke andre
|
||
emojier) og kommentere på innlegg."). Migrasjon 059
|
||
(`round_message_reaction`/`_comment`, `message_reaction`/`_comment`
|
||
-- to tabellpar, ett uten RLS speiler `round_message`, ett RLS'et
|
||
speiler `message`). Nye endepunkter i `round_messages.py` og
|
||
`messaging.py`: `PUT`/`DELETE .../reaction` (upsert, ett fast
|
||
kuratert emoji-sett, én reaksjon per bruker per innlegg), `GET`/
|
||
`POST .../comments` + `DELETE .../comments/{id}` (flat lagring,
|
||
frontend bygger tre-strukturen, kaskade-sletting av svar).
|
||
Autorisasjon speiler hver innleggstypes EKSISTERENDE post-/
|
||
slette-regler uendret (ingen ny modell).
|
||
|
||
**Én reell regresjon funnet og rettet FØR utrulling, under ekte
|
||
nettleserverifisering (ikke fanget av API-testskriptet alene):** de
|
||
nye kommentar-endepunktene i `round_messages.py` kalte først
|
||
`broadcast_round_update()` ved en inkonsekvens mot egen plan (planen
|
||
sa eksplisitt ingen live-push for kommentarer/reaksjoner i v1) --
|
||
dette trigget rundens eksisterende WS-tick, som fikk `RoundMessages`
|
||
til å refetche HELE meldingslisten og med det kollapse enhver
|
||
allerede-utvidet kommentartråd andre steder på siden ved hver eneste
|
||
kommentarhandling. Fjernet fra de nye endepunktene, bekreftet rettet
|
||
ved re-test (tråd forble utvidet gjennom en påfølgende reaksjon/
|
||
kommentar).
|
||
|
||
**Frontend:** ny delt `post-engagement.tsx` (via ÉN Claude-skrevet
|
||
V0-prompt), integrert i `round-messages.tsx`, `feed.tsx`
|
||
(`FeedCard`), `team-chat.tsx`, `public-tournament.tsx`
|
||
(`TournamentFeed`). To integrasjonsjusteringer utover ren copy-inn:
|
||
byttet V0-eksportens egendefinerte SVG-ikoner til `lucide-react`
|
||
(DESIGN_SYSTEM.md sin "ingen annen ikonpakke"-regel), og flyttet
|
||
`PostEngagement` UT av `feed.tsx` sin `FeedCard`-`<Link>` (var
|
||
opprinnelig nøstet inni hele kort-lenken -- ville trigget navigering
|
||
ved reaksjonsklikk og vært ugyldig nøstet interaktivt innhold).
|
||
|
||
**Kjent, bevisst forenkling (ikke en bug):** `public-tournament.tsx`
|
||
sin Oppslagstavle setter `canModerate={false}` alltid i UI-et --
|
||
backend håndhever fortsatt korrekt "forfatter ELLER org-admin"
|
||
uansett, men en org-admin ser ikke en slett-knapp for ANDRES
|
||
kommentarer der ennå (ren UI-fullstendighets-luke, ikke et
|
||
sikkerhetshull).
|
||
|
||
**Scratch-verifisert grundig, to runder:** (1) API-nivå -- full
|
||
autorisasjonsmatrise for alle tre innleggstyper (upsert-reaksjon
|
||
bekreftet ikke-stablende, tråding, kaskade-sletting, riktig
|
||
avvisning per type sin faktiske regel, RLS-isolasjon for
|
||
`message_reaction` eksplisitt bekreftet), `test_isolation.sql`
|
||
12/12, ekte produksjonsbuild av frontend kjørt og bekreftet ren.
|
||
(2) Ekte nettleser (egen scratch-Caddy for sesjon/WS) -- reagert,
|
||
byttet reaksjon, postet trådet samtale, slettet med kaskade-
|
||
bekreftelse, bekreftet `feed.tsx` sin Link-nøsting-fiks IKKE
|
||
trigger navigering, bekreftet komponenten rendrer korrekt i alle
|
||
fire flater uten konsollfeil. Begge scratch-miljøer (DB/rolle/API-/
|
||
frontend-/Caddy-containere/images) ryddet opp fullstendig.
|
||
|
||
**Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt ("Ja
|
||
takk"): migrasjon 059 kjørt mot ekte `teecup_db` (fire nye tabeller,
|
||
additiv, ingen endring av eksisterende data), deretter `docker
|
||
compose up -d --build teecup_api teecup_frontend`. Begge containere
|
||
boot-et rent. Bekreftet via live `openapi.json`: alle ni nye ruter
|
||
(`.../reaction` × 3, `.../comments` × 3, `.../comments/{id}` × 3)
|
||
riktig registrert og nåbare. `teeoff.no` upåvirket.
|
||
|
||
34. **Leaderboard lukket gapet mot en konkurrentapp-referanse — 2026-08-06,
|
||
se ADR-047 for full begrunnelse/beslutningsdetalj.** Brukeren viste en
|
||
video av GolfGameBook sitt leaderboard (trykk-for-å-utvide rad → fullt
|
||
hull-for-hull-scorekort + sosiale handlinger per spiller), ba om noe
|
||
minst like bra i TeeCup -- eksplisitt IKKE en kopi av utseendet.
|
||
Research avdekket at kjernemekanikken (utvidbare rader med
|
||
hull-for-hull-rutenett, egen form+farge-språk) allerede var bygget og
|
||
live for `IndividualBoard` siden 2026-07-26 -- denne runden lukket de
|
||
reelle gapene i stedet for å bygge om fra bunnen: ingen sosial-tilgang
|
||
noe sted i leaderboardet, ingen kompakt oppsummeringslinje,
|
||
`TwoSidedBoard`/`HighLowBoard` (to-sidede formater) hadde INGEN
|
||
utvidelse i det hele tatt, `TeamFlightBoard` hadde en flat,
|
||
ikke-utvidbar råscore-liste.
|
||
|
||
Avklart eksplisitt med bruker (AskUserQuestion) før bygging: (1)
|
||
`post-engagement.tsx` (ADR-046) reagerer på ETT spesifikt innlegg, ikke
|
||
runden -- alle utvidede rader lenker derfor til den SAMME, allerede
|
||
eksisterende kommentarseksjonen på rundesiden i stedet for en
|
||
(ikke-eksisterende) tråd per spiller. (2) To-sidede formater fikk ÉN
|
||
utvidbar detaljseksjon for hele kampen, ikke et per-rad-mønster (kun 2
|
||
sider, ikke en spillerliste).
|
||
|
||
Alt i `frontend/components/round-leaderboard.tsx` + anker-id på
|
||
kommentarseksjonen i `round-detail.tsx`/`watch-round.tsx`: `HoleByHole`
|
||
fikk en oppsummeringslinje + "Kommentarer"-lenke; `TeamFlightBoard`
|
||
sin råscore-liste konvertert til utvidbare rader (ny `TeamMemberRow`,
|
||
gjenbruker `HoleByHole` uendret); `TwoSidedBoard`/`HighLowBoard` fikk
|
||
en ny, delt `MatchDetailSection` som lazy-monterer den nå eksporterte
|
||
`MatchScorecardGrid` (`round-scorecard.tsx`, samme ADR-045-presedens).
|
||
Ingen ny V0-runde, INGEN backend-endring -- ren gjenbruk/eksport av
|
||
allerede godkjente komponenter.
|
||
|
||
**Funnet og rettet under nettleserverifisering:** nettleserens egen
|
||
anker-scroll (`#kommentarer`) rakk ikke frem siden elementet ikke
|
||
fantes i DOM-en før async rundedata var lastet -- løst med en liten
|
||
`useEffect` som trigger scrollet på nytt når dataen ankommer.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, ekte produksjonsbuild rent.
|
||
Isolert scratch-miljø, ekte nettleser: regresjon FØRST (slagspill-
|
||
formatets eksisterende utvidelse identisk før noe nytt ble testet),
|
||
deretter ny oppsummeringslinje+lenke (inkl. faktisk fungerende
|
||
anker-scroll) og ny `MatchDetailSection`/`MatchScorecardGrid`-utvidelse
|
||
bekreftet på et fourball-format med ekte 4-spiller-data, samme format
|
||
bekreftet identisk i `publicMode` (anonym tilskuer, egen isolert
|
||
nettleser-kontekst, kommentar-lenken riktig pekende til
|
||
`/watch/{id}#kommentarer`). `TeamFlightBoard`/`HighLowBoard` ikke
|
||
direkte browser-testet (ingen testdata for disse formatene i
|
||
scratch-DB-malen) -- vurdert lav risiko siden de gjenbruker nøyaktig
|
||
samme, allerede-testede kodeveier (`HoleByHole` uendret;
|
||
`MatchDetailSection`/`toGridRound`/`MatchScorecardGrid` uendret), se
|
||
ADR-047 for full begrunnelse.
|
||
|
||
**Egen feil funnet og rettet underveis, verdt å notere:** det første
|
||
forsøket på scratch-frontend-bygg pekte ved en kopier-lim-feil
|
||
`TEECUP_API_ORIGIN` mot den EKTE produksjons-API-en (`teecup_api`) i
|
||
stedet for scratch-containeren -- oppdaget da innlogging som en
|
||
scratch-testbruker konsekvent feilet. All interaksjon i det vinduet
|
||
var lesing (sidevisning/rad-utvidelse, ingen skriving/mutasjon), men
|
||
bildet ble likevel bygget på nytt med riktig scratch-API-peker og HELE
|
||
nettleser-verifiseringen kjørt om igjen fra bunnen mot den korrekt
|
||
isolerte stacken før noe ble konkludert.
|
||
|
||
Ren frontend, ingen migrasjon. **Rullet ut live 2026-08-06**, bruker
|
||
bekreftet eksplisitt ("Ja takk"): `docker compose up -d --build
|
||
teecup_frontend` (Compose gjenskapte også `teecup_api`-containeren i
|
||
samme kommando -- ingen kodeendring der denne runden, kun en ren
|
||
restart). Begge containere boot-et rent, `/health` → 200. `teeoff.no`
|
||
upåvirket.
|
||
|
||
35. **Reaksjonsraden viste kun et antall, ikke HVEM som reagerte —
|
||
2026-08-07.** Brukeren, rett etter å ha godkjent V0-eksperimentet på
|
||
`/logg-inn`: "Vi kan ikke se HVEM som liker noe i feeden. Det må på
|
||
plass." En reell, riktig funnet mangel i ADR-046-bygget fra dagen
|
||
før.
|
||
|
||
Løst uten noen ny tur-retur til serveren: `ReactionSummary` fikk et
|
||
nytt felt `reactors: list[str]` (fulle navn, roster-kontekst --
|
||
CLAUDE.md sin navneformat-regel), fylt i SAMME grupperte batch-
|
||
spørring som allerede beregnet antallet (`array_agg(...) ORDER BY
|
||
created_at`), i både `round_messages.py` (JOIN `app_user`, samme
|
||
"fornavn+etternavn eller display_name"-fallback som
|
||
`_resolve_round_message_author_name`) og `messaging.py` (LEFT JOIN
|
||
`player` for org-spesifikt visningsnavn, samme mønster som
|
||
`_resolve_author_display_name` -- fallback til e-postens lokaldel).
|
||
|
||
`post-engagement.tsx`: et alltid-synlig sammendrag under
|
||
reaksjonsraden ("Kari Nordmann og 3 andre reagerte" --
|
||
`formatReactorSummary()`, ALDRI kun synlig ved hover/tap alene,
|
||
tilgjengelighetsregelen), utvidbart (44px trykkflate) til en full
|
||
liste gruppert per emoji. Ingen ny henting -- dataen var allerede i
|
||
`reactionRow`.
|
||
|
||
**Scratch-verifisert:** isolert DB fra `teecup_db`-mal, egen API-
|
||
container. Bekreftet for begge tabellpar (`round_message_reaction`:
|
||
én reaksjon → riktig navn; `message_reaction`, org-scopet: to
|
||
brukere reagerer med samme emoji → begge navn i riktig rekkefølge,
|
||
org-spesifikt spillernavn brukt, ikke kontoens globale navn).
|
||
`tsc --noEmit`/`py_compile` rent. Ekte nettleser: sammendragslinjen
|
||
og den utvidbare per-emoji-listen begge bekreftet fungerende, ingen
|
||
konsollfeil. Scratch ryddet opp fullstendig.
|
||
|
||
Ren tillegg til eksisterende `ReactionSummary`-kontrakt (ingen
|
||
migrasjon, ingen brytende endring) + `post-engagement.tsx`.
|
||
|
||
**Rullet ut live 2026-08-07**, bruker bekreftet eksplisitt ("Ja
|
||
takk"): `docker compose up -d --build teecup_api teecup_frontend`.
|
||
Begge containere boot-et rent, `/health` → 200. `teeoff.no`
|
||
upåvirket.
|
||
|
||
36. **"Ingen"/"Alle"-hurtigknapp for venne-kategori-velgeren i
|
||
rundedeling — 2026-08-07.** Brukeren: "Der man, i et rundeoppsett,
|
||
velger hvem man skal dele runden med, så bør det være en 'bryter'
|
||
med 'Ingen (helt privat)' eller 'Alle'. Deretter velger eller
|
||
fravelger man etter det som passer en best." Avklart eksplisitt med
|
||
bruker (AskUserQuestion) hvilket av to funn passet: Privat/Venner/
|
||
Offentlig-hovedvalget forblir uendret (3-veis, uendret semantikk);
|
||
hurtigknappen legges TIL kategori-pillisten som vises når "Venner"
|
||
er valgt.
|
||
|
||
Fant samtidig et reelt, eksisterende avvik mellom de to stedene
|
||
denne velgeren finnes: opprettelses-veiviseren (`new-round.tsx`)
|
||
forhåndsvelger alle 10 kategoriene med opt-out-ordlyd, mens
|
||
rediger-dialogen (`round-detail.tsx`) verken har noen hurtigknapp
|
||
eller noe "ingen valgt"-varsel i det hele tatt (kun veiviseren hadde
|
||
det varselet fra før). Begge fikk nå identisk `Pill`-/knapp-basert
|
||
"Ingen"/"Alle" (setter kategorisettet til tomt/alle ti, finjusteres
|
||
deretter med enkeltpiller som før) OG samme "ingen kategori valgt"-
|
||
varsel -- lukker det avviket som en naturlig del av samme endring,
|
||
ikke en egen runde.
|
||
|
||
Ren frontend, ingen migrasjon, ingen ny backend-logikk (API-et tok
|
||
allerede imot tomme/fulle `visible_categories`-lister uendret).
|
||
`tsc --noEmit` rent. Scratch-verifisert i ekte nettleser for
|
||
rediger-dialogen (isolert DB-mal, egen API-/frontend-/Caddy-
|
||
container): "Ingen" tømmer alle ti pillene og viser varselet,
|
||
"Alle" fyller alle ti, begge fremhever seg selv korrekt når
|
||
tilstanden allerede matcher. Veiviserens versjon (identisk kode-
|
||
mønster, samme delte `Pill`-komponent) bekreftet via kodegjennomgang
|
||
i stedet for full klikk-gjennom (banesøk mot ekte teeoff-oppslag
|
||
gjorde en full E2E-kjøring uforholdsmessig tidkrevende for en
|
||
kodemessig identisk endring). Scratch ryddet opp fullstendig.
|
||
|
||
**Rullet ut live 2026-08-07**, bruker bekreftet ("ja"): `docker
|
||
compose up -d --build teecup_frontend`. Begge containere boot-et
|
||
rent, `/health` → 200. `teeoff.no` upåvirket.
|
||
|
||
37. **Slag-for-slag GPS-avstandsmåling — backend + skjema, 2026-08-07, se
|
||
ADR-048 for full begrunnelse/beslutningsdetalj.** Brukeren: "Måle
|
||
lenge på slag ... Fra der man er ELLER fra et valgt punkt på et
|
||
satellittfoto, til der man står ved siden av ballen. Man skal også
|
||
kunne si hvilken kølle man slo med. Dette kan deles i feeden."
|
||
Formell planleggingsrunde kjørt (Explore-agenter for
|
||
`round_hole`-skjema/scoreførings-UI/`BAG_CLUBS`/geolocation-bruk,
|
||
Plan-agent for konkret design), etterfulgt av AskUserQuestion for tre
|
||
gjenstående valg: v1 dekker BÅDE individuelle runder OG lagformater
|
||
fra start, delingstekst er auto-generert-men-redigerbar PLUSS et
|
||
satellitt-utsnitt av slaget, og måling er tilgjengelig både i
|
||
scoreførings-veiviseren og som en retroaktiv hull-kort-handling.
|
||
|
||
Migrasjon `060_round_shot.sql`: ny tabell, kjenner kun
|
||
`round_hole_id` (arver eierskap individuell/lagformat transitivt via
|
||
`round_hole` sin eksisterende XOR, migrasjon 031 -- ingen egen
|
||
XOR-logikk trengs). Eksplisitt, hull-tolerant `shot_number` (ikke
|
||
`ORDER BY captured_at`). Kun `UPDATE (shared_round_message_id)`
|
||
grantet -- ellers samme "slett og opprett på nytt"-prinsipp som
|
||
`round_message`.
|
||
|
||
Backend (`app/routers/rounds.py`): `ShotIn`/`ShotOut`/`ShotShareIn` +
|
||
seks endepunkter -- deltaker-GET/POST og side-GET/POST (speiler
|
||
`RoundHoleOut`/`RoundSideHoleOut` sitt eksisterende doble mønster for
|
||
å dekke lagformater), eierskap-agnostisk DELETE (join via
|
||
`round_hole` → `round_participant`/`round_side` for å bekrefte
|
||
tilhørighet til riktig runde), og `POST .../shots/{id}/share` som
|
||
genererer et satellitt-utsnitt SERVER-SIDE (Mapbox Static Images
|
||
API, ny `TEECUP_MAPBOX_SECRET_TOKEN`-setting i `config.py`, egen
|
||
polyline-encoder skrevet for topunkts-overlayet, URL-format
|
||
verifisert mot Mapbox sin offisielle dokumentasjon via WebFetch
|
||
fremfor antatt fra hukommelse) og laster opp via eksisterende
|
||
`storage.upload_image()` inn i en vanlig `round_message`-rad.
|
||
Rekkefølgen (slag opprettes ALLTID først, uten deling) unngår en
|
||
foreldreløs feed-melding ved en feilet slag-opprettelse. Mapbox-
|
||
kallet degraderer grasiøst til tekst-only ved manglende token/feil
|
||
(samme mønster som SMTP/push). `_resolve_round_message_author_name`
|
||
flyttet fra `round_messages.py` til `rounds.py` (unngår sirkulær
|
||
import, `rounds.py` var allerede den importerte parten) slik at
|
||
`share_shot` kunne gjenbruke navnefeltet uendret i stedet for en
|
||
duplisert kopi.
|
||
|
||
Frontend (kun det som IKKE avhenger av V0-eksporten ennå):
|
||
`frontend/lib/geo.ts` (ren Haversine, ingen avhengigheter). Det
|
||
eksisterende kølle-plukker-pill-mønsteret i `ScoringWizard`
|
||
(`round-detail.tsx`) trukket ut til en delt `ClubPicker`-komponent
|
||
(ren refaktor, `tsc --noEmit` rent før/etter). `mapbox-gl` lagt til
|
||
`package.json` (versjon slått opp direkte mot npm-registeret, ikke
|
||
gjettet) -- `@types/mapbox-gl` bevisst IKKE lagt til, `pnpm install`
|
||
varslet at den er en utdatert stub siden `mapbox-gl` nå leverer egne
|
||
typedefinisjoner. `pnpm-lock.yaml` regenerert (`pnpm install
|
||
--lockfile-only`) -- oppdaget under scratch-frontend-bygg at den
|
||
ELLERS ville vært ute av synk med `package.json`, noe som ville feilet
|
||
`pnpm install --frozen-lockfile` i BÅDE scratch- og ekte Docker-bygg
|
||
(fanget her, ikke først ved ekte utrulling). `NEXT_PUBLIC_MAPBOX_TOKEN`
|
||
tredd gjennom `frontend/Dockerfile` (KUN builder-steget, siden bruken
|
||
er ren klient-side -- speiler `TEECUP_API_ORIGIN`s build-tid-fallgruve
|
||
som allerede var dokumentert der) og `docker-compose.yml`. To tomme
|
||
plassholder-linjer lagt til i `.env` (`NEXT_PUBLIC_MAPBOX_TOKEN`,
|
||
`TEECUP_MAPBOX_SECRET_TOKEN`) -- MÅ fylles inn med reelle Mapbox-
|
||
kontoverdier før kart-/delings-bilde-flyten kan testes fullt ut eller
|
||
rulles ut live.
|
||
|
||
V0-prompt for selve `ShotMeasurementSheet` (alle steg + et bevisst
|
||
MOCK satellitt-kart-plassholder, ikke et ekte kartbibliotek -- ekte
|
||
Mapbox GL JS kobles på hånd-kodet etter eksport, for å unngå at V0
|
||
genererer en kart-komponent som remonterer per interaksjon og bryter
|
||
ADR-048 Beslutning B) skrevet og sendt til bruker. **Venter på
|
||
V0-eksport** før inngangspunktene (veiviser-knapp + hull-kort-merkelapp)
|
||
kan kobles til i `round-detail.tsx`.
|
||
|
||
**Scratch-verifisert grundig, kun backend** (isolert `teecup_scratch7`-
|
||
DB, alle 60 migrasjoner kjørt friskt, isolert `teecup_app_scratch7`-
|
||
rolle, isolert scratch-MinIO, engangs API-container). **Ett reelt
|
||
funn og umiddelbar retting UNDER selve scratch-oppsettet:**
|
||
migrasjon 002 hardkoder BÅDE rollenavn OG databasenavn i sin
|
||
`GRANT CONNECT ON DATABASE teecup_db`-linje -- rutinemessig
|
||
sed-omdøping av kun rollenavnet (`teecup_app` → `teecup_app_scratch7`)
|
||
før migrasjonene kjøres mot scratch-DB-en resulterte i at den nye
|
||
scratch-rollen fikk CONNECT-rettighet på den EKTE `teecup_db`
|
||
(kun en tilkoblings-rettighet, ingen tabell-/dataadgang fulgte med,
|
||
men uansett et utilsiktet avtrykk på ekte database uten bekreftelse).
|
||
Oppdaget og revokert umiddelbart (`REVOKE CONNECT ON DATABASE
|
||
teecup_db FROM teecup_app_scratch7`), verifisert at `teecup_db` sin
|
||
`datacl` var tilbake til nøyaktig samme tilstand som før. Ingen data i
|
||
`teecup_db` ble noensinne lest/skrevet. Notert her i tråd med
|
||
CLAUDE.md sin åpenhetsplikt -- vil unngå denne fallgruven ved å
|
||
ekskludere/håndtere migrasjon 002 sin databasenavn-linje separat neste
|
||
gang en fersk scratch-DB settes opp fra migrasjoner 001+.
|
||
|
||
Testmatrise kjørt mot ekte HTTP (magic-link-innlogging, ekte
|
||
sesjonscookie, `TEECUP_DEV_LOG_MAGIC_LINKS=true`): opprett slag
|
||
gps/gps og map_tap/gps (deltaker-eid hull), opprett slag på side-eid
|
||
hull (fourball-format), avstand >500m avvist (422), ugyldig lat
|
||
avvist (422), slett midt i sekvensen + bekreftet gap (neste slag fikk
|
||
nummer 3, ikke gjenbruk av 1), id-gjetting på tvers av runder avvist
|
||
(404), ikke-medlem avvist (403), linket medspiller kan måle for hele
|
||
flighten (bekreftet med en andre, faktisk innlogget testbruker),
|
||
slett/liste av ikke-eksisterende ressurser gir 404, delings-endepunkt
|
||
kjørt uten Mapbox-token satt (bekreftet grasiøs tekst-only-degradering,
|
||
`shared_image_url: null`, meldingen dukket opp korrekt i
|
||
`/rounds/{id}/messages` med riktig `author_display_name`-fallback).
|
||
`test_isolation.sql` 12/12 uendret. `py_compile` rent. Scratch-miljøet
|
||
(DB/rolle/API-/MinIO-container/image) ryddet opp fullstendig
|
||
etterpå, bekreftet tomt.
|
||
|
||
**Ikke testet i denne runden** (avhenger av ting bruker/V0 ennå ikke
|
||
har levert): selve satellitt-bilde-genereringen (krever en ekte
|
||
`TEECUP_MAPBOX_SECRET_TOKEN`), hele frontend-flyten (avhenger av
|
||
V0-eksporten), ekte nettleserverifisering av "null Mapbox-kall på
|
||
ren-GPS-veien"-kostnadskontroll-invarianten (ADR-048 Beslutning B) --
|
||
gjøres når V0-eksporten er integrert.
|
||
|
||
38. **Fast bunn-navigasjon manglet på fem av hovedsidene — 2026-08-07.**
|
||
Brukeren spurte, i forbindelse med dashbord-V0-forhåndsvisningen,
|
||
"hva skjedde med den sticky menyen i bunnen?" på andre sider enn
|
||
dashbordet. Undersøkt: `BottomTabBar` (eksportert fra
|
||
`dashboard.tsx`, migrasjon 2026-08-01) var kun faktisk importert i
|
||
`dashboard.tsx` selv og `more-menu.tsx` -- til tross for at BÅDE
|
||
`dashboard.tsx` sin egen kodekommentar ("vist på ALLE skjermer som
|
||
importerer denne") og CHANGELOG-oppføringen fra samme dag ("brukt av
|
||
flere sider") beskrev en bredere utrulling enn det som faktisk ble
|
||
koblet inn. Ingen ADR/CHANGELOG-oppføring dokumenterte en bevisst
|
||
innsnevring -- vurdert som en ufullstendig utrulling, ikke en
|
||
beslutning, og rettet direkte uten en egen avklaringsrunde (bekreftet
|
||
med bruker: fiks nå).
|
||
|
||
Lagt til i `own-rounds.tsx` (`active="rounds"` -- direkte fanemål),
|
||
`account-settings.tsx` (`active="profile"` -- direkte fanemål, Profil-
|
||
fanen peker allerede til `/account`), og `friends.tsx`/`feed.tsx`/
|
||
`notifications.tsx` (`active="more"` -- ingen av de tre er et direkte
|
||
fanemål, men alle tre er allerede listet som "Mer"-menyens egne
|
||
undersider i `more-menu.tsx`, så dette matcher appens eksisterende
|
||
IA i stedet for å oppfinne en ny). `<main>`-bunnpolstring justert til
|
||
`pb-24` (samme verdi som dashbordet selv bruker for å ikke overlappe
|
||
den faste raden) i de fire filene som ikke allerede hadde nok
|
||
(`notifications.tsx` hadde fra før `pb-28`, urørt). Ren tillegging av
|
||
en allerede ferdig, gjenbrukt komponent -- ingen ny logikk.
|
||
|
||
**Reelt funn og rettet UNDER selve arbeidet, ikke i selve
|
||
bunn-nav-fiksen:** `frontend/pnpm-lock.yaml` hadde vært ute av synk
|
||
med `package.json` siden `mapbox-gl`/`@types/mapbox-gl` ble lagt til
|
||
(ADR-048, punkt 37 over) -- oppdaget først her fordi dette var første
|
||
gang siden den endringen at en full `pnpm install --frozen-lockfile`-
|
||
Docker-bygg faktisk ble kjørt. Ville ha feilet ETHVERT fremtidig
|
||
scratch- ELLER ekte frontend-bygg. Rettet: `@types/mapbox-gl` fjernet
|
||
helt (pnpm varslet at den er en utdatert stub -- `mapbox-gl` leverer
|
||
egne typer nå), `mapbox-gl` endret fra `^3.28.1` til eksakt `3.27.0`
|
||
(3.28.x var under ett døgn gammel og feilet pnpms egen
|
||
minimumReleaseAge-leverandørkjede-policy inni Docker-bygget -- en
|
||
fornuftig sikkerhetssperre mot ferskpubliserte pakker, ikke en feil
|
||
i verktøyet), lockfile
|
||
regenerert (`pnpm clean --lockfile && pnpm install`).
|
||
|
||
**Scratch-verifisert i ekte nettleser** (åttende scratch-miljø denne
|
||
økten: isolert DB fra migrasjoner 001-060 kjørt friskt, isolert
|
||
`teecup_app_scratch8`-rolle, isolert scratch-MinIO, scratch-API- og
|
||
-frontend-container -- frontend bygget med riktig scratch-
|
||
`TEECUP_API_ORIGIN`). Logget inn med ekte magic-link-flyt, fullførte
|
||
profil, besøkte alle fem rettede sider (`/my-rounds`, `/my-friends`,
|
||
`/account`, `/my-feed`, `/my-notifications`) pluss dashbordet --
|
||
bunn-raden vises korrekt nederst uten å overlappe innhold på noen av
|
||
dem, riktig fane fremhevet grønt på hver (Runder/Profil/Mer×3), ingen
|
||
konsollfeil på noen av sidene. Scratch-miljøet ryddet opp fullstendig
|
||
etterpå (denne runden traff samme fallgruve som punkt 37 med
|
||
migrasjon 002s hardkodede `teecup_db`-referanse -- rettet i den
|
||
kopierte migrasjonsfilen FØR kjøring denne gangen, ikke etterpå, og
|
||
eksplisitt bekreftet at ekte `teecup_db` sin `datacl` var uendret
|
||
etterpå).
|
||
|
||
**Rullet ut 2026-08-08** sammen med punkt 40 og 41 (`docker compose
|
||
build teecup_frontend` + `up -d` -- ingen migrasjon, kun ny
|
||
frontend-image). Bekreftet ingen konsollfeil på
|
||
`https://teecup.golf/` etter omstart.
|
||
|
||
39. **Dashbordet reskinnet til "clubhouse"-paletten — 2026-08-07, bruker
|
||
bekreftet eksplisitt "full overtagelse, også bakgrunnen".** Egen,
|
||
parallell V0-utforskning (ikke samme retning som "Forest Green" fra
|
||
2026-08-01/02) — sendt som en eksplisitt EKSPLORERENDE prompt ("samme
|
||
ånd som login-siden-utforskningen... ikke bedt om å matche eksisterende
|
||
stil"), forhåndsvist for bruker (screenshots av en `npm install`+
|
||
`next dev`-kjøring av selve V0-eksporten, mock-data) FØR noe ble
|
||
integrert i den ekte, datakoblede `dashboard.tsx`.
|
||
|
||
**Viktig funn før integrering:** paletten (`--tee`/`--tee-strong`/
|
||
`--cup`/`--cup-strong`/`--clubhouse-*`) og `TeeCupWordmark`-komponenten
|
||
fantes ALLEREDE i prosjektet — fra en tidligere, ennå ikke besluttet
|
||
`/logg-inn`-utforskning (egen isolert sammenligningsside, additive
|
||
CSS-tokens i `globals.css`, rørte ikke skya-tokens). V0 gjenbrukte dem
|
||
konsekvent for dashbord-eksporten fordi de allerede var etablert i
|
||
prosjektkonteksten, ikke fordi det gjenbrukte "gamle" farger — bekreftet
|
||
ved faktisk pikselsampling av skjermdumpen (`#2f6b1e`/`#ff5427`, begge
|
||
forskjellige fra "Forest Green" sin `#1f6b08`/ingen oransje i det hele
|
||
tatt). `/logg-inn`s egen etablerte presedens ble fulgt: Bricolage-
|
||
visningsfonten IKKE lagt til (samme utelatelse som der), kun paletten
|
||
og wordmarken.
|
||
|
||
**Omfangsgrenser, avklart eksplisitt med bruker underveis (ikke
|
||
antatt):** `RoundCard`/`TournamentCard` (delt med `/my-rounds`) IKKE
|
||
re-stylet — beholder sin eksisterende shadcn-token-baserte styling
|
||
(viste seg uansett visuelt kompatibelt, siden begge alt bruker et
|
||
grønt-for-primær/oransje-for-sekundær-mønster). `BottomTabBar` (delt
|
||
med de fem sidene fra punkt 38) IKKE re-stylet direkte av meg — bruker
|
||
ba eksplisitt om en egen V0-prompt for den ("lag et prompt til V0, og
|
||
la den bestemme utseendet") fremfor at jeg skulle avgjøre det selv,
|
||
siden komponenten må fungere rimelig godt mot BÅDE den nye og den gamle
|
||
paletten avhengig av hvilken side den vises på. Egen prompt skrevet og
|
||
sendt, venter på eksport.
|
||
|
||
**Faktisk re-stylet:** header (erstattet flagg-i-sirkel med ekte
|
||
`TeeCupWordmark`), hilsen, `InstallPrompt` (KUN dens egen selvstendige
|
||
JSX-retur, mørkegrønn gradient endret til en tee-strong-forankret
|
||
gradient — all ekte iOS/Android-deteksjons-/14-dagers-utsettelses-logikk
|
||
uendret, samme "logikk uendret, kun styling"-disiplin som Forest Green-
|
||
runden fulgte), `QuickAction` (fikk `primary`/`expanded`-varianter som
|
||
matcher V0-eksportens mønster — "Ny runde" solid, de to andre kort-
|
||
stil), delt seksjon-"chrome" (`SectionHeader`/`SeeAllLink`/`EmptyState`/
|
||
`ShortcutButton`), "Venner på banen", `Sparkline`/`StatTile`/
|
||
`StatsSection`, "Spilte baner", "Venner"-oppsummeringen, og
|
||
organisasjon-foten. `AVATAR_HUES` (venne-avatar-fargene) beholdt
|
||
uendret — allerede nøytrale nok til å fungere i begge paletter.
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (niende
|
||
scratch-miljø denne økten, samme mønster som punkt 37/38 — inkludert
|
||
samme forsiktighet med migrasjon 002s databasenavn-linje, bekreftet
|
||
`teecup_db` uendret både før og etter): logget inn, fullført profil,
|
||
besøkt dashbordet i både tom tilstand og med ekte seed-data (pågående
|
||
runde, aktiv turnering som arrangør, statistikk, spilt bane) —
|
||
korrekt rendret i begge tilstander, `RoundCard`/`TournamentCard` sitter
|
||
visuelt fint på den nye bakgrunnen, "Ny turnering"-hurtighandlingen
|
||
utvidet korrekt og det eksisterende skjemaet (uendret) fungerte som før.
|
||
Ingen konsollfeil. Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
**Rullet ut 2026-08-08** sammen med BottomNav (punkt 40) og
|
||
ny-runde-veiviseren (punkt 41), som planlagt -- se punkt 41 for
|
||
utrullingsdetaljer.
|
||
|
||
40. **BottomTabBar erstattet med `BottomNav` fra egen V0-prompt —
|
||
2026-08-08.** Bruker ba eksplisitt om at bunn-navigasjonens utseende
|
||
skulle avgjøres av V0 selv, ikke av meg (se punkt 39) -- prompt skrevet
|
||
med eksplisitt kontekst om at komponenten deles mellom clubhouse- og
|
||
Forest Green-sider og må fungere rimelig godt mot begge, uten å få
|
||
oppgitt en fasit-retning. V0 valgte en bevisst palett-nøytral løsning:
|
||
en "flytende hvit flate" (halvtransparent hvit, backdrop-blur, egen
|
||
skygge) som ikke arver noen sideb palett -- kun den grønne
|
||
`tee`/`tee-strong`-aksenten (felles for begge paletter) brukes for
|
||
aktiv-tilstand.
|
||
|
||
Ny fil `components/teecup/bottom-nav.tsx`, eksporterer `BottomNav`
|
||
(ikke lenger `BottomTabBar`). Strukturell endring fra forgjengeren:
|
||
aktiv fane leses nå fra `usePathname()` internt (ikke en `active`-prop
|
||
utenfra) -- hrefs rettet fra V0s plassholdere (`/hjem`, `/runder` osv.)
|
||
til de faktiske rutene under integrering. Fjernet den gamle
|
||
`BottomTabBar`-definisjonen (og dens nå-ubrukte `C`-fargekonstant og
|
||
fire lucide-ikon-importer) fra `dashboard.tsx`; alle seks sider
|
||
(`dashboard.tsx`, `own-rounds.tsx`, `friends.tsx`, `account-
|
||
settings.tsx`, `feed.tsx`, `notifications.tsx`, `more-menu.tsx`)
|
||
importerer nå `BottomNav` fra den nye filen i stedet, uten `active`-
|
||
prop.
|
||
|
||
**To reelle bugs funnet og rettet UNDER scratch-verifisering (ikke i
|
||
selve V0-eksporten -- begge i MIN EGEN integreringskode):**
|
||
1. Forsøkte først å "forbedre" aktiv-fane-sammenligningen ved å
|
||
strippe bort et evt. `#hash` før sammenligning (siden "Turneringer"
|
||
sin href er `/dashboard#kommende-turneringer`). Dette var FEIL --
|
||
browser-verifisert til å få BÅDE "Hjem" og "Turneringer" til å vise
|
||
som aktive samtidig på `/dashboard`, siden begge da strippet til
|
||
samme sti. Reversert til V0s opprinnelige, rå strengsammenligning
|
||
(som aldri hadde dette problemet -- "Turneringer" matcher rett og
|
||
slett aldri via ren pathname, akkurat som forgjengeren uansett aldri
|
||
fremhevet den).
|
||
2. `/my-friends`, `/my-feed` og `/my-notifications` viste INGEN fane
|
||
som aktiv i det hele tatt (ren pathname-matching kjenner dem ikke
|
||
igjen som noen fanes mål) -- forgjengeren løste dette implisitt ved
|
||
at hver side selv sendte inn riktig `active`-prop. Lagt til en ny,
|
||
liten `matchPaths`-mekanisme på `Tab`-typen; "Mer" sin oppføring
|
||
fikk `matchPaths: ["/my-friends", "/my-feed", "/my-notifications"]`
|
||
-- speiler nøyaktig hvilke tre sider `more-menu.tsx` selv allerede
|
||
lister som sine undersider, ingen ny gruppering oppfunnet.
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tiende
|
||
scratch-miljø denne økten, samme migrasjon-002-forsiktighet som
|
||
punkt 37-39, `teecup_db` bekreftet uendret): logget inn, besøkt alle
|
||
syv sider som bruker `BottomNav` (dashbord, my-rounds, my-friends,
|
||
account, my-feed, my-notifications, more) -- riktig (og ETT AV GANGEN,
|
||
etter fiks 1) fane fremhevet på hver, ingen konsollfeil. Scratch-miljøet
|
||
ryddet opp fullstendig.
|
||
|
||
**Rullet ut 2026-08-08** sammen med punkt 39 og 41 -- se punkt 41 for
|
||
utrullingsdetaljer.
|
||
|
||
41. **`/my-rounds/new` erstattet med ny-runde-veiviser fra egen V0-prompt
|
||
— 2026-08-08.** Samme mønster som punkt 40: prompt skrevet med full
|
||
teknisk spesifikasjon (alle 18 spilleformer, alle steg/felt/
|
||
valideringsregler, alle 12 backend-endepunkt, `submitWizard()`-
|
||
kontrakten) og eksplisitt UTEN visuell føring -- V0 fikk velge
|
||
utseendet fritt. Resultat: en ny, selvstendig palett (`--nr-*` i
|
||
`globals.css`, kald blå/grå, `--nr-accent: #1f5fd6`), additiv og
|
||
scoped til veiviseren alene, samme "co-eksisterende paletter via
|
||
Tailwind arbitrary-value-syntaks"-mønster som clubhouse-paletten
|
||
(punkt 39) -- ingen `@theme inline`-kobling, ingen påvirkning på
|
||
resten av appen.
|
||
|
||
Gammel `components/new-round.tsx` (3364 linjer) SLETTET -- bekreftet
|
||
via grep at kun `app/my-rounds/new/page.tsx` importerte den (de tre
|
||
andre grep-treffene, i `course-template-editor.tsx`/
|
||
`round-leaderboard.tsx`/`round-detail.tsx`, var bare beskrivende
|
||
norske kommentarer, urørt). Ny modul under
|
||
`components/ny-runde/` (`wizard-shell.tsx`, `wizard-context.tsx`,
|
||
fem steg-filer, `primitives.tsx`, `progress.tsx`, `footer-bar.tsx`,
|
||
`create-course-form.tsx`) + `lib/ny-runde/` (`types.ts`,
|
||
`formats.ts`, `api.ts`).
|
||
|
||
V0s eksport brukte mock-data gjennomgående -- hele portingsjobben
|
||
var å koble hvert steg til de faktiske backend-kontraktene fra
|
||
`app/routers/rounds.py` og den gamle `new-round.tsx`, IKKE bare et
|
||
overflatisk bytte av komponentnavn: `Gender`-oversettelse
|
||
(`"m"|"f"` API ↔ `"mann"|"kvinne"|"annet"` UI), `Tee`-formen
|
||
forenklet til kun `{id, name, genders}` (aldri CR/Slope til klienten),
|
||
`CourseMeta`-discriminated-union for teeoff- vs. egen-bane, ekte
|
||
`POST /rounds` → `POST .../sides` → `POST .../participants` →
|
||
`PATCH .../participants/{id}`-sekvens i `submit()`, ekte
|
||
kontosøk/gjeste-oppslag i steg 3, ekte banesøk (teeoff-fasilitet +
|
||
egne baner, inkl. geolokasjon for "i nærheten") i steg 1.
|
||
|
||
**Fire reelle bugs funnet og rettet under scratch-verifisering:**
|
||
1. `TextInput` i `primitives.tsx` manglet `forwardRef` (feil i selve
|
||
V0-eksporten, ikke i portingen) -- ga 3 TS-feil der nedstrøms kode
|
||
sendte `ref` til komponenten. Rettet ved å pakke inn med
|
||
`forwardRef`, `tsc --noEmit` gikk fra 3 feil til rent.
|
||
2. Steg 3 viste "Utslag: Ikke valgt" for eieren selv etter at tee var
|
||
valgt i steg 1 -- min egen portingsfeil, jeg hadde ikke tatt med
|
||
den ekte `chooseCourse()`s side-effekt som synker valgt tee inn i
|
||
spillerlisten. Rettet ved å legge `players:
|
||
state.players.map(...)` til i `pickCourse()`
|
||
(`step1-course-time.tsx`). Verifisert rettet ved reload.
|
||
3. Manglende "Ingen"/"Alle"-hurtigknapp og "ingen kategori
|
||
valgt"-advarsel i steg 5s kategorivelger (samme mangel som ble
|
||
oppdaget og rettet i selve appen 2026-08-06, se punkt 36 -- V0s
|
||
eksport hadde ikke fått denne konteksten). Lagt til i
|
||
`step5-sharing.tsx`, samme mønster som punkt 36 (`role="group"`,
|
||
`aria-pressed`, `role="alert"` ved null valgt). Browser-verifisert
|
||
i scratch: "Ingen" tømmer alle 10 avkrysninger og viser advarselen,
|
||
"Alle" gjenoppretter alle.
|
||
4. Fulgte først en blindvei: trodde "Legg til gjest"-knappen i steg 3
|
||
var usynlig/klikk-slukende bak den klissede (`sticky bottom-0`)
|
||
`FooterBar`-en (samme feilklasse som `pb-32`-saken 2026-08-02,
|
||
nevnt i den gamle `new-round.tsx`s egne kommentarer) -- la til
|
||
`pb-28` på `<main>`. Padding-fiksen var faktisk RIKTIG og
|
||
nødvendig (uten den er det for lite scroll-klaring til å noensinne
|
||
få knappen helt fri av footeren), men den første reproduksjonen av
|
||
"feilen" var selv et testverktøy-artefakt: et koordinat-basert
|
||
klikk uten forutgående scroll traff footeren fordi den, ved
|
||
`scrollY: 0`, visuelt ligger over knappen (klissete element som
|
||
ikke har "festet seg" ennå fordi normal dokumentflyt allerede
|
||
plasserer det nederst i viewport). Bekreftet ved å måle
|
||
`getBoundingClientRect()` for begge før/etter scroll: uten scroll
|
||
overlapper de nesten fullstendig (y:776 vs. top:775); etter
|
||
`scrollIntoView` (174px, alt tilgjengelig scroll) står knappen på
|
||
y:602, godt klar. Et ekte, koordinat-basert klikk etter scroll
|
||
fungerte perfekt. Konklusjon: `pb-28`-fiksen var korrekt og er
|
||
beholdt (den gir nødvendig klaring når brukeren scroller helt ned),
|
||
ingen ytterligere kodefeil forelå.
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (ellevte
|
||
scratch-miljø denne økten, samme migrasjon-002-forsiktighet som
|
||
punkt 37-40, `teecup_db`s ACL bekreftet uendret før og etter): full
|
||
veiviser-gjennomkjøring for Fourball -- egen bane opprettet og valgt,
|
||
tee/HCP riktig synket til eier, gjest uten konto lagt til, begge
|
||
spillere tildelt hver sin side i steg 4, "Ingen"/"Alle"-toggle testet
|
||
i steg 5, fullført innsending. **DB-verifisert direkte**: `round`-rad
|
||
med riktig `play_format`/`visibility_mode`/bane-/tee-snapshot,
|
||
to `round_side`-rader, begge `round_participant`-rader korrekt
|
||
knyttet til hver sin `round_side_id` med riktig
|
||
HCP-/tee-/stat_level-snapshot. Ingen konsollfeil. Scratch-miljøet
|
||
(containere, images, DB, rolle) ryddet opp fullstendig etterpå.
|
||
|
||
**Rullet ut 2026-08-08** sammen med punkt 39 og 40, etter eksplisitt
|
||
brukerbekreftelse ("Rull ut alle tre samlet"): `docker compose build
|
||
teecup_frontend && docker compose up -d teecup_frontend` (kun
|
||
frontend-image, ingen migrasjon). Bekreftet `teecup_api`s image
|
||
UENDRET (bygget 2026-08-07, ikke i dag) -- containeren ble kun
|
||
re-opprettet fra samme eksisterende image pga. avhengighetskjeden i
|
||
`docker-compose.yml`, ikke bygget på nytt, så de uncommittede
|
||
backend-endringene som lå i arbeidstreet (`rounds.py`,
|
||
`handicap_engine.py` m.fl., urelatert til denne økten) ble IKKE
|
||
utilsiktet rullet ut. `https://teecup.golf/` bekreftet oppe, ingen
|
||
konsollfeil på innloggingssiden. Autentisert gjennomgang av
|
||
dashbord/BottomNav/ny-runde-veiviseren i produksjon overlatt til
|
||
brukeren selv (krever ekte pålogging).
|
||
|
||
42. **`/logg-inn` gjort til den EKTE innloggingssiden (erstatter `/` sin
|
||
gamle `LoginForm`) — 2026-08-08.** Brukeren la merke til at
|
||
innloggingssiden fortsatt viste "det gamle grensesnittet" etter
|
||
punkt 39-41s utrulling -- undersøkelse avdekket at `/logg-inn`
|
||
(clubhouse-palett, `TeeCupAuth`) var en fullstendig FORELDRELØS V0-
|
||
utforskning: INGENTING i appen lenket eller redirectet dit (bekreftet
|
||
ved grep), alle ~11 uautentisert-steder pekte fortsatt til `/` (gammel
|
||
`LoginForm`, Forest Green). Se dashboard.tsx sin egen kommentar
|
||
(linje 26-34) som allerede erkjente dette -- "fra en tidligere, ennå
|
||
ikke integrert /logg-inn-utforskning".
|
||
|
||
**Kritisk oppdagelse FØR arbeidet startet:** `TeeCupAuth` var 100 %
|
||
mock -- `apiSendMagicLink`/`apiPasswordLogin`/`apiJoinByCode` var alle
|
||
`sleep()`-baserte stubber (hardkodet demo-passord `"teecup123"`,
|
||
hardkodede demo-koder `TEECUP`/`RYDER-25`/`HOST2026`, en falsk
|
||
"2FA-kode sendes"-tekst som ALDRI faktisk sendte noe). Dette ble
|
||
eksplisitt flagget til brukeren (AskUserQuestion) FØR noe ble bygget,
|
||
siden dette er sikkerhetskritisk kode og omfanget var langt større enn
|
||
en ren redirect-ombytting -- brukeren bekreftet "gjør full port nå".
|
||
|
||
**Full port utført:**
|
||
- `components/teecup/teecup-auth.tsx` skrevet om fra bunnen: ekte
|
||
`POST /auth/request-link` (magic link), ekte
|
||
`POST /auth/login-password` (med `LoginResult`-status-håndtering
|
||
identisk med den gamle `LoginForm`), ekte
|
||
`GET /public/tournaments/by-code/{code}`. Alle demo-hint/mock-tekster
|
||
fjernet. `TwoFactorVerifyForm`/`TwoFactorSetupForm`
|
||
(`components/two-factor-flow.tsx`) gjenbrukt UENDRET -- samme
|
||
"delt komponent ikke re-stylet"-mønster som `RoundCard`/
|
||
`TournamentCard` i dashbord-reskinnet (punkt 39) -- lavest mulig
|
||
risiko for sikkerhetskritisk 2FA-kode.
|
||
- `app/logg-inn/page.tsx`: fikk samme server-side allerede-innlogget-
|
||
sjekk (`cookies()` + `/auth/me`) som `/`-siden hadde.
|
||
- `app/page.tsx` (root): redusert til en tynn videresending
|
||
(autentisert → `/dashboard`/`/account`, uautentisert → `/logg-inn`)
|
||
-- beholdt for gamle bokmerker/lenker til `teecup.golf/`. Gamle
|
||
`LoginForm`/`components/login-form.tsx` SLETTET (bekreftet ingen
|
||
andre importer via grep).
|
||
- Alle 11 `router.replace("/")`-steder (401-håndtering + logout) i
|
||
`dashboard.tsx`, `friends.tsx`, `more-menu.tsx`,
|
||
`account-settings.tsx`, `notifications.tsx`, `course-rounds.tsx`,
|
||
`own-rounds.tsx`, `rounds-stats-summary.tsx` byttet til
|
||
`router.replace("/logg-inn")`. `verify-form.tsx` sin
|
||
"Be om en ny lenke"-fallback-lenke (`href="/"`) byttet til
|
||
`/logg-inn`.
|
||
|
||
**Én reell bug funnet og rettet under scratch-verifisering:**
|
||
`app/logg-inn/page.tsx` kalte `redirect()` INNI `try`-blokken som
|
||
henter `/auth/me` -- Next.js sin `redirect()` fungerer ved å kaste en
|
||
egen `NEXT_REDIRECT`-kontrollflyt-exception som MÅ boble videre
|
||
urørt til Next.js sin render-maskineri. Den tomme `catch {}`-en rundt
|
||
slukte denne stille, så en allerede innlogget bruker som besøkte
|
||
`/logg-inn` fikk se innloggingsskjemaet på nytt i stedet for å bli
|
||
sendt videre -- oppdaget fordi en ekte innlogget test-bruker IKKE ble
|
||
omdirigert i scratch, mens en direkte nettleser-`fetch("/auth/me")`
|
||
(som går via `next.config.mjs` sin rewrite, ikke gjennom denne
|
||
server-komponentens egen kode) bekreftet sesjonen var helt gyldig --
|
||
avslørte at feilen satt i AKKURAT denne serverkomponentens egen
|
||
try/catch-struktur. Den opprinnelige `/`-sidens ekvivalente kode
|
||
unngikk dette ved å KUN sette `authenticated`/`profileComplete`-
|
||
variabler inni try/catch og kalle `redirect()` etterpå, UTENFOR
|
||
blokken -- samme mønster gjeninnført her. `tsc --noEmit` rent etter
|
||
fiks.
|
||
|
||
**Scratch-verifisert i ekte nettleser** (tolvte scratch-miljø denne
|
||
økten, samme migrasjon-002-forsiktighet som punkt 37-41 --
|
||
`teecup_db`s ACL bekreftet uendret før og etter; `TEECUP_DEV_LOG_MAGIC_LINKS=true`
|
||
brukt for å hente ekte magic-link-tokens fra API-loggen i stedet for å
|
||
sende ekte e-post til testadresser): full runde -- magic-link-
|
||
forespørsel + `/verify?token=`-innlogging (ny bruker auto-opprettet,
|
||
korrekt sendt til `/account` pga. ufullstendig profil), passord-
|
||
innlogging med feil passord (ekte feilmelding "E-post eller passord
|
||
er feil." fra `/auth/login-password`), invitasjonskode med ugyldig
|
||
kode (ekte feilmelding fra `/public/tournaments/by-code/`), logout
|
||
(→ `/logg-inn`), uautentisert besøk til `/dashboard` (→ `/logg-inn`,
|
||
ikke lenger `/`), `/` og `/logg-inn` besøkt allerede innlogget med
|
||
ufullstendig profil (→ `/account`) og med fullført profil
|
||
(→ `/dashboard`, satt via direkte `PATCH /auth/profile`-kall for å
|
||
unngå å klikke gjennom fødselsdato-datepickeren manuelt). Ingen
|
||
konsollfeil. **Ikke click-through-testet:** `2fa_required`-grenen
|
||
(krever en konto med 2FA allerede aktivert) -- vurdert lav risiko
|
||
siden `TwoFactorVerifyForm`/`TwoFactorSetupForm` er gjenbrukt helt
|
||
uendret fra den allerede beviste `LoginForm`-implementasjonen, kun
|
||
kablingen frem til dem (`handleLoginResult`) er ny kode, og den er
|
||
identisk portert fra `LoginForm`. Scratch-miljøet (containere, images,
|
||
DB, rolle) ryddet opp fullstendig etterpå, inkl. en disk-full-hendelse
|
||
underveis (`docker builder prune` frigjorde 11,75 GB build-cache fra
|
||
denne øktens mange scratch-bygg -- ingen kjørende containere eller
|
||
volumer berørt).
|
||
|
||
**Rullet ut 2026-08-08**, etter egen, eksplisitt brukerbekreftelse
|
||
separat fra punkt 39-41s batch-bekreftelse (sikkerhetskritisk --
|
||
autentisering). `docker compose build teecup_frontend && up -d` --
|
||
denne gangen ble kun `teecup_frontend` gjenskapt (`teecup_api` forble
|
||
"Running" med bekreftet uendret image-tidsstempel før/etter, ulikt
|
||
forrige utrulling i punkt 39-41 der en avhengighets-kjede-bivirkning
|
||
gjenskapte -- men ikke bygde om -- `teecup_api`). `https://teecup.golf/`
|
||
bekreftet å redirecte til `/logg-inn` med clubhouse-designet, ingen
|
||
konsollfeil.
|
||
|
||
43. **PRODUKSJONSBUG: "Laster baner…" hang for alltid ved offisiell
|
||
banevalg i ny-runde-veiviseren — funnet og fikset 2026-08-08.**
|
||
Brukeren rapporterte (skjermbilde) at steget etter å ha valgt et
|
||
teeoff-anlegg ("Baner") aldri kom videre fra "Laster baner…". Dette
|
||
hadde vært live siden punkt 41s utrulling -- treffer ALLE nye runder
|
||
som starter fra en offisiell (teeoff-) bane, ikke egne baner.
|
||
|
||
**Rotårsak:** `fetchFacilityCourses()` i `lib/ny-runde/api.ts` antok
|
||
at `GET /rounds/official-search/{slug}` returnerer banene direkte som
|
||
en array (`data.map(...)`), men det ekte endepunktet returnerer et
|
||
OBJEKT -- `OfficialFacilityDetail = {slug, name, courses: [...]}`
|
||
(`app/routers/rounds.py` linje 530). `data.map` er ikke en funksjon
|
||
på et objekt, så kallet kastet en `TypeError` -- og siden
|
||
`OfficialCourses` sin `useEffect` i `step1-course-time.tsx` kun hadde
|
||
`.then(...)` uten `.catch(...)`, ble denne exceptionen en stille,
|
||
ufanget promise-rejection: `setCourses` ble aldri kalt, og
|
||
`courses === null`-grenen ("Laster baner…") viste seg for alltid.
|
||
Bug i min egen porting (punkt 41) -- **denne konkrete stien
|
||
(offisiell teeoff-bane) ble aldri scratch-testet den runden**, kun
|
||
"egen bane"-veien ble klikket gjennom (se punkt 41s test-notat).
|
||
|
||
**To rettelser:**
|
||
1. `fetchFacilityCourses()`: leser nå `data.courses.map(...)` i
|
||
stedet for `data.map(...)`.
|
||
2. `OfficialCourses` (`step1-course-time.tsx`): la til en reell
|
||
`.catch()` + en `loadError`-tilstand (`role="alert"`, samme
|
||
`--nr-danger`-stil som resten av veiviseren) -- uten denne ville
|
||
ENHVER fremtidig feil her (f.eks. teeoff nede, 502) gitt nøyaktig
|
||
samme uendelige "Laster baner…"-hang på nytt, uansett om
|
||
datakontrakt-bugen over er rettet. Dette er defensivt, ikke bare
|
||
en fiks for det spesifikke tilfellet.
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (trettende
|
||
scratch-miljø denne økten, samme migrasjon-002-forsiktighet, `teecup_db`
|
||
bekreftet uendret) MOT EKTE teeoff-data (samme `teeoff_db`/`teeoff_api`
|
||
som produksjon deler, nådd direkte på `teeoff_default`-nettverket):
|
||
søkte opp "Hvaler Golfklubb" (reell fasilitet), banen ("Hovedbanen, 2
|
||
utslag") lastet korrekt i stedet for å henge, gikk videre til
|
||
felt-steget og bekreftet ekte utslag (Gul/Rød) hentet riktig fra
|
||
teeoff. Ingen konsollfeil (kun én forhåndseksisterende, ikke-relatert
|
||
"Deprecated feature"-info-melding fra nettleseren selv). Scratch-miljøet
|
||
ryddet opp fullstendig.
|
||
|
||
**Rullet ut umiddelbart** (produksjonsbug som traff alle live
|
||
brukere som prøvde å starte en runde på en offisiell bane) --
|
||
`docker compose build teecup_frontend && up -d`, kun frontend-image,
|
||
`teecup_api`s image-tidsstempel bekreftet uendret før/etter. Kunne
|
||
ikke click-through-verifisere selve produksjonssiden med ekte
|
||
brukerkonto (krever brukerens egen pålogging) -- basert på grundig
|
||
scratch-verifisering mot ekte teeoff-data rett før utrulling.
|
||
|
||
44. **PRODUKSJONSBUG: rundedeling med venner i flere kategorier virket
|
||
ikke som forventet — funnet og fikset 2026-08-08, se ADR-036
|
||
Beslutning E.** Brukeren rapporterte at en runde delt med kategorien
|
||
"Make" ikke ble synlig for vedkommendes ektefelle, til tross for at
|
||
"Make" var huket av. Diagnostisert direkte mot ekte `teecup_db`
|
||
(skrivebeskyttede spørringer): eieren (Erol) hadde kategorisert
|
||
ektefellen (Gina) under TRE kategorier (`close_family`,
|
||
`extended_family`, `spouse`), mens runden kun delte
|
||
(`close_family`, `golf_friends`, `spouse`) -- `extended_family`
|
||
manglet. `_can_view_round` krevde den gang at ALLE en venns
|
||
kategorier måtte være i rundens synlige sett (presisert av bruker
|
||
2026-07-29, kun dokumentert i kode-kommentarer, ALDRI i
|
||
ARCHITECTURE_DECISIONS.md -- selve dokumentasjonshullet som gjorde
|
||
dette vanskelig å spore tilbake), ikke bare én relevant kategori.
|
||
|
||
Brukeren fikk vist mekanismen konkret (hvilke kategorier Gina var
|
||
tagget i, hvilke runden delte, den strenge AND-regelen) og valgte via
|
||
et eksplisitt spørsmål å reversere til "minst én kategori er nok"
|
||
(som var den OPPRINNELIGE ADR-036 Beslutning B-regelen fra
|
||
2026-07-25 -- 2026-07-29-presiseringen hadde altså strammet inn en
|
||
regel utover det som noensinne ble ordentlig dokumentert som en
|
||
bevisst arkitekturbeslutning).
|
||
|
||
**Rettet i alle fire duplikate SQL-steder** (samme
|
||
kopier-inn-i-SQL-mønster som allerede omtalt i ADR-036 Beslutning B):
|
||
`app/routers/rounds.py` sin `_can_view_round` (selve
|
||
tilgangssjekken), `_friends_who_can_see_round` (varsel-fan-out ved
|
||
rundeopprettelse/fullføring), `list_friends_on_course`
|
||
(dashbordets "Venner på banen"); `app/routers/round_messages.py` sin
|
||
`/feed`-listing. Alle gikk fra "`NOT EXISTS` en kategori UTENFOR
|
||
synlig sett" til et enklere "`EXISTS` en kategori INNENFOR synlig
|
||
sett" -- enklere spørringer, ikke bare annen semantikk.
|
||
|
||
**Scratch-verifisert i ekte API-kall** (fjortende scratch-miljø denne
|
||
økten, samme migrasjon-002-forsiktighet, `teecup_db` bekreftet
|
||
uendret): gjenskapte den ekte Erol/Gina-situasjonen (venn kategorisert
|
||
i to kategorier, runde deler kun én av dem) -- vennen fikk nå 200 på
|
||
`GET /public/rounds/{id}` (tidligere 403). Bekreftet at motsatt
|
||
tilfelle (venn med INGEN overlappende kategori) fortsatt korrekt gir
|
||
403 -- ingen utilsiktet åpning av tilgangskontrollen. `/friends/
|
||
on-course` og `/feed` (de to andre endrede spørringene) begge 200
|
||
uten SQL-feil. Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
**Rullet ut umiddelbart** (produksjonsbug, brukerens egen delte runde
|
||
var konkret berørt) -- `docker compose build teecup_api && up -d`,
|
||
KUN backend-image (ingen migrasjon, `visibility_mode`/
|
||
`round_visible_category` er uendret skjema), `teecup_frontend`s
|
||
image-tidsstempel bekreftet uendret før/etter. **Bekreftet direkte
|
||
mot ekte data etter utrulling**: samme spørring som
|
||
`_can_view_round` nå bruker, kjørt skrivebeskyttet mot den ekte
|
||
Erol/Gina/runde-situasjonen, returnerer nå `true`.
|
||
|
||
45. **Slag-for-slag GPS-avstandsmåling — inngangspunkt 1 av 2, i
|
||
scoring-veiviseren — 2026-08-08 (ADR-048).** Backend (migrasjon
|
||
`060_round_shot.sql`, `ShotIn`/`ShotOut`/`ShotShareIn`-endepunktene i
|
||
`rounds.py`, `lib/geo.ts` Haversine, Mapbox-token-plumbing) var
|
||
allerede bygget og scratch-verifisert fra tidligere i økten (se
|
||
ADR-048 i ARCHITECTURE_DECISIONS.md). Denne runden: selve
|
||
frontend-flaten.
|
||
|
||
V0-prompt skrevet med `DESIGN_SYSTEM.md`s tokens som en HARD
|
||
begrensning (i motsetning til dashbord-/veiviser-/login-promptene
|
||
tidligere i økten, som bevisst fikk null designføring) -- dette
|
||
arket lever INNI den eksisterende Forest Green-skjermen
|
||
(`round-detail.tsx`), ikke som en ny frittstående side. Eksporten
|
||
(zip 4) var meget tro mot spesifikasjonen: `next/dynamic({ssr:false})`
|
||
for kartsteget, ingen `mapbox-gl`-import i det hele tatt på
|
||
GPS-only-stien, kartet mountes kun én gang per arkåpning
|
||
(tom-deps `useEffect`, klikk/drag re-initialiserer aldri).
|
||
|
||
`components/shot/shot-measurement-sheet.tsx` +
|
||
`components/shot/map-point-picker.tsx` kopiert inn uendret (kun
|
||
én import-sti rettet: V0s egen plassholder-`ClubPicker` byttet til
|
||
den nå faktisk utrukne, delte `components/teecup/club-picker.tsx`
|
||
-- ren utrekking fra `round-detail.tsx`s tidligere lokale kopi,
|
||
ingen atferdsendring for veiviserens eksisterende bruk). Ny
|
||
`components/ui/textarea.tsx`-shadcn-primitiv lagt til (fantes ikke
|
||
fra før).
|
||
|
||
Koblet inn som `ScoringWizard`s nye `roundId`-prop + lokal
|
||
`shotSheetOpen`/`shotCount`-state: "Mål et slag"-knapp rett etter
|
||
kølle-plukkeren i detalj-steget, henter eksisterende slag-antall on
|
||
mount (`GET .../holes/{n}/shots`), sender til ekte
|
||
`POST .../holes/{n}/shots` ved innsending og (hvis deling valgt)
|
||
en påfølgende `POST /rounds/{id}/shots/{shot_id}/share`.
|
||
|
||
**Scratch-verifisert** (femtende scratch-miljø denne økten, samme
|
||
migrasjon-002-forsiktighet, `teecup_db` bekreftet uendret): full
|
||
klikk-gjennomgang fra "Mål et slag" til arket åpner riktig, korrekt
|
||
feilmelding+"Prøv igjen" når GPS-tillatelse mangler (bekreftet ekte
|
||
-- CDP-automatiserte nettlesersesjoner har `geolocation`-tillatelse
|
||
permanent `denied` uten noen dialog å akseptere, en verktøy-
|
||
begrensning i selve test-miljøet, ikke i appen). Siden selve
|
||
GPS-suksess-stien derfor ikke lot seg klikke gjennom i denne
|
||
økten, ble de eksakte kallene `submitShot()` sender (opprett slag,
|
||
list slag, del) i stedet verifisert direkte mot API-et med samme
|
||
data-kontrakt -- alle tre 200/201, ingen feil i API-loggen. Bekreftet
|
||
i ekte nettleser at slag-tellingen faktisk oppdateres og vises i
|
||
knappeteksten ("Mål et slag (1 målt)") etter et slag er opprettet.
|
||
Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
**Rullet ut 2026-08-08** -- se punkt 48 for migrasjons-/
|
||
utrullingsdetalj (samlet med inngangspunkt 2 og gjest-tee-fiksen).
|
||
|
||
46. **Slag-for-slag GPS-avstandsmåling — inngangspunkt 2 av 2, alltid-
|
||
synlig merkelapp på hull-kortet — 2026-08-08 (ADR-048).** Fullfører
|
||
v1-kravet om å dekke BEGGE hull-eiertyper fra start (punkt 45 dekket
|
||
kun deltaker-eide hull via `ScoringWizard`).
|
||
|
||
Refaktorerte punkt 45s inline logikk (state + fetch + submit, som satt
|
||
direkte i `ScoringWizard`) ut til en ny, delt lokal funksjon
|
||
`ShotMeasurementEntry` i `round-detail.tsx` -- samme
|
||
"lokal gjenbruk innad i filen, ikke en egen delt-fil"-mønster som
|
||
resten av filens komponenter (`NumberPicker`/`Stepper`/`ChoiceRow`
|
||
m.fl., se `DESIGN_SYSTEM.md`s "Komponentmønstre"-seksjon), siden alle
|
||
tre bruksstedene nå ligger i samme fil. Komponenten bygger riktig
|
||
URL-base (`.../participants/{id}/holes/{n}/shots` vs.
|
||
`.../sides/{id}/holes/{n}/shots`) fra en enkel `owner: {kind, id}`-
|
||
prop -- ingen egen gren utover selve URL-en trengs, ADR-048s speilede
|
||
endepunkt-par dekker begge hull-eiertyper identisk.
|
||
|
||
Koblet inn tre steder:
|
||
- `ScoringWizard`s detalj-steg (uendret fra punkt 45, nå bare kalt
|
||
via den delte komponenten i stedet for egen kopi).
|
||
- `PlayerHoleCards` (deltaker-eide hull, hoved-"Score"-fanens
|
||
spillerkort): en liten merkelapp-rad lagt til som SØSKEN av kortets
|
||
store klikkbare knapp (ikke nøstet inni -- ugyldig HTML/ARIA å neste
|
||
`<button>` i `<button>`), synlig uavhengig av om veiviseren er åpen,
|
||
for retroaktiv måling.
|
||
- `SideScorecardGrid` (side-eide hull, delt-ball-formater): siden selve
|
||
scorekort-gridet er en tett 52px-per-hull-tabell uten plass til en
|
||
egen knapp per rute, ble to merkelapper (én per side, med lag-
|
||
etikett, f.eks. "Rødt lag: 2 slag målt") lagt til som en egen rad
|
||
rett under gridet, ved siden av "Forrige/Neste hull"-knappene.
|
||
|
||
**Scratch-verifisert i ekte nettleser** (sekstende scratch-miljø
|
||
denne økten, samme migrasjon-002-forsiktighet, `teecup_db` bekreftet
|
||
uendret) -- **med et ekte Mapbox-token satt inn i `.env` av brukeren
|
||
midt i denne runden** (`NEXT_PUBLIC_MAPBOX_TOKEN`, offentlig/URL-
|
||
restriktert; `TEECUP_MAPBOX_SECRET_TOKEN` for delings-satellittbilder
|
||
fortsatt ikke mottatt): opprettet én vanlig slagspill-runde (deltaker-
|
||
eid) og én foursome-runde med to sider (side-eid, via egen-opprettede
|
||
sider + en gjest lagt til side B) direkte mot API-et. Bekreftet
|
||
merkelappen vises korrekt og uavhengig av kortknappen i
|
||
`PlayerHoleCards`, åpner arket direkte (ikke via veiviseren). Testet
|
||
"Velg punkt på kart"-veien med det ekte tokenet -- Mapbox avviste med
|
||
403 (URL-restriksjonen tillater kun det ekte domenet, ikke dette
|
||
scratch-miljøets `localhost`-opprinnelse), som bekreftet at
|
||
feilhåndteringen i `MapPointPicker` fungerer rent (ren "Kunne ikke
|
||
laste kartet"-melding, ingen krasj) -- selve kart-SUKSESS-stien kunne
|
||
ikke click-through-testes i dette miljøet av samme grunn. For
|
||
side-eide hull: bekreftet begge merkelapper ("Blått lag"/"Rødt lag")
|
||
henter riktig antall fra sine respektive `.../sides/{id}/holes/{n}/
|
||
shots`-endepunkt (bekreftet i API-loggen), og at et slag opprettet på
|
||
én side kun oppdaterer DEN sidens merkelapp, ikke den andres. Samme
|
||
GPS-tillatelse-miljøbegrensning som punkt 45 gjaldt fortsatt for
|
||
GPS-suksess-stien; de eksakte `submitShot`-kallene ble derfor igjen
|
||
verifisert direkte mot API-et for begge eiertyper. Scratch-miljøet
|
||
ryddet opp fullstendig.
|
||
|
||
**Rullet ut 2026-08-08** -- se punkt 48.
|
||
|
||
47. **Ny-runde-veiviser: utslag lagt til direkte i gjesteskjemaet —
|
||
2026-08-08, brukertilbakemelding.** Brukeren rapporterte at
|
||
utslag-valget for en nyopprettet gjest ("midlertidig spiller") ikke
|
||
var intuitivt -- måtte inn i det ferdigopprettede gjestekortet
|
||
("Rediger") for i det hele tatt å SE hvilket utslag som var valgt.
|
||
|
||
Viste seg at `addGuest()` allerede valgte utslag automatisk
|
||
(`compatibleTees(course, gender)[0]?.id`), men helt stille -- selve
|
||
gjesteskjemaet (`GuestForm` i `step3-players.tsx`) hadde aldri et
|
||
synlig Utslag-felt, kun Fornavn/Etternavn/E-post/Kjønn/HCP/
|
||
Statistikk. Lagt til et kontrollert `teeId`-felt direkte i skjemaet,
|
||
samme `NativeSelect`-mønster som `PlayerCard`s tilsvarende felt,
|
||
med samme kjønn→utslag-resynk-logikk (`stillOk`-sjekk) som
|
||
`PlayerCard` allerede hadde -- inkludert i "Fyll inn automatisk"-
|
||
knappen for kjente gjester (samme feilklasse, ikke tidligere
|
||
rettet der).
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser**
|
||
(syttende scratch-miljø denne økten, samme migrasjon-002-
|
||
forsiktighet, `teecup_db` bekreftet uendret): opprettet en egen bane
|
||
med kjønns-eksklusive utslag (Gul kun menn, Rød kun kvinner) for å
|
||
bevise resynk-logikken fungerer -- byttet kjønn til Kvinne i
|
||
gjesteskjemaet, utslag-feltet oppdaterte seg umiddelbart til "Rød",
|
||
la til gjesten, og det ferdige spillerkortet viste "Utslag: Rød"
|
||
med det samme, uten å måtte åpne "Rediger". Ingen konsollfeil.
|
||
Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
**Rullet ut 2026-08-08** -- se punkt 48.
|
||
|
||
48. **Slag-for-slag GPS-avstandsmåling rullet ut mot ekte `teecup_db`/
|
||
`teecup_api`/`teecup_frontend` — 2026-08-08. Samler utrullingen for
|
||
punkt 45-47.** Bruker satte inn begge Mapbox-tokens midt i økten:
|
||
`NEXT_PUBLIC_MAPBOX_TOKEN` (offentlig, URL-restriktert til
|
||
`teecup.golf` -- bekreftet BEVISST, ikke en feil: avviste med 403 fra
|
||
et scratch-miljøs `localhost`-opprinnelse under punkt 46s
|
||
verifisering, nøyaktig som en URL-restriksjon skal virke) og
|
||
`TEECUP_MAPBOX_SECRET_TOKEN` (server-side, ingen URL-restriksjon
|
||
siden Static Images-kallet er server-til-server og aldri sender en
|
||
nettleser-`Referer`; kun offentlige `styles:tiles`/`styles:read`-
|
||
scopes, ingen skrive-/bruker-/tokens-tilganger -- backend-koden
|
||
trenger ikke mer).
|
||
|
||
**Attende scratch-miljø denne økten** (kun API + MinIO, ingen
|
||
frontend nødvendig -- rent backend-kall): bekreftet
|
||
`TEECUP_MAPBOX_SECRET_TOKEN` faktisk fungerer ende-til-ende --
|
||
opprettet en runde+slag, kalte `share_shot`, fikk et REELT,
|
||
ikke-null `shared_image_url` tilbake (176 KB AVIF-fil i MinIO, ikke
|
||
en tom/feilet fil). Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
**Migrasjon + utrulling mot ekte systemer, etter eksplisitt
|
||
brukerbekreftelse ("ja"):**
|
||
1. `060_round_shot.sql` kjørt mot ekte `teecup_db` -- kun ny tabell +
|
||
to `GRANT`-er (`SELECT/INSERT/DELETE` full, `UPDATE` begrenset til
|
||
`shared_round_message_id`), ingen endring i eksisterende skjema.
|
||
`\d round_shot` bekreftet alle CHECK-constraints/FK-er riktige.
|
||
`test_isolation.sql` kjørt på nytt mot ekte `teecup_db` rett
|
||
etter -- fortsatt 12/12 grønt.
|
||
2. `docker compose build teecup_api teecup_frontend && up -d
|
||
teecup_api teecup_frontend` -- begge bygget rent, ren oppstart i
|
||
loggene, ingen feil.
|
||
3. `https://teecup.golf/` bekreftet oppe, ingen konsollfeil.
|
||
Autentisert gjennomgang ("Mål et slag" i ekte nettleser) overlatt
|
||
til brukeren selv (krever brukerens egen pålogging, samme
|
||
begrensning som tidligere utrullinger denne økten).
|
||
|
||
Med dette er ADR-048 (slag-for-slag GPS-avstandsmåling) fullt bygget,
|
||
verifisert og live -- begge inngangspunkt, begge hull-eiertyper,
|
||
begge Mapbox-token-veier (kart-valg + delings-satellittbilde).
|
||
|
||
49. **PRODUKSJONSBUG: slagmåling ga alltid 0 meter, delinger forsvant
|
||
stille — 2026-08-08, brukerrapport rett etter punkt 48s utrulling.**
|
||
Brukeren: "den målte aldri mer enn 0 meter. Jeg så ingen bilder."
|
||
|
||
**Rotårsak 1 (selve 0-meter-buggen):** `shot-measurement-sheet.tsx`
|
||
sitt "end"-steg (ballens posisjon) avfyrte GPS-målingen AUTOMATISK i
|
||
et `useEffect` idet steget ble aktivt -- rett etter at startpunktet
|
||
var målt, med null tid for brukeren til faktisk å gå fra utslagsstedet
|
||
til ballen. Start- og sluttpunkt endte dermed på praktisk talt samme
|
||
sted, samme øyeblikk -- avstanden ble alltid ~0m, uavhengig av hvor
|
||
langt slaget faktisk var. Rettet ved å fjerne auto-avfyringen og
|
||
kreve et eksplisitt "Jeg er ved ballen nå"-trykk (samme mønster som
|
||
"Prøv igjen" ved feil, nå gjenbrukt for begge) -- brukeren går fysisk
|
||
til ballen FØR målingen skjer, i stedet for at appen antar de allerede
|
||
er der.
|
||
|
||
**Rotårsak 2 (stille tap, "ingen bilder"):** en konsekvens av
|
||
rotårsak 1 -- en 0m-avstand ble avvist av backendens
|
||
`distance_meters > 0`-validering (422), men `submitShot()` i
|
||
`round-detail.tsx` sin `ShotMeasurementEntry` gjorde da bare
|
||
`setOpen(false)` og returnerte -- INGEN feilmelding, arket lukket seg
|
||
stille som om alt var i orden. Brukeren fikk aldri vite at slaget
|
||
(og dermed en eventuell deling) aldri ble lagret. Rettet: `submitShot`
|
||
setter nå en `submitError`-state (parser backendens feilrespons,
|
||
som kan være enten appens vanlige `{detail:{message}}`-form ELLER
|
||
FastAPI/Pydantic sin rå valideringsform `{detail:[{msg,...}]}` --
|
||
bekreftet eksakt hvilken av de to ved å faktisk trigge en 422 mot
|
||
scratch-API-et: det er listeformen), viser feilen i arket
|
||
(`role="alert"`, ny `submitError`/`submitting`-prop på
|
||
`ShotMeasurementSheetProps`), og holder arket ÅPENT ved feil i stedet
|
||
for å anta suksess og lukke.
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (nittende
|
||
scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
|
||
`teecup_db` bekreftet uendret): CDP-automatiserte nettlesersesjoner
|
||
har `geolocation`-tillatelse permanent `denied` i dette miljøet (samme
|
||
begrensning som punkt 45-46), så `navigator.geolocation.
|
||
getCurrentPosition` ble midlertidig overstyrt via `evaluate_script`
|
||
for å drive den EKTE React-komponentens fulle flyt (ikke bare et
|
||
API-nivå-kall) -- bekreftet "Jeg er ved ballen nå"-knappen faktisk
|
||
vises i stedet for å auto-måle, at et ekte gap mellom start-/
|
||
sluttkoordinat ga "MÅLT LENGDE: 130 m" (ikke 0), at delingen faktisk
|
||
postet riktig tekst til `/rounds/{id}/messages`, OG (motsatt test,
|
||
identisk start-/sluttkoordinat) at en avvist 0m-innsending nå viser
|
||
feilmeldingen i arket i stedet for å lukke seg stille. Scratch-miljøet
|
||
ryddet opp fullstendig.
|
||
|
||
50. **Slag-for-slag GPS-avstandsmåling: liste over egne slag + fant en
|
||
TREDJE stille-feil-bug — 2026-08-08, brukeroppfølging.** Brukeren
|
||
prøvde på nytt etter punkt 49s fiks (bekreftet: målte nå 13 meter,
|
||
ikke 0), men spurte "jeg kan ikke se det delte slaget i ettertid?".
|
||
|
||
**Diagnose (skrivebeskyttede spørringer mot ekte `teecup_db` + ekte
|
||
API-logger):** slaget lå riktig i `round_shot` (13m, ikke 0 -- punkt
|
||
49s fiks virket), men `shared_round_message_id` var tom, og
|
||
`round_message`-tabellen hadde null rader for runden. API-loggene
|
||
viste at `POST .../shots` ga 201 Created, men det fantes INGEN
|
||
etterfølgende kall til del-endepunktet i det hele tatt. Konklusjon:
|
||
"Del i feeden"-valget var ikke aktivt idet brukeren trykket "Lagre
|
||
slag" (arket starter forfra, inkl. tilbake til "Behold privat"
|
||
-standardvalget, hver gang det åpnes på nytt -- forklart til
|
||
brukeren).
|
||
|
||
Brukerens motspørsmål var det egentlig viktige: **et privat, ikke-delt
|
||
slag burde uansett kunne SES i ettertid** -- merkelappen viste
|
||
tidligere kun et tall ("N slag målt"), aldri kølle/avstand for de
|
||
faktiske slagene. Dette var en reell mangel i inngangspunkt 2 (punkt
|
||
46), ikke bare en brukerforvirring.
|
||
|
||
**Bygget:** `ShotMeasurementEntry` lagrer nå hele slag-listen (ikke
|
||
bare `.length`), med en ny utvidbar `ShotList` (kølle + avstand per
|
||
slag, "Delt"-merke eller en "Del"-lenke for et ikke-delt slag, en
|
||
slette-knapp per slag via det allerede eksisterende
|
||
`DELETE /rounds/{id}/shots/{shot_id}`-endepunktet). Splittet den
|
||
tidligere ene knappen i to: en utvidbar "N slag målt"-disclosure
|
||
(kun synlig når `count > 0`) og en separat, alltid synlig "+"-knapp
|
||
for å måle et NYTT slag -- unngår at å åpne listen og å starte en ny
|
||
måling er samme handling.
|
||
|
||
**Tredje stille-feil-bug funnet og rettet i samme runde:** akkurat
|
||
som punkt 49s rotårsak 2, sjekket heller ikke re-del-fra-liste-kallet
|
||
(`shareExisting`) svaret sitt. Rettet parallelt med bygningen av
|
||
listen, IKKE etter en ny brukerrapport denne gangen. En mislykket
|
||
deling (enten ved førstegangs innsending eller re-del fra listen)
|
||
vises nå som en egen, kortvarig feiltekst ved siden av merkelappen
|
||
(`shareError`-state) -- BEVISST atskilt fra `submitError` (som
|
||
fortsatt holder selve MÅLE-arket åpent ved en avvist innsending):
|
||
siden selve slaget alerede er lagret på tidspunktet en delingsfeil
|
||
kan oppstå, ville gjenbruk av `submitError` (som holder arket åpent)
|
||
risikert at brukeren trykker "Lagre slag" på nytt og oppretter et
|
||
duplikat-slag.
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tjuende
|
||
scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
|
||
`teecup_db` bekreftet uendret): opprettet to slag direkte mot API-et
|
||
(Driver 215,5m, 7-jern 145m), bekreftet merkelappen viser "2 slag
|
||
målt" som en egen utvidbar knapp ved siden av en separat "+"-knapp,
|
||
utvidet listen og så begge slag med riktig kølle/avstand, klikket
|
||
"Del" på Driver-slaget og bekreftet det ble til "Delt" OG at meldingen
|
||
faktisk postet riktig tekst til `/rounds/{id}/messages`, slettet
|
||
7-jern-slaget og bekreftet det forsvant fra listen ("1 slag målt"
|
||
etterpå). Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
51. **PRODUKSJONSBUG: kart-steget viste "Kunne ikke laste kartet" for
|
||
alle -- CSS-arv-krasj med mapbox-gl.css — 2026-08-08, brukeroppfølging
|
||
("jeg ser ikke noe kart eller satellittfoto").** Bruker bekreftet
|
||
"alle tre" da spurt hvor de forventet kart/foto: (1) kart-valg under
|
||
måling, (2) forhåndsvisning i resultatsteget, (3) bilde i den delte
|
||
meldingen.
|
||
|
||
**(1) Rotårsak:** `mapbox-gl.css` (selve biblioteket sin stilark,
|
||
importert i `map-point-picker.tsx`) definerer `.mapboxgl-map {
|
||
position: relative }`. Mapbox GL JS legger `mapboxgl-map`-klassen til
|
||
PÅ kart-beholderen sin egen `<div>` ved initialisering -- denne
|
||
klassen kom SENERE i CSS-cascaden enn Tailwind sin `absolute`-klasse
|
||
(samme element), og vant dermed kappløpet, og overstyrte `position`
|
||
fra `absolute` til `relative`. Så snart `position` ikke lenger var
|
||
`absolute`, mistet Tailwind sin `inset-0`-klasse all effekt på
|
||
STØRRELSEN (den styrer kun posisjon for absolutt/fixed-plasserte
|
||
elementer) -- beholderen kollapset til `height: 0`, og selve
|
||
Mapbox-kartet (som TEKNISK sett lastet helt fint -- style/tiles/
|
||
events ga alle 200/204) ble usynlig, klippet vekk av en 0px-høy
|
||
forelder. Bekreftet direkte via `getBoundingClientRect()`-kjeden opp
|
||
DOM-treet: `containerRect.height: 0` til tross for at Mapbox sitt
|
||
eget `<canvas>`-element hadde en normal størrelse. Rettet ved å bytte
|
||
`absolute inset-0` til `h-full w-full` på beholder-diven -- løser
|
||
størrelsen via prosent-arv, upåvirket av hvilken `position`-verdi som
|
||
vinner cascade-kappløpet.
|
||
|
||
Denne konkrete feilen kunne IKKE vært fanget opp i noen av de
|
||
tidligere scratch-testene denne økten (femtende, sekstende), siden
|
||
Mapbox sitt eget offentlige token alltid ble avvist av URL-
|
||
restriksjonen mot `localhost`-scratch-opprinnelser der -- kartet
|
||
kom aldri langt nok til å faktisk RENDRE for at CSS-krasjen skulle
|
||
bli synlig. Verifisert denne runden ved å midlertidig overstyre
|
||
nettleserens `Referer`-header til `https://teecup.golf/` (CDP-nivå,
|
||
forbi selve nettleserens fetch()-header-restriksjon) for å simulere
|
||
det ekte, godkjente domenet i scratch -- avdekket samtidig at
|
||
scratch-testen selv hadde en snubletråd: en `grep`-basert token-
|
||
utpakking (`grep NEXT_PUBLIC_MAPBOX_TOKEN .env`, uten `^`-anker)
|
||
matchet FEILAKTIG også `.env` sin forklarende kommentarlinje over
|
||
selve variabelen (som også inneholder teksten "NEXT_PUBLIC_MAPBOX_
|
||
TOKEN"), og satte sammen kommentarteksten med selve tokenet til en
|
||
ugyldig verdi -- bekreftet at DENNE spesifikke feilen kun rammet
|
||
scratch-testverktøyet mitt, ikke selve produksjonsutrullingen
|
||
(`docker-compose.yml` sin `${NEXT_PUBLIC_MAPBOX_TOKEN}`-variabel-
|
||
substitusjon er upåvirket, bekreftet ved å grepe direkte i den ekte,
|
||
kjørende frontend-containerens bygde JS-bunt).
|
||
|
||
Underveis avdekket også en ekte SERVICE WORKER-cache-fallgruve verdt
|
||
å ha i bakhoden for senere feilsøking: en omstart av scratch-
|
||
frontend-containeren (SAMME port) beholdt en GAMMEL, cachet JS-bunt
|
||
i nettleseren til service workeren ble eksplisitt avregistrert og
|
||
cachen tømt -- PWA-installasjonen cacher altså aggressivt nok til å
|
||
overleve en full container-utrulling, noe som er relevant å huske
|
||
ved fremtidig feilsøking av "jeg ser fortsatt det gamle" -rapporter.
|
||
|
||
**(2) Bygget (ikke opprinnelig planlagt, brukerønske):** en client-
|
||
side forhåndsvisning i resultatsteget, FØR "Lagre slag" trykkes.
|
||
Bruker Mapbox Static Images API direkte som en `<img src>`, med det
|
||
OFFENTLIGE tokenet (trygt i nettleseren) -- ingen server-tur-retur
|
||
nødvendig kun for en forhåndsvisning, adskilt fra selve delingens
|
||
server-side-genererte bilde (som fortsatt inkluderer en linje mellom
|
||
punktene, ikke bare to nåler).
|
||
|
||
**(3) Allerede fungerende:** bekreftet tidligere denne økten
|
||
(scratch18, punkt 48) at selve delings-bildegenereringen fungerer
|
||
server-side med `TEECUP_MAPBOX_SECRET_TOKEN` -- brukerens opplevelse
|
||
av "ingen bilde" der skyldtes at selve DELINGEN aldri fullførte
|
||
(punkt 50s diagnose: "Del i feeden"-valget nullstilles hver gang
|
||
arket åpnes på nytt), ikke en feil i bildegenereringen selv.
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tjueførste
|
||
scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
|
||
`teecup_db` bekreftet uendret) MED `Referer`-overstyring for å komme
|
||
forbi URL-restriksjonen: kartet viste nå ekte satellittbilder (Oslo
|
||
sentrum, default-koordinat), et simulert klikk på kartet plasserte en
|
||
markør korrekt og aktiverte "Bekreft punkt", og resultatsteget viste
|
||
en korrekt forhåndsvisning med A/B-markører og riktig avstand (130m).
|
||
Scratch-miljøet ryddet opp fullstendig.
|
||
|
||
**Rullet ut umiddelbart** (produksjonsbug som traff ALLE brukere som
|
||
prøvde kart-basert måling) -- kun frontend-image, `teecup_api`
|
||
bekreftet uendret.
|
||
|
||
52. **PRODUKSJONSBUG: kartet sentrerte alltid på en hardkodet Oslo-
|
||
koordinat, aldri brukerens faktiske posisjon — 2026-08-08, umiddelbar
|
||
brukeroppfølging etter punkt 51.** Bruker: "Kartet viser ikke hvor
|
||
jeg faktisk er, men en eller annen vilkårlig by."
|
||
|
||
**Rotårsak:** `MapPointPicker` sin `center: [10.7522, 59.9139]` var
|
||
en HARDKODET Oslo-koordinat, ledsaget av en kommentar som hevdet
|
||
"real app centers on last-known position" -- den logikken fantes
|
||
ALDRI, kun påstanden i kommentaren. Kartet åpnet dermed alltid på
|
||
samme faste punkt i Oslo, uansett hvor i verden (eller landet)
|
||
brukeren faktisk befant seg.
|
||
|
||
**Rettet:** kartet henter nå brukerens ekte GPS-posisjon (ett
|
||
`getCurrentPosition`-kall, samme mønster som "Min posisjon nå"-veien
|
||
-- ingen løpende `watchPosition`) FØR selve Mapbox-kartet
|
||
initialiseres, og sentrerer der. Oslo-koordinaten beholdt KUN som
|
||
fallback for det tilfellet posisjon ikke kan hentes (avslått
|
||
tillatelse, tidsavbrudd, ingen støtte i nettleseren).
|
||
|
||
`tsc --noEmit` rent. **Scratch-verifisert i ekte nettleser** (tjueandre
|
||
scratch-miljø denne økten, samme migrasjon-002-forsiktighet,
|
||
`teecup_db` bekreftet uendret): overstyrte `navigator.geolocation` til
|
||
Bergen sentrum (60,39°N 5,32°Ø) og bekreftet kartet faktisk åpnet der
|
||
(synlig annen bystruktur enn Oslo-testen fra punkt 51, pluss Mapbox
|
||
sin egen posisjons-markør synlig i visningen) i stedet for på den
|
||
tidligere faste Oslo-koordinaten. Scratch-miljøet ryddet opp
|
||
fullstendig.
|
||
|
||
**Rullet ut umiddelbart** (samme produksjonsbug-alvorlighet som
|
||
punkt 51 -- traff alle som brukte kart-valget utenfor Oslo sentrum)
|
||
-- kun frontend-image, `teecup_api` uendret.
|
||
|
||
53. **Ballens posisjon (sluttpunktet) kan nå OGSÅ velges på kart, ikke
|
||
bare GPS — pluss et vist referansepunkt for utslaget — 2026-08-08,
|
||
brukerønske umiddelbart etter punkt 52.** Bruker: "Jeg må også kunne
|
||
velge på kartet hvor ballen ligger. Dessuten: Jeg vil gjerne se
|
||
kartet mens jeg går frem til ballen. Slagpunktet må være en del av
|
||
det jeg ser." Dette opphever ADR-048s opprinnelige "sluttpunkt:
|
||
alltid GPS, aldri kart"-del av flyten — se "Tillegg 2026-08-08" i
|
||
ARCHITECTURE_DECISIONS.md for full begrunnelse, historikken der er
|
||
beholdt, ikke overskrevet.
|
||
|
||
**Endringer:**
|
||
- Migrasjon `061_round_shot_end_method.sql`: ny `round_shot.end_method
|
||
text NOT NULL DEFAULT 'gps' CHECK (IN ('gps','map_tap'))` — speiler
|
||
`start_method` nøyaktig. `DEFAULT 'gps'` er historisk korrekt: ALLE
|
||
slag før dette tillegget ble faktisk målt med GPS for sluttpunktet.
|
||
- Backend (`app/routers/rounds.py`): `ShotIn.end_method` (default
|
||
`"gps"` for bakoverkompatibilitet), `ShotOut.end_method`,
|
||
`_SHOT_SELECT`/`_shot_out`/`_insert_shot` oppdatert til å lese/skrive
|
||
kolonnen.
|
||
- `frontend/components/shot/map-point-picker.tsx`: ny valgfri
|
||
`referencePoint`/`referenceLabel`-prop. Når satt (kun på
|
||
ball-steget): en FAST, ikke-flyttbar oransje markør ("Utslag") vises
|
||
på kartet, og punktet som faktisk plasseres ved tap er grønt i
|
||
stedet for oransje (samme `ff5a1f`/`2f7a3f`-fargekonvensjon som
|
||
delings-bildet allerede brukte, nå ført konsekvent i selve
|
||
UI-et også). Kartet henter brukerens live GPS-posisjon OG kjenner
|
||
referansepunktet, og bruker `map.fitBounds([nåværende posisjon,
|
||
referansepunkt])` slik at BEGGE garantert er synlige med det samme
|
||
(ikke bare sentrert på ett av dem) — faller tilbake til å sentrere
|
||
på referansepunktet alene (zoom 17) hvis GPS feiler.
|
||
- `frontend/components/shot/shot-measurement-sheet.tsx`: nytt
|
||
`end_map`-steg. "end"-steget tilbyr nå samme valg som "start":
|
||
"Jeg er ved ballen nå" (GPS, uendret) vs. "Vis kart mens jeg går"
|
||
(nytt — åpner `MapPointPicker` med `referencePoint={startPoint}`).
|
||
`goBack()` og `onMapStep`-sjekken oppdatert for det nye steget;
|
||
`endMethod`-state lagt til og sendt med i `onSubmit`-payloaden.
|
||
- `frontend/components/round-detail.tsx`: `submitShot()` sender nå
|
||
`end_method` i POST-kroppen.
|
||
|
||
`tsc --noEmit` og `python3 -c "import ast; ast.parse(...)"` begge
|
||
rene.
|
||
|
||
**Scratch-verifisert** (tjuetredje scratch-miljø denne økten, samme
|
||
migrasjon-002-forsiktighet, `teecup_db`s ACL bekreftet uendret
|
||
før/etter): (1) alle 61 migrasjoner (inkl. ny 061) kjørte rent mot
|
||
scratch-DB, `test_isolation.sql` fortsatt 12/12 grønt; (2)
|
||
`\d round_shot` bekreftet ny kolonne + CHECK-constraint, et direkte
|
||
forsøk på å sette `end_method='not_valid'` ble korrekt avvist; (3)
|
||
API-nivå via ekte innlogging (magic-link, `TEECUP_DEV_LOG_MAGIC_
|
||
LINKS=true`) og direkte HTTP-kall: `POST .../shots` med
|
||
`end_method: "map_tap"` lagret og returnerte korrekt, samme endepunkt
|
||
UTEN `end_method` i kroppen falt korrekt tilbake til `"gps"`
|
||
(bakoverkompatibilitet bekreftet); (4) full nettleser-gjennomgang via
|
||
Chrome DevTools MCP (samme Referer-header-omgåelse for det
|
||
URL-restrikterte Mapbox-tokenet, og samme
|
||
`navigator.geolocation.getCurrentPosition`-monkey-patch for CDP sin
|
||
faste geolocation-`denied`-begrensning, begge kjente fra punkt 51/52):
|
||
valgte "Min posisjon nå" for startpunktet, deretter "Vis kart mens jeg
|
||
går" for ballen — bekreftet visuelt at kartet åpnet med BÅDE
|
||
"Utslag"-referansemarkøren (oransje) og var zoomet/panorert slik at
|
||
den var synlig med det samme (fitBounds), trykket et punkt på kartet
|
||
og fikk en distinkt GRØNN markør for ballen, fullførte flyten til
|
||
lagring, og verifiserte til slutt med direkte SQL mot scratch-DB-en at
|
||
det lagrede slaget faktisk hadde `start_method=gps, end_method=
|
||
map_tap` — den nøyaktige blandede kombinasjonen brukerens ønske
|
||
beskriver. Scratch-miljøet (DB, rolle, MinIO, API- og
|
||
frontend-container/-images) ryddet opp fullstendig etterpå.
|
||
|
||
**Rullet ut** samme økt, bruker bekreftet eksplisitt ("Ja") — migrasjon
|
||
061 kjørt mot ekte `teecup_db`, `teecup_api` og `teecup_frontend`
|
||
bygget/restartet rent, `teecup_db`s ACL bekreftet uendret før/etter,
|
||
verifisert live på `teecup.golf` uten konsollfeil.
|
||
|
||
54. **Ball-steget viser nå alltid kartet med løpende (sanntids) posisjon
|
||
og avstand, og allerede målte slag viser satellittfoto i listen —
|
||
2026-08-08, umiddelbar brukeroppfølging etter punkt 53.** Bruker,
|
||
etter å ha sett skjermbilder av den nye funksjonen: "Jeg får ikke sett
|
||
slagene jeg allerede har målt... jeg ønsker å kunne se på et
|
||
satelittfoto hvor jeg har slått hvert slag. Det andre er at selv om
|
||
jeg velger 'min posisjon nå', så skal jeg se start og slutt på et
|
||
satelittfoto... I alle sammenhenger... ønsker jeg at jeg skal se
|
||
lengden så langt i sanntid mens jeg nærmer meg ballen." Se
|
||
"Tillegg 2026-08-08 (del 2)" i ARCHITECTURE_DECISIONS.md (ADR-048) for
|
||
full arkitektur-begrunnelse, inkl. det bevisste, avgrensede unntaket
|
||
fra "ingen løpende watchPosition"-prinsippet.
|
||
|
||
**Endringer, kun frontend (ingen migrasjon, `ShotOut` hadde allerede
|
||
alle koordinatene):**
|
||
- `map-point-picker.tsx`: `onConfirm` tar nå `(lngLat, method)` i
|
||
stedet for bare `(lngLat)`. Når `referencePoint` er satt (ball-
|
||
steget) startes `navigator.geolocation.watchPosition()` ved mount,
|
||
med en egen blå "du er her"-markør som oppdateres løpende og et
|
||
stort sanntids-avstand-tall ("AVSTAND SÅ LANGT") nederst på kartet,
|
||
beregnet med `haversineMeters(referencePoint, livePosition)`.
|
||
Footeren viser nå TO knapper når `referencePoint` er satt: "Bekreft
|
||
ballens posisjon" (trykket punkt, kun aktiv etter tap) og "Jeg er
|
||
ved ballen nå" (siste sporede posisjon, aktiv så snart første
|
||
GPS-fix er mottatt). `watchPosition` ryddes opp (`clearWatch`) ved
|
||
avmontering.
|
||
- `shot-measurement-sheet.tsx`: `end_map`-steget fjernet igjen (varte
|
||
kun én runde) — "end"-steget rendrer nå ALLTID `MapPointPicker`
|
||
direkte med `referencePoint={startPoint}`, ingen egen GPS/kart-valg-
|
||
skjerm lenger (den valget ligger nå inne i selve kartkomponentens
|
||
footer, se over). `measureEndPoint()`/`endStatus` fjernet (ikke
|
||
lenger i bruk). Egen duplikat Haversine-implementasjon
|
||
(`previewDistance`) erstattet med `haversineMeters` fra
|
||
`frontend/lib/geo.ts` (som alt fantes, men var ubrukt inntil nå) —
|
||
ren opprydding, ingen atferdsendring.
|
||
- `round-detail.tsx`: `ShotRecord`-typen utvidet med
|
||
`start_lat`/`start_lng`/`end_lat`/`end_lng` (allerede returnert av
|
||
API-et, kun frontend-typen manglet dem). `ShotList` viser nå et
|
||
160×160 satellitt-thumbnail per slag (samme offentlige token og
|
||
`pin-s-a`/`pin-s-b`-fargekonvensjon som forhåndsvisningen), bygget
|
||
client-side direkte fra de lagrede koordinatene — løser "jeg burde
|
||
jo se slaget selv, selv om det ikke er delt" sitt neste lag: ikke
|
||
bare klubbe/avstand som tekst, men HVOR slaget faktisk ble slått.
|
||
|
||
`tsc --noEmit` rent (ingen backend-endring denne runden).
|
||
|
||
**Scratch-verifisert** (tjuefjerde scratch-miljø denne økten, ingen
|
||
ny migrasjon å teste denne runden — samme 61 migrasjoner + `teecup_db`
|
||
ACL-forsiktighet som før): full nettleser-gjennomgang via Chrome
|
||
DevTools MCP med BÅDE `getCurrentPosition`- og `watchPosition`
|
||
monkey-patchet (sistnevnte simulerer en spiller som beveger seg fra
|
||
start mot ballen over ~5 sekunder via et `setInterval`). Bekreftet:
|
||
(1) sanntids-avstanden i kartet regner nøyaktig samme tall som en
|
||
uavhengig Python Haversine-kontroll (315,04 m); (2) "Jeg er ved ballen
|
||
nå" bruker siste sporede posisjon og lagret korrekt med
|
||
`end_method=gps` i databasen; (3) tap på kartet plasserer en grønn
|
||
markør og aktiverer "Bekreft ballens posisjon"; (4) en reell
|
||
grensesnitt-verifisering av EKSISTERENDE feilhåndtering (fra punkt 50)
|
||
skjedde underveis helt av seg selv: et tap som (ved et scratch-
|
||
test-uhell) landet ~0 m fra referansepunktet ble korrekt avvist av
|
||
backendens `distance_meters > 0`-sjekk (422), og arket viste riktig
|
||
feilmelding i stedet for å late som suksess — bekrefter at den
|
||
beskyttelsen fortsatt virker uendret gjennom hele denne
|
||
ombyggingen; (5) slag-listens nye satellitt-thumbnails lastet korrekt
|
||
for begge tidligere lagrede slag. Ingen uventede konsollfeil (kun
|
||
kjente scratch-miljø-artefakter: WebGL-fallback-advarsel, en
|
||
irrelevant WebSocket-tidsavbrudd, og den FORVENTEDE 422-en fra punkt
|
||
4). Scratch-miljøet (DB, rolle, MinIO, API- og frontend-container/
|
||
-images) ryddet opp fullstendig etterpå, `teecup_db`s ACL bekreftet
|
||
uendret.
|
||
|
||
**Rullet ut** samme økt, bruker bekreftet eksplisitt ("Ja") —
|
||
`teecup_frontend` bygget/restartet rent (ingen migrasjon eller
|
||
backend-endring denne runden, `teecup_api` uendret), verifisert live
|
||
på `teecup.golf` uten konsollfeil.
|
||
|
||
55. **Ny landingsside `/velkommen` for innlogget-men-ikke-fullført-profil
|
||
— 2026-08-09, eksplisitt brukerønske (ADR-049, se
|
||
ARCHITECTURE_DECISIONS.md for full begrunnelse og drøfting).** Bruker:
|
||
"Skjemaet med personlig informasjon vises for tidlig for ikke
|
||
registrerte spillere etter at de logger seg inn... Jeg tror det beste
|
||
er at man kommer til en side med oppfordring til å installere som app
|
||
(som vanlig) og med deaktiverte knapper for runde, turneringen og bli
|
||
med med kode. Trykker man på noen av disse skal man få beskjed om at
|
||
personlig informasjon må fylles ut først, med lenke til skjemaet."
|
||
Midtveis presisert: "Alle knappene bør egentlig være der, deaktivert.
|
||
bortsett fra til profilen" — dvs. HELE den vanlige bunn-navigasjonen
|
||
skal vises, ikke bare de tre hurtighandlingene.
|
||
|
||
**Rotårsak til det opprinnelige problemet:** tre steder rutet en
|
||
innlogget-men-ikke-fullført-profil-bruker RETT til `/account` sitt
|
||
påtvungne skjema uten noen kontekst: `app/page.tsx` (root-redirect),
|
||
`app/logg-inn/page.tsx` (post-innlogging-redirect), og
|
||
`dashboard.tsx` sin egen klient-side `profile_complete`-vakt
|
||
(`loadMe()`, fyres hvis en bruker skulle lande direkte på
|
||
`/dashboard`, f.eks. via `verify-form.tsx` som ALLTID sender dit
|
||
etter innlogging uansett profil-status).
|
||
|
||
**Bygget:**
|
||
- Ny `frontend/app/velkommen/page.tsx` (server-komponent, samme
|
||
autentiserings-/redirect-mønster som `/logg-inn/page.tsx` --
|
||
`redirect()`-kall bevisst holdt UTENFOR try/catch, samme fallgruve
|
||
som ble funnet og fikset i `/logg-inn/page.tsx` 2026-08-08 unngås
|
||
her fra start) + `frontend/components/teecup/velkommen.tsx`
|
||
(klient-komponent). Alle tre stedene over pekt om til `/velkommen`
|
||
i stedet for `/account`. `/velkommen` selv redirecter videre til
|
||
`/dashboard` (komplett profil) eller `/logg-inn` (ikke innlogget)
|
||
-- kan ikke nås "feil" via direkte URL.
|
||
- Siden gjenbruker eksisterende, allerede etablerte komponenter
|
||
uendret: `InstallPrompt` (samme PWA-oppfordring som dashbordet),
|
||
`TeeCupWordmark`, og samme "clubhouse"-palett-tokens som
|
||
`/logg-inn`/dashbordet (reskinnet 2026-08-07/08). Personlig
|
||
hilsen bruker `first_name ?? display_name`, samme navneformat-regel
|
||
som dashbordets hilsen (CLAUDE.md).
|
||
- Tre synlige, LÅSTE hurtighandlinger ("Ny runde"/"Ny turnering"/"Bli
|
||
med med kode" -- hengelås-ikon, `aria-disabled`, IKKE HTML
|
||
`disabled` siden de fortsatt skal være klikkbare for å forklare
|
||
hvorfor de er låst).
|
||
- `frontend/components/teecup/bottom-nav.tsx` utvidet med valgfrie
|
||
`disabledHrefs`/`onDisabledClick`-props (bakoverkompatibelt --
|
||
andre 6 sider som allerede bruker `BottomNav` uendret, ingen prop
|
||
sendt). Når en fane er i `disabledHrefs`, rendres den som en
|
||
`<button aria-disabled>` i stedet for `<Link>` -- `/velkommen`
|
||
viser dermed HELE den vanlige bunn-navigasjonen (Hjem/Runder/
|
||
Turneringer/Profil/Mer), med alle unntatt "Profil" låst, per
|
||
brukerens presisering midtveis.
|
||
- Trykk på EN HVILKEN SOM HELST låst kontroll (hurtighandling ELLER
|
||
bunnfane) viser samme delte påminnelse (`role="alert"`, fast
|
||
posisjonert rett over bunn-navigasjonen slik at den er synlig
|
||
uansett hvilken av de to gruppene som trigget den) med lenke videre
|
||
til `/account`.
|
||
- Presentasjons-seksjonen nederst er hentet fra `teecup-beskrivelse.md`,
|
||
skrevet om til kort UI-tekst (fire punkter: turneringsformater, live
|
||
scoreføring, WHS-handicap, sosialt) -- ikke limt inn rått.
|
||
|
||
`tsc --noEmit` rent. Ingen migrasjon eller backend-endring.
|
||
|
||
**Scratch-verifisert** (tjuefemte scratch-miljø denne økten): full
|
||
nettleser-gjennomgang av HELE kjeden med en HELT NY bruker (aldri sett
|
||
før, ikke forhåndsopprettet i databasen som tidligere scratch-økter)
|
||
-- registrerte e-post, verifiserte magic-link, landet automatisk på
|
||
`/velkommen` (bekrefter at BÅDE `verify-form.tsx` sin `/dashboard`-
|
||
redirect OG dashbordets egen vakt samvirker riktig -- vakten er den
|
||
som faktisk avgjør endestasjonen). Bekreftet: hilsen viser riktig navn,
|
||
`InstallPrompt` vises, hurtighandlinger viser hengelås og er
|
||
ikke-navigerende, bunn-navigasjonen viser alle fem faner med fire
|
||
låst og "Profil" aktiv, trykk på en låst hurtighandling viser
|
||
påminnelsen med korrekt lenke. Fulgte "Fyll ut nå" til `/account`,
|
||
fylte ut skjemaet ende-til-ende, endte korrekt på `/dashboard` med
|
||
riktig personlig hilsen ("God dag, Ny"). Ingen konsollfeil gjennom
|
||
hele kjeden. Scratch-miljøet (DB, rolle, MinIO, API- og
|
||
frontend-container/-images) ryddet opp fullstendig etterpå,
|
||
`teecup_db`s ACL bekreftet uendret.
|
||
|
||
**Rullet ut** samme økt, bruker bekreftet eksplisitt ("ja") —
|
||
`teecup_frontend` bygget/restartet rent (`teecup_api` uendret, ingen
|
||
migrasjon), verifisert live på `teecup.golf` uten konsollfeil.
|
||
|
||
56. **Fjernet "Bekreft ballens posisjon" (trykk-på-kartet for ballens
|
||
posisjon) igjen — 2026-08-09, samme dag som punkt 54/55, etter faktisk
|
||
bruk på ekte bane.** Bruker sendte et ekte skjermbilde fra telefonen
|
||
(utslags- og live-markør nesten overlappende, "AVSTAND SÅ LANGT 1 m")
|
||
og spurte om forskjellen mellom de to bekreftelsesknappene. Etter
|
||
forklaring: "bekreft ballens posisjon er unødvendig." Avklart via
|
||
spørsmål (siden dette reverserer noe brukeren selv ba om bare timer
|
||
tidligere, jf. CLAUDE.md "spør heller enn å gjette" ved usikkerhet om
|
||
omfang): fjern trykk-alternativet HELT, behold kartet (referansepunkt
|
||
+ sanntidsposisjon/-avstand), ballens posisjon bekreftes nå
|
||
UTELUKKENDE med "Jeg er ved ballen nå" (sporet GPS).
|
||
|
||
**Endringer, kun `map-point-picker.tsx`:**
|
||
- `map.on("click", ...)`-registreringen (trykk-for-å-plassere-markør)
|
||
er nå betinget på `!referencePoint` -- kjører fortsatt uendret på
|
||
start-steget, aldri lenger på ball-steget.
|
||
- "Trykk der ballen ligger"-banneret fjernet for ball-steget (ingen
|
||
trykk-handling å instruere om lenger); start-stegets "Trykk på
|
||
kartet for å plassere punktet" uendret.
|
||
- Footeren på ball-steget viser nå kun ÉN knapp ("Jeg er ved ballen
|
||
nå", primærstil) i stedet for to stablede knapper.
|
||
- Ingen backend-/migrasjonsendring: `end_method`-kolonnen og
|
||
`Literal["gps", "map_tap"]`-typen beholdes uendret (historiske
|
||
`map_tap`-rader fra den korte perioden funksjonen var live skal
|
||
fortsatt leses/vises korrekt -- kun hvordan NYE slag kan opprettes
|
||
er endret).
|
||
|
||
`tsc --noEmit` rent.
|
||
|
||
**Scratch-verifisert** (tjuesjette scratch-miljø denne økten, samme
|
||
forsiktighetsrutine, `teecup_db`s ACL bekreftet uendret): full
|
||
nettleser-gjennomgang med `getCurrentPosition`/`watchPosition`
|
||
monkey-patchet. Bekreftet at ball-steget nå viser kartet direkte med
|
||
KUN "Jeg er ved ballen nå" i footeren (ingen "Bekreft ballens
|
||
posisjon"), at et trykk midt på kartet ikke lenger gjør noe (ingen ny
|
||
markør, ingen banner om å trykke), og at "Jeg er ved ballen nå"
|
||
fortsatt lagrer korrekt -- `start_method=gps, end_method=gps`
|
||
bekreftet direkte i databasen etter en full måle-runde (start via GPS,
|
||
kølle, lagring). Ingen konsollfeil. Scratch-miljøet ryddet opp
|
||
fullstendig.
|
||
|
||
**Rullet ut** samme økt, bruker bekreftet eksplisitt ("ja") —
|
||
`teecup_frontend` bygget/restartet rent (`teecup_api` uendret),
|
||
verifisert live på `teecup.golf` uten konsollfeil.
|
||
|
||
57. **Sikkerhetsgjennomgang + fiks av alle funn — 2026-08-09/10, bruker:
|
||
"sjekk alle felter... sjekk alle URL-er. Er alt sikret godt nok? Lar
|
||
siden/appen seg hacke?", deretter "tett sikkerhetshullene først."**
|
||
Full gjennomgang (dedikert agent) av autentisering, autorisasjon på
|
||
tvers av alle 16 routere, input-validering, filopplasting, CORS/
|
||
nettverk, hemmeligheter i git. Se ADR-050 i ARCHITECTURE_DECISIONS.md
|
||
for full begrunnelse. Konklusjon: autorisasjonslaget er uvanlig
|
||
solid (ingen bekreftet IDOR), men fire reelle hull ble funnet og
|
||
fikset:
|
||
|
||
1. **Rate limiting** (HØY) lagt til på fem auth-endepunkter (ny
|
||
`app/rate_limit.py`) — ingen fantes fra før, passord/2FA-koder
|
||
kunne i praksis brute-forces.
|
||
2. **Sikkerhetshoder** (LAV-MIDDELS) lagt til i Caddy for
|
||
teecup.golf: CSP, X-Frame-Options, X-Content-Type-Options,
|
||
Referrer-Policy, HSTS.
|
||
3. **Input-validering** (LAV) — manglende `max_length`/`ge`/`le` lagt
|
||
til konsekvent i `rounds.py`/`players.py`/`round_messages.py`/
|
||
`messaging.py`, etter mønster som allerede fantes andre steder i
|
||
samme filer.
|
||
4. **BBB-endepunkt informasjonslekkasje** (informativt) — autorisasjon
|
||
flyttet til FØR formatsjekk i `update_bbb_hole`.
|
||
|
||
**Reell driftsfallgruve funnet og løst underveis:** `caddy reload`
|
||
(og en direkte admin-API-PUT) rapporterte suksess, men Caddyfile-
|
||
endringen slo aldri igjennom — `teeoff_caddy`s bind-mount av
|
||
Caddyfile-EN ENKELT FIL var bundet til en nå frikoblet inode etter en
|
||
atomisk skriving (skriv+omdøp) på verten. Løst med `docker restart
|
||
teeoff_caddy` (ikke bare reload) — se ADR-050 for full forklaring,
|
||
dette er en STÅENDE fallgruve for enhver fremtidig Caddyfile-endring.
|
||
|
||
`python3 -m py_compile` rent på alle endrede filer.
|
||
|
||
**Scratch-verifisert** (tjuesjuende scratch-miljø denne økten, samme
|
||
`teecup_db`-ACL-forsiktighet): rate limiting bekreftet å faktisk
|
||
utløse 429 ved riktig terskel på BÅDE `login-password` (10 tillatt,
|
||
11. avvist) og `request-link` (5 tillatt, 6. avvist); input-
|
||
validering bekreftet begge veier (HCP=99 avvist, HCP=18.4 akseptert,
|
||
50-tegns mobilnummer avvist); BBB-endepunktet bekreftet å returnere
|
||
403 NOT_AUTHORIZED (ikke lenger en 400 format-lekkasje) for en bruker
|
||
uten tilgang til en fremmed runde, OG fortsatt 200 OK for den
|
||
faktiske eieren på en ekte BBB-runde (regresjonssjekk). CSP-en
|
||
verifisert direkte mot PRODUKSJON i ekte nettleser (ikke scratch,
|
||
siden Caddy-konfigen er delt infrastruktur som ikke er en del av
|
||
scratch-oppsettet): ingen CSP-brudd i konsollen, et ekte Mapbox-kall
|
||
med gyldig token ga 200 OK, login-siden rendret visuelt korrekt (inline
|
||
styles uendret). `teecup_db`s ACL bekreftet uendret før/etter,
|
||
scratch-ressurser ryddet opp fullstendig.
|
||
|
||
**Rullet ut** — Caddy-delen ble rullet ut/verifisert direkte mot
|
||
produksjon underveis (se over). `teecup_api`-delen (rate limiting,
|
||
validering, BBB-fiks) bygget/restartet etter eksplisitt
|
||
brukerbekreftelse ("ja takk"), verifisert LIVE: `POST /auth/
|
||
request-link` mot ekte `teecup.golf` ga 200 på de fem første
|
||
forsøkene og 429 på det sjette (nøyaktig terskelen satt i koden),
|
||
ingen konsollfeil i ekte nettleser etterpå.
|
||
|
||
58. **13-årsgrense for kontoregistrering + to presentasjonstekst-
|
||
korrigeringer — 2026-08-10, bruker etter å ha lest to PDF-vedlegg.**
|
||
Se ADR-051 i ARCHITECTURE_DECISIONS.md for full begrunnelse og
|
||
drøfting av anbefalingene i "Barns personvern i golfapp"-PDF-en.
|
||
|
||
**Endringer:**
|
||
- `app/routers/auth.py`: `update_profile` avviser nå `PATCH /auth/
|
||
profile` med `400 UNDER_MINIMUM_AGE` hvis `birth_date` innebærer
|
||
under 13 år (eksakt dagsberegning). Dette er den ENESTE veien inn i
|
||
appen (profil må være komplett for å komme forbi `/velkommen`,
|
||
ADR-049), så dette ene stedet håndhever grensen for hele appen.
|
||
Bevisst IKKE utvidet til `player`-tabellen (organisasjonens
|
||
roster/CRM, `players.py`/`registration.py`) -- en `player`-rad er
|
||
en arrangørs registrering AV en person, ikke personen som
|
||
registrerer SEG SELV, og er selve PDF-ens egen anbefalte, tryggere
|
||
løsning for yngre spillere.
|
||
- `account-settings.tsx`: ny delt `computeAge()`-hjelpefunksjon,
|
||
brukt i BÅDE `ProfileOnboarding` (førstegangs-utfylling) og
|
||
`ProfileSection` (senere redigering under Konto) for umiddelbar
|
||
rød feilmelding + deaktivert lagre-knapp -- før serveren i det
|
||
hele tatt kontaktes.
|
||
- `install-prompt.tsx`: ny `iosBrowser()` skiller Safari fra Chrome
|
||
på iOS (`CriOS` i user agent) -- installasjonsteksten hevdet
|
||
tidligere feilaktig at man MÅTTE til Safari, og pekte til "nederst
|
||
i Safari" uansett nettleser. Siden iOS 16.4 kan Chrome installere
|
||
PWA-er direkte, med Del-ikonet et ANNET sted (oppe til høyre i
|
||
adressefeltet, ikke nederst). Ukjente iOS-nettlesere får nå en
|
||
nøytral "i nettleseren din"-tekst i stedet for en gjetning.
|
||
- `teecup-beskrivelse.md` + `velkommen.tsx`: "golfklubber" fjernet
|
||
fra målgruppe-beskrivelsen (to steder i beskrivelses-dokumentet,
|
||
ett i `/velkommen`s "Hva er TeeCup?"), nytt punkt lagt til om at
|
||
turneringsadministrasjon/-presentasjon fungerer minst like godt på
|
||
PC som på telefon. "Klubbhus-stemning" (design-retningens navn,
|
||
ren tone-beskrivelse) er bevisst uendret.
|
||
|
||
`tsc --noEmit` og `python3 -m py_compile` begge rene.
|
||
|
||
**Scratch-verifisert** (tjueåttende scratch-miljø denne økten, samme
|
||
`teecup_db`-ACL-forsiktighet): eksakt dagsgrense bekreftet med tre
|
||
API-kall (10-åring avvist, nøyaktig 13 år i dag akseptert med
|
||
`profile_complete: true`, én dag under 13 avvist). Ekte nettleser:
|
||
klientside rød feilmelding + deaktivert knapp bekreftet ved
|
||
2018-fødselsdato, `/velkommen`s presentasjonstekst bekreftet uten
|
||
"golfklubber" og med det nye PC-punktet. Ingen konsollfeil. Scratch-
|
||
miljøet (DB, rolle, MinIO, API- og frontend-container/-images) ryddet
|
||
opp fullstendig, `teecup_db`s ACL bekreftet uendret.
|
||
|
||
**Rullet ut** samme økt, bruker bekreftet eksplisitt ("Ja") — begge
|
||
containere bygget/restartet rent, verifisert live på `teecup.golf`
|
||
uten konsollfeil.
|
||
|
||
59. **Designrunden: full clubhouse-overtagelse (lys + mørk) — 2026-08-10,
|
||
se ADR-052.** Bruker ba om "designrunden" og valgte, via
|
||
`AskUserQuestion`, det bredeste av tre foreslåtte omfang: "Full
|
||
overtagelse, alt på én gang" — resten av appen (unntatt
|
||
ny-runde-veiviserens `nr-*`-palett) skulle over på "clubhouse"-
|
||
paletten i én samlet runde, ikke gradvis skjerm for skjerm som
|
||
tidligere planlagt.
|
||
|
||
**Root cause / tilnærming:** tre paletter levde side om side —
|
||
"Forest Green" (shadcn-tokens, ~90 filer), "clubhouse" (7 filer:
|
||
innlogging/dashbord/bunnnav/velkommen), "nr-*" (ny-runde-veiviseren,
|
||
utenfor omfang). En Explore-agent bekreftet at `components/ui/*` OG
|
||
de ~90 gjenværende filene er RENE token-konsumenter (null hardkodet
|
||
hex funnet noe sted utenfor de 7 clubhouse-filene) — konsekvensen:
|
||
hele overtagelsen kunne gjøres ved å redefinere shadcn-tokenene i
|
||
`frontend/app/globals.css` (`:root`/`.dark`/media-blokk) til
|
||
clubhouse sine faktiske verdier, i stedet for å endre className i 90
|
||
filer. `--destructive`/`--brand-orange`/`--info`/`--gold`/
|
||
`--chart-1..6` bevisst UENDRET (egne semantiske signalfarger, ikke
|
||
del av identiteten paletten styrer).
|
||
|
||
**Kontrast-korreksjon funnet FØR utrulling:** `--primary-foreground`
|
||
måtte endres fra nesten-svart til hvit — clubhouse sin
|
||
`--tee-strong` (`#2f6b1e`) er en MØRK grønn (brukes allerede med
|
||
`text-white` i `velkommen.tsx`), ulikt den gamle LYSE primærgrønnen.
|
||
|
||
**Mørk variant designet av V0 på bestilling** (ingen fantes fra før —
|
||
clubhouse var "fast lys-modus"). Claude skrev en ren fargeoppgave-
|
||
prompt (ti låste lyse verdier som fasit, eksplisitt WCAG AA-krav),
|
||
bruker limte den inn i V0 og limte svaret tilbake som en prosjekt-zip
|
||
(`globals.css`-diffen inneholdt kun ny `.dark`-blokk). Claude
|
||
verifiserte V0s egne kontrasttall uavhengig (egen WCAG-beregning) —
|
||
alle stemte eksakt: ink/bg 15.7:1, muted/bg 8.2:1, muted/card 7.2:1,
|
||
hvit/tee-strong 4.76:1.
|
||
|
||
**Reelt funn under planlegging, sparte en unødvendig kodeendring:**
|
||
opprinnelig plan antok de 7 clubhouse-filenes hardkodede
|
||
`bg-clubhouse-*`/`bg-tee-strong`-klassenavn måtte skrives om til
|
||
generiske tokens for å følge mørk modus. Viste seg unødvendig —
|
||
siden en `.dark`/media-blokk for de RÅ `--tee`/`--cup`/
|
||
`--clubhouse-*`-variablene ble lagt til (symmetri med
|
||
hovedremappen), arver disse 7 filene mørk modus automatisk via
|
||
CSS-variabel-cascade, uansett hvilket klassenavn de bruker. Ingen av
|
||
de 7 filene endret.
|
||
|
||
Liten tilleggsrettelse: `app/layout.tsx`s `themeColor`-metadata
|
||
(`#8BC24A`/`#1c261d`) pekte fortsatt på gamle farger — oppdatert til
|
||
de nye bakgrunnsfargene (`#f3f6ec`/`#131a0f`).
|
||
|
||
`DESIGN_SYSTEM.md` oppdatert til å beskrive clubhouse som selve
|
||
hovedpaletten (var tidligere "Forest Green").
|
||
|
||
**Scratch-verifisert** (full stack: ny scratch-DB med alle 61
|
||
migrasjoner, `teecup_app_dr1`-rolle, hyphenert scratch-MinIO, scratch
|
||
API- og frontend-container — ekte produksjonsbuild av frontend, ikke
|
||
bare `tsc`). Data seedet via ekte API-kall (personlig bane, runde,
|
||
hullscore), ikke rå SQL. `tsc --noEmit` og produksjonsbuild begge
|
||
rene. Browserverifisert i BÅDE lys og mørk modus (Chrome DevTools
|
||
MCP, `emulate colorScheme`) på `/logg-inn`, `/velkommen`,
|
||
`/dashboard` (tom + med data), `/my-rounds/[id]` (appens største
|
||
fil, 6726 linjer, tidligere Forest Green), scorekort-tabellen (tett
|
||
datagrid), `/account` (skjematung side) — alle konsistente, god
|
||
kontrast, ingen konsollfeil utover en harmløs PWA-infomelding.
|
||
`/my-rounds/new` bekreftet visuelt uendret (ingen smitte inn i
|
||
`nr-*`). `BottomNav`s bevisst palett-nøytrale hvite flate forblir
|
||
hvit i mørk modus også — eksisterende, bevisst V0-designvalg, ikke
|
||
en regresjon. `teecup_db`s ACL bekreftet uendret før/etter, alle
|
||
scratch-ressurser ryddet opp fullstendig.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Ja takk") —
|
||
`docker compose build teecup_frontend && docker compose up -d
|
||
teecup_frontend`, ren omstart. Bekreftet live på `teecup.golf` i ekte
|
||
nettleser i BÅDE lys og mørk modus, ingen konsollfeil utover den
|
||
harmløse PWA-infomeldingen.
|
||
|
||
60. **Ny-runde-veiviseren retemaet til clubhouse — 2026-08-10, se
|
||
ADR-053.** Rett etter forrige punkt ba bruker om at veiviseren
|
||
(`nr-*`-paletten, bevisst holdt utenfor punkt 59 som en tredje,
|
||
uavhengig V0-palett) også skulle få det nye designet.
|
||
|
||
Samme remap-prinsipp som punkt 59, samme fil (`globals.css`): de 16
|
||
`--nr-*`-variablene komponentene i `components/ny-runde/*` leser
|
||
(rå `var(--nr-x)`, bekreftet null hardkodet hex noe sted) fikk nye
|
||
verdier — `--nr-bg/-surface/-surface-2/-ink/-muted/-border/-accent/
|
||
-accent-ink` byttet til clubhouse (lys+mørk), `--nr-danger(-soft)`/
|
||
`--nr-ok(-soft)` beholdt sin egen røde/grønne hue i lys modus
|
||
(samme prinsipp som uendret `--destructive`), men fikk NYE mørke
|
||
varianter siden `.nr` aldri hadde mørk-modus-støtte før. Fire
|
||
variabler uten direkte clubhouse-kilde (`--nr-faint`, `--nr-border-
|
||
strong`, `--nr-accent-soft`, `--nr-accent-ring`) ble avledet
|
||
(alfa-blandet) fra clubhouse sine godkjente farger, kontrastsjekket
|
||
til aldri å havne under originalens egne verdier. Ingen av de 11
|
||
filene i `components/ny-runde/*` endret.
|
||
|
||
**Scratch-verifisert** (eget scratch-miljø, samme fulle DB/rolle/
|
||
MinIO/API/frontend-oppsett som punkt 59). `tsc --noEmit` rent.
|
||
Browserverifisert i BÅDE lys og mørk modus: bane-valg, "Egen
|
||
bane"-tom-tilstand (øver `--nr-faint`), og det 18-raders tette
|
||
hull-for-hull-skjemaet (par/stroke-indeks-dropdowns). Alle
|
||
konsistente, god kontrast. Tre pre-eksisterende DOM-lint-varsler
|
||
(manglende `autocomplete`/`id`-attributter i selve V0-markupen) —
|
||
uendret fra før denne CSS-only-runden, ikke forårsaket av den.
|
||
Scratch-ressurser ryddet opp fullstendig, `teecup_db`s ACL bekreftet
|
||
uendret.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Deretter;
|
||
slett kontoen, og gjør det resterende live") — ren omstart av
|
||
`teecup_frontend`, bekreftet live på `teecup.golf` uten konsollfeil
|
||
utover den harmløse PWA-infomeldingen.
|
||
|
||
61. **PRODUKSJONSHENDELSE: konto under 13-årsgrensen slettet — 2026-08-10.**
|
||
Bruker ba om en engangssjekk av ekte `teecup_db` (read-only SELECT,
|
||
eksplisitt bedt om av bruker) etter at 13-årsgrensen (punkt 58) gikk
|
||
live: fantes det allerede kontoer registrert med fødselsdato under
|
||
13 år? Ett treff — "Fredrik Mathisen" (`fredrik.f.mathisen@gmail.com`),
|
||
registrert 2026-08-09 med fødselsdato SATT TIL SAMME DAG (2026-08-09,
|
||
altså 0 år) — mest sannsynlig en dato-velger som ble stående på
|
||
dagens dato i stedet for en reell fødselsdato, ikke nødvendigvis en
|
||
bekreftet mindreårig, men nøyaktig signalet regelen er bygget for å
|
||
reagere på uansett årsak. Sjekket tilknyttet data først (runder,
|
||
medlemskap, meldinger, venner) — null treff overalt, en helt tom,
|
||
ubrukt konto.
|
||
|
||
Bruker godkjente eksplisitt både e-postteksten (norsk, forklarer
|
||
sletting og gir mulighet til å opprette ny konto med riktig
|
||
fødselsdato ved feilregistrering) og selve slettingen FØR noe ble
|
||
utført. Rekkefølge: (1) `DELETE FROM app_user WHERE id = ...` mot
|
||
ekte `teecup_db` (teeoff_admin) — bekreftet FK-kaskade ren siden
|
||
kontoen ikke hadde noen tilknyttet data i noen av de NO ACTION-
|
||
begrensede tabellene (message/round_message/round_participant/
|
||
round_shot m.fl.), (2) e-post sendt til den registrerte adressen via
|
||
appens eksisterende `_send_sync`-mekanisme i `app/email.py` (samme
|
||
SMTP-oppsett som magic-link-utsending), kjørt som et engangsskript
|
||
inni den ekte `teecup_api`-containeren, ikke en ny permanent
|
||
funksjon (ett engangstilfelle, ingen gjenbruk planlagt ennå).
|
||
Bekreftet i etterkant: kontoen finnes ikke lenger i `app_user`.
|
||
|
||
**Bevisst utenfor omfang:** ingen automatisert, gjentakende sjekk
|
||
for fremtidige under-13-registreringer er bygget — aldersgrensen i
|
||
`PATCH /auth/profile` (punkt 58) hindrer allerede NYE registreringer,
|
||
dette var en engangsopprydding av kontoer fra FØR den regelen gikk
|
||
live. Et automatisert varsel/rutine for dette kan vurderes som egen,
|
||
fremtidig sak om det blir aktuelt.
|
||
|
||
62. **Fjernet e-postvarsel for "venn startet en runde du kan følge" —
|
||
2026-08-10.** Bruker ba eksplisitt om å fjerne kun e-posten for denne
|
||
hendelsen (`type="round"`-notifikasjonen sendt fra `create_round` i
|
||
`rounds.py` til venner som kan følge runden) — in-app-varselet
|
||
(klokke-ikonet) og push-varselet skal fortsatt fungere som før.
|
||
|
||
**Kompleksitet:** samme `type="round"` deles av EN ANNEN, fortsatt
|
||
ønsket e-post ("du ble lagt til som medspiller", sendt fra
|
||
`add_participant`) — å slå av e-post for hele `"round"`-typen ville
|
||
fjernet begge. Løst med et nytt `allow_email: bool = True`-parameter
|
||
på `create_notification()` (`app/routers/notifications.py`), satt
|
||
til `False` KUN på kallstedet i `create_round`. In-app-innsettingen
|
||
og `_push_for_notification` kjører uendret uansett — kun selve
|
||
e-post-blokken hopper over. Ingen migrasjon, ingen endring i
|
||
`user_notification_email_pref`-skjemaet.
|
||
|
||
`frontend/components/account-settings.tsx`: "Runder"-varselvalgets
|
||
beskrivelsestekst rettet ("Du blir lagt til som medspiller, eller en
|
||
venn starter en runde du kan følge." → "Du blir lagt til som
|
||
medspiller på en runde.") — teksten lovet tidligere en e-post som nå
|
||
aldri sendes.
|
||
|
||
**Scratch-verifisert** (egen scratch-DB/rolle/MinIO/API-container,
|
||
`TEECUP_DEV_LOG_MAGIC_LINKS=true` + tomme SMTP-variabler for å
|
||
observere e-post-forsøk som en tydelig loggmelding i stedet for en
|
||
ekte utsending): to testbrukere, akseptert vennskap, `watcher` opt-et
|
||
inn på `round`-e-post. (1) `owner` opprettet en offentlig synlig
|
||
runde — in-app-varsel opprettet for `watcher`, INGEN "[DEV]
|
||
Varsel-e-post"-linje i loggen. (2) `owner` la `watcher` til som
|
||
medspiller på samme runde — in-app-varsel opprettet OG nøyaktig én
|
||
"[DEV] Varsel-e-post"-linje, bekreftet riktig melding. Begge
|
||
in-app-radene bekreftet i `notification`-tabellen etterpå. `tsc
|
||
--noEmit` og `python3 -m py_compile` begge rene. Scratch-ressurser
|
||
ryddet opp fullstendig, `teecup_db`s ACL bekreftet uendret.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Ja, kjør") —
|
||
begge containere bygget/restartet rent, bekreftet live på
|
||
`teecup.golf` uten konsollfeil utover den harmløse PWA-infomeldingen.
|
||
|
||
63. **Kartretning ved slagmåling: "opp = fremover på hullet", uten
|
||
green-koordinater — 2026-08-10, se ADR-054.** Bruker spurte om
|
||
kartet kunne roteres slik at man "går oppover" under slagmåling,
|
||
uten å registrere green-koordinater (bekreftet tidligere at ingen
|
||
slike finnes noe sted) — og ba deretter eksplisitt om at det bygges.
|
||
|
||
Løsning: bearingen (kompassretningen) kartet roteres til regnes ut
|
||
fra spillerens EGET forrige slag på samme hull (`round_shot` sine
|
||
allerede lagrede start-/sluttkoordinater), ikke fra en lagret
|
||
hull-linje. Ny `bearingDegrees()` i `frontend/lib/geo.ts` (ren
|
||
funksjon, standard forward-azimuth). Satt ÉN gang ved
|
||
kart-initialisering (`bearing` i Mapbox-konstruktøren), aldri
|
||
løpende oppdatert under gange — vurdert og bevisst avvist, se
|
||
ADR-054 Beslutning B (GPS-heading er for støyete ved lav fart til
|
||
kontinuerlig rotasjon). Første slag på et hull (ingen forrige å
|
||
regne fra) faller tilbake til ett engangs-forsøk på enhetens
|
||
`coords.heading`, deretter nord.
|
||
|
||
Rent klientside: `lib/geo.ts`, `components/shot/map-point-picker.tsx`,
|
||
`components/shot/shot-measurement-sheet.tsx`,
|
||
`components/round-detail.tsx` (`ShotMeasurementEntry`, som allerede
|
||
har slag-listen for hullet fra sin eksisterende henting). Ingen
|
||
migrasjon, ingen backend-endring.
|
||
|
||
**Verifisert i to lag:** `bearingDegrees()` unit-testet frittstående
|
||
(fire kjente himmelretninger — nord/øst/sør/vest ga 0/90/180/270
|
||
eksakt). Ende-til-ende i ekte nettleser (eget scratch-miljø): seedet
|
||
ett slag rett øst på hull 1 via ekte API-kall, midlertidig
|
||
konsollogg (fjernet igjen etter verifisering) bekreftet kartet fikk
|
||
`bearing≈90` på hull 1 og `bearing=0` (nord-fallback) på hull 2 (uten
|
||
tidligere slag). `tsc --noEmit` rent. Scratch-ressurser ryddet opp
|
||
fullstendig, `teecup_db`s ACL bekreftet uendret.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør den.") —
|
||
ren omstart av `teecup_frontend`, bekreftet live på `teecup.golf`
|
||
uten konsollfeil utover den harmløse PWA-infomeldingen.
|
||
|
||
64. **Nytt app-ikon (ball/pokal/tee), full-bleed, hakk-feil rettet —
|
||
2026-08-10, se ADR-055.** Bruker: dagens ikon var "fremdeles et
|
||
gammelt utkast" — bekreftet ved å åpne `icon-512.png`/
|
||
`icon-maskable-512.png`/`apple-icon.png` direkte: riktig motiv, men
|
||
grov beskjæring med enorm hvit padding rundt en liten grafikk,
|
||
nesten ulesbart ved 32×32.
|
||
|
||
V0-prompt (samme motiv, ny komposisjon: full-bleed bakgrunn,
|
||
sentrert i safe-zone, ingen egen avrunding) ga et første svar med to
|
||
synlige hakk i pokal-formen. Claude sammenlignet FØRST mot feil
|
||
kildefil og konkluderte feilaktig at V0 hadde "tegnet på nytt etter
|
||
øyemål" — bruker rettet dette ved å dele den faktiske kilde-SVG-en
|
||
(`TeeCup-logo-kun.svg`) direkte. Rendret stort viste at hakkene var
|
||
en ekte, pre-eksisterende unøyaktighet i selve kildekunsten (to
|
||
overlappende oransje former som ikke dekker hverandre helt i to
|
||
punkter) — usynlig på hvit bakgrunn, synlig mot mørk. V0s "tatt
|
||
verbatim"-påstand var korrekt; Claudes første mistanke var feil.
|
||
|
||
Presis rettemelding til V0 (identifiserte de to path-fyllfargene)
|
||
ga en fiks (tettet gapet med en `stroke` i samme farge som fyllet,
|
||
ikke en omtegning) — verifisert visuelt før integrering, hakkene
|
||
bekreftet borte, ingen nye artefakter.
|
||
|
||
Integrert: ett 1024×1024 master-SVG er eneste kilde. Alle
|
||
pikselstørrelser (`icons/icon-192.png`, `icons/icon-512.png`,
|
||
`icons/icon-maskable-512.png`, `apple-icon.png` 180×180,
|
||
`icon-light/dark-32x32.png`) rastret av Claude selv via ekte
|
||
nettleser-rendring (`devicePixelRatio=1`, bekreftet eksakte
|
||
pikseldimensjoner etterpå) — ikke V0-genererte PNG-er, for å unngå
|
||
eksport-unøyaktighet. `public/icon.svg` (SVG-favikon) erstattet med
|
||
master-SVG-et. `app/manifest.ts` sin `theme_color`/`background_color`
|
||
(fortsatt gamle Forest Green-farger, `#8BC24A`/`#ffffff`) oppdatert
|
||
til clubhouse (`#2f6b1e`/`#f3f6ec`).
|
||
|
||
**Scratch-verifisert**: `tsc --noEmit` rent, egen frontend-only
|
||
scratch-container (ekte produksjonsbuild), `GET
|
||
/manifest.webmanifest` bekreftet nye farger, ikonfilene bekreftet
|
||
200 og visuelt korrekte i ekte nettleser. Scratch-ressurser ryddet
|
||
opp fullstendig.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør") — ren
|
||
omstart av `teecup_frontend`, bekreftet live på `teecup.golf`:
|
||
`icon.svg` og `manifest.webmanifest` (nye farger) begge korrekte,
|
||
ingen konsollfeil utover den harmløse PWA-infomeldingen.
|
||
|
||
65. **WHS Rule 3.1b-unntak (>54 banehandicap + 4 mottatte slag → par+5)
|
||
implementert og fasit-testet + `round.tee_name_snapshot` synk-bug
|
||
rettet — 2026-08-10, se ADR-056.** Bruker delte en ekstern vurdering
|
||
av appens to "harde kjerner" (WHS-beregning, live-scoring/
|
||
samtidighet) og ba om en ærlig sjekk. To Explore-agenter gransket
|
||
hver sin kjerne parallelt.
|
||
|
||
**WHS-funn:** `handicap_engine.py` manglet et reelt, publisert
|
||
unntak (verifisert direkte mot kilde-PDF-en, side 37, ikke bare
|
||
agentens gjenfortelling): banehandicap over 54 OG 4+ mottatte slag
|
||
på ett hull → maks hullscore par+5, ikke den vanlige
|
||
par+2+mottatte-slag. Lagt til som et nytt, bakoverkompatibelt
|
||
`course_handicap`-parameter på `max_hole_score_for_handicap`/
|
||
`adjusted_gross_score`, wired inn i de to produksjonskallstedene i
|
||
`rounds.py`. Fire nye tester i `test_handicap_engine.py` (eksplisitte
|
||
grensetilfeller: eksakt 54, eksakt 3 slag, samt end-til-ende) — 117/117
|
||
grønne. Scratch-verifisert også via ekte API-kall (deltaker med
|
||
banehandicap 63, "plukket opp" på et hull med 4 slag ga korrekt 9,
|
||
ikke det gamle 10).
|
||
|
||
**Egen, urelatert bug funnet samtidig** (bruker viste skjermdump av
|
||
en live runde der toppheaderens utslag ikke fulgte et utslagsbytte):
|
||
`PATCH .../participants/{id}` skrev kun til `round_participant`,
|
||
aldri til `round.tee_name_snapshot` (som selve header-visningen
|
||
leser). Fikset: skriver nå begge når det er EIERENS EGEN
|
||
deltaker-rad som endres (ikke en gjests — ulike deltakere kan
|
||
bevisst ha ulikt utslag). Scratch-verifisert: eierens bytte
|
||
oppdaterer runden, en gjests bytte gjør det ikke.
|
||
|
||
`tsc`/`py_compile` rene der aktuelt. Scratch-ressurser ryddet opp
|
||
fullstendig, `teecup_db`s ACL bekreftet uendret gjennom hele
|
||
verifiseringen.
|
||
|
||
**Ikke rettet ennå:** den konkrete live runden brukeren viste (feil
|
||
historisk data i ekte `teecup_db`) — venter på egen bekreftelse for
|
||
en engangs datakorreksjon. Samtidighets-/konflikt-halvparten av
|
||
vurderingen er bevisst IKKE besluttet i denne runden — krever en
|
||
designbeslutning fra bruker først, se ADR-056.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør alt") —
|
||
`teecup_api` bygget/restartet rent. Den konkrete runden brukeren
|
||
rapporterte (`round.tee_name_snapshot` feilaktig "50") korrigert til
|
||
"55" med en engangs `UPDATE` mot ekte `teecup_db`, bekreftet i
|
||
etterkant.
|
||
|
||
66. **Optimistisk versjonssjekk for samtidig hull-redigering — 2026-08-10,
|
||
se ADR-057.** Direkte oppfølging av punkt 65: bruker fikk valget
|
||
mellom tre tilnærminger til samtidighets-halvparten av den eksterne
|
||
vurderingen (versjonssjekk / feltvis skrivning / la det ligge), valgte
|
||
"enkel versjonssjekk + tydelig feilmelding".
|
||
|
||
Migrasjon 062: ny `version`-kolonne på `round_hole` (dekker begge
|
||
eierskapstyper, deltaker og side, én tabell). `update_hole`/
|
||
`update_side_hole` sjekker nå `expected_version` ATOMISK i selve
|
||
UPDATE-en (`WHERE ... AND (expected_version IS NULL OR version =
|
||
expected_version)`, ingen separat les-så-skriv-race), 409 med tydelig
|
||
norsk feiltekst ved konflikt, skiller korrekt fra 404 (hull finnes
|
||
ikke). `expected_version` valgfri — bakoverkompatibelt.
|
||
|
||
Offline-køen (`offline-queue.ts`) kjeder nå versjonen fremover
|
||
innad i én flush-runde — uten dette ville en spillers EGNE
|
||
påfølgende offline-redigeringer av samme hull avvist hverandre som
|
||
falske konflikter (funnet under design, før noe ble bygget).
|
||
|
||
**Reelt UX-hull funnet under scratch-verifisering, rettet før
|
||
utrulling:** første versjon gjenbrukte komponentens eksisterende
|
||
`error`-tilstand for konfliktmeldingen — viste seg (kun synlig ved
|
||
ekte to-enhets-test i nettleser) å være en FATAL, hele-siden-
|
||
erstattende tilstand. En forbigående hull-konflikt tok dermed over
|
||
hele skjermen. Rettet med en egen, dismissbar `conflictNotice`-
|
||
banner (gjenbrukt også for den eksisterende kø-synk-feilmeldingen,
|
||
som hadde samme problem fra før).
|
||
|
||
**Scratch-verifisert i tre lag, alt i ekte nettleser/API:** (1)
|
||
begge backend-endepunktene hver for seg — riktig 409/404-skille,
|
||
vinnerens data bekreftet bevart, bakoverkompatibilitet uten
|
||
`expected_version` bekreftet. (2) To ISOLERTE nettleser-kontekster
|
||
(ekte to-enheter-simulering) — konflikt ga 409, veiviseren viste
|
||
automatisk riktig (den andres) verdi, banner dismissbart, RESTEN AV
|
||
SIDEN forble fullt brukbar (dette avdekket UX-hullet over). (3)
|
||
Ekte offline-emulering — to redigeringer av samme hull frakoblet,
|
||
begge synkroniserte korrekt ved reconnect, ingen falsk konflikt.
|
||
`tsc`/`py_compile` rene. Scratch-ressurser ryddet opp fullstendig,
|
||
`teecup_db`s ACL bekreftet uendret.
|
||
|
||
**Bevisst utenfor omfang:** org-turneringenes match-scoring (helt
|
||
separat system/tabell) ikke undersøkt eller endret denne runden.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt ("Kjør alt") —
|
||
migrasjon 062 kjørt mot ekte `teecup_db` FØR `teecup_api`/
|
||
`teecup_frontend` ble bygget/restartet (riktig rekkefølge, ny kode
|
||
leser `version`-kolonnen). Bekreftet live: begge containere startet
|
||
rent, ingen konsollfeil på `teecup.golf`.
|
||
|
||
67. **Automatisert backend-testinfrastruktur — RLS, versjonssjekk,
|
||
aldersgrense — 2026-08-10, se ADR-058.** Direkte oppfølging av
|
||
investor-statusrapporten samme dag: "nesten ingen automatisert
|
||
testdekning utenfor HCP-motoren" var det klart største enkeltfunnet.
|
||
Bruker ba om å ta tak i akkurat dette.
|
||
|
||
`scripts/run_backend_tests.sh` automatiserer det som til nå har
|
||
vært en manuell prosedyre hver runde: oppretter en scratch-database
|
||
+ scratch-rolle i den samme `teeoff_db`-containeren, kjører alle 62
|
||
migrasjonene i rekkefølge (med `sed`-patch av migrasjon 002 sitt
|
||
hardkodede rollenavn OG databasenavn til scratch-spesifikke verdier
|
||
— sistnevnte et nytt funn: uten patch ville testrollen fått en
|
||
reell, om enn ufarlig, `GRANT CONNECT`-rettighet på den EKTE
|
||
`teecup_db`), bygger en dedikert `Dockerfile.test`-container
|
||
(pytest, IKKE i prod-imaget) tilkoblet samme `teeoff_default`-
|
||
nettverk som `teecup_api` selv bruker (nødvendig — Postgres-
|
||
containeren har ingen host-publisert port i dette miljøet), kjører
|
||
testene, dropper scratch-database + -rolle igjen. Rører aldri ekte
|
||
`teecup_db` — bekreftet uendret ACL og radantall etter hver kjøring.
|
||
|
||
17 nye tester, tre områder valgt etter risiko: RLS/tenant-isolasjon
|
||
(4 — inkl. regresjonsvern for migrasjon 005s NULL-guard), den
|
||
optimistiske versjonssjekken fra punkt 66 (6 — begge
|
||
`update_hole`/`update_side_hole`), 13-årsgrensen (5 — inkl. eksakt
|
||
dagsgrense begge veier). Testene kaller de FAKTISKE router-/auth-
|
||
funksjonene direkte (ikke en SQL-gjenimplementering) — samme
|
||
kodesti som produksjon.
|
||
|
||
**Verifisert:** alle 17 nye + de eksisterende 117 HCP-testene
|
||
grønne. Ingen produksjonskode endret denne runden — ren
|
||
testinfrastruktur, ingen deploy.
|
||
|
||
**Bevisst utenfor omfang:** frontend-tester, og dekning av
|
||
org-turneringenes match-scoring (`scoring.py`) — som ADR-057
|
||
allerede har dokumentert mangler samtidighetsvern, men som
|
||
fortsatt ikke har en test som beviser det empirisk.
|
||
CI-workflow-filen ble løst SAMME dag, se punkt 68.
|
||
|
||
68. **Selvhostet Forgejo Actions-runner — 2026-08-10, se ADR-059.**
|
||
Direkte oppfølging av punkt 67: en `.forgejo/workflows/
|
||
backend-tests.yml` ble lagt til og pushet som en empirisk test —
|
||
Forgejo-instansen svarte `has_actions: true`, men det beviste ikke
|
||
at noe faktisk plukket opp jobber. Jobben la seg som "Waiting" og
|
||
sto uendret i over et minutt. Ingen runner fantes. Bruker ba om
|
||
at det rettes ("start med ci").
|
||
|
||
Satt opp en selvhostet `act_runner` PÅ DENNE serveren (må være her
|
||
— `run_backend_tests.sh` er avhengig av `docker exec teeoff_db` og
|
||
`teeoff_default`-nettverket, som kun finnes lokalt). Docker-
|
||
utenfor-Docker (`docker_host: automount`): jobb-containeren får
|
||
host-ens `docker.sock` bind-montert inn, slik at testskriptets
|
||
egne `docker build`/`run`/`exec`-kall lager ekte søsken-containere
|
||
på host-nivå. Jobb-image `catthehacker/ubuntu:act-latest` (docker-
|
||
CLI/git/bash forhåndsinstallert).
|
||
|
||
Registrerings-tokenet ble ALDRI limt inn i chatten — bruker hentet
|
||
det selv fra Forgejo sitt UI, la det i en `chmod 600`-fil på
|
||
serveren, Claude leste filen direkte og makulerte den (`shred -u`)
|
||
rett etter registrering. Den ekte, langlevde runner-legitimasjonen
|
||
som oppsto (`.forgejo-runner/.runner`) er gitignored og `chmod 600`
|
||
— samme disiplin som `.env`.
|
||
|
||
Startet først manuelt for å bevise at det virket, deretter flyttet
|
||
inn som en egen `teecup-forgejo-runner`-tjeneste i
|
||
`docker-compose.yml` (`restart: unless-stopped`) for samme
|
||
driftsdisiplin som resten av stacken.
|
||
|
||
**Sikkerhetsnotat, bevisst akseptert:** `docker.sock`-tilgang er
|
||
root-ekvivalent kontroll over verten — en reell heving av
|
||
angrepsflaten. Akseptert fordi det er samme prinsipp
|
||
`run_backend_tests.sh` allerede krevde (nå automatisert fremfor
|
||
kjørt fra en allerede-priviligert shell), og runneren betjener kun
|
||
denne ene repoens CI på en enkeltpersons egen server.
|
||
|
||
**Verifisert:** ekte push → workflowen kjørte → "Success" i
|
||
Forgejo sitt UI (grønn hake, samme kø-oppføring som nettopp sto
|
||
"Waiting"). `teecup_db`s rolleoppsett og fravær av gjenglemte
|
||
scratch-databaser bekreftet uendret etter kjøring.
|
||
|
||
69. **Optimistisk versjonssjekk for org-turneringers match-scoring —
|
||
2026-08-10, se ADR-060.** Fortsettelse av robusthetslinjen: den
|
||
siste dokumenterte, kjente svakheten (ADR-057/058: org-
|
||
turneringenes match-scoring hadde ingen samtidighetsvern, ren
|
||
siste-skriving-vinner) tettet på brukerens eksplisitte forespørsel.
|
||
|
||
Migrasjon 063: `version`-kolonne på BÅDE `hole_score` og
|
||
`match_hole_result` (to separate tabeller/scoring-modi). Ulikt
|
||
round_hole er disse UPSERT (`INSERT ... ON CONFLICT DO UPDATE`),
|
||
ikke ren UPDATE — løst med Postgres sin `DO UPDATE ... WHERE`, som
|
||
kun evalueres på selve konflikt-grenen. Konsekvens: enklere enn
|
||
round_hole — en avvist skrivning er ALLTID en versjonskonflikt,
|
||
aldri en "finnes ikke"-tvetydighet.
|
||
|
||
Reelt funn under design: offline-kø-kjedingen fra punkt 66 nøklet
|
||
på URL alene, som IKKE er entydig for `/hole-scores`/`/hole-
|
||
results` (samme URL for alle hull i en match). Generalisert med et
|
||
nytt, valgfritt `resourceKey`-felt på kø-oppføringen (default url,
|
||
100 % bakoverkompatibelt med round_hole sin bruk).
|
||
|
||
`submit_hole_score`/`submit_hole_result` i `app/routers/scoring.py`,
|
||
`session-scorecard.tsx`, `offline-queue.ts`. 6 nye pytest-tester
|
||
(`tests/test_scoring_concurrency.py`) — delt-ball og individuell-
|
||
ball-grenen hver for seg, begge endepunktene, vellykket skrivning +
|
||
409-konflikt + bekreftet at avvist skrivning ikke lagres. Alle 23
|
||
backend-tester grønne (kjørt via CI-runneren fra punkt 68). `tsc
|
||
--noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-10**, bruker bekreftet eksplisitt — migrasjon
|
||
063 kjørt mot ekte `teecup_db`, begge containere bygget/restartet
|
||
rent, ingen konsollfeil. CI plukket opp pushen og kjørte grønt.
|
||
|
||
70. **Frontend-testinfrastruktur (Vitest) — 2026-08-10, se ADR-061.**
|
||
Fortsettelse av robusthetslinjen: frontend-tester var eksplisitt
|
||
utenfor omfang i både punkt 67 og 69. Bruker ba om å ta tak i det.
|
||
|
||
Vitest satt opp (node-miljø, ingen komponenttester ennå). 30 tester
|
||
i tre filer: `lib/geo.test.ts` (GPS-avstand/retning, fasit-
|
||
forankret mot kjente geodetiske verdier), `lib/offline-queue.
|
||
test.ts` (regresjonsvern for `resourceKey`-fiksen fra punkt 69 --
|
||
beviser eksplisitt at to ulike hull som deler samme URL ikke lenger
|
||
blander sammen versjonsnummer, med `fake-indexeddb` som ekte
|
||
IndexedDB-implementasjon), `lib/ny-runde/formats.test.ts`
|
||
(fullstendighetssjekk på tvers av format-listene -- fanger opp det
|
||
TypeScript sin `as`-cast i `FORMAT_MAP` IKKE garanterer).
|
||
|
||
To reelle funn underveis: (1) prosjektet bruker pnpm, ikke npm --
|
||
et første forsøk med `npm install` feilet (intern npm/arborist-
|
||
krasj mot en pnpm-strukturert `node_modules`), ingen skade, løst
|
||
ved å bruke riktig verktøy. (2) `frontend/pnpm-workspace.yaml` var
|
||
ignorert i `.gitignore` (arv fra V0-sandbox-malen) -- fila fikk nå
|
||
reelt CI-relevant innhold (`allowBuilds: sharp: true`), og ville
|
||
latt CI feile stille på `[ERR_PNPM_IGNORED_BUILDS]` hver gang uten
|
||
å bli sporet i git. Rettet.
|
||
|
||
Egen, lettvekts CI-workflow (`.forgejo/workflows/frontend-tests.
|
||
yml`) -- INGEN Docker-utenfor-Docker, siden denne suiten ikke
|
||
rører database/containere. `actions/setup-node@v4` (Node 22) +
|
||
`corepack enable` + `pnpm install --frozen-lockfile` + `pnpm test`.
|
||
|
||
**Verifisert:** 30/30 grønt lokalt. Ingen produksjonskode endret.
|
||
|
||
71. **Åtte brukerrapporterte UX-funn — "Ny runde" og score-registrering
|
||
— 2026-08-11, se ADR-062.** Bruker sendte åtte konkrete, skjermbilde-
|
||
dokumenterte problemer. Delt i to spor: fire rettet direkte (reelle
|
||
logikkfeil/tekstvalg), fire sendt via V0 (reelt interaksjonsdesign).
|
||
|
||
**Direkte:** `round-card.tsx` sin "Hull"-celle viste alltid planlagt
|
||
antall hull, aldri faktisk spilt, selv for tidlig avsluttede
|
||
"Fullført"-runder — rettet (`played < round.holes` avgjør nå om
|
||
avviket vises, uansett status). Putt-avstand-knappenes etiketter
|
||
endret fra et blandet "<Xm ... 8m+"-mønster til konsekvent
|
||
"X-Ym" (kun visningstekst, ikke backend-kontrakten) — bekreftet via
|
||
git-historikk at dette var Claude-forfattet, ikke V0. "Se feed" på
|
||
dashbordet endret til "Se venneaktivitet". @-tagging av medspillere
|
||
i feeden vurdert og UTSATT (egen fremtidig funksjon) — anbefalt
|
||
personvern-begrensning (kun faktiske relasjoner) og ekte søkbar
|
||
nedtrekksliste (ikke fritekst-tolkning) notert til den runden.
|
||
|
||
**Via V0** (`tee-cup (9).zip`, kun `delivery/`-mappen tatt inn, diffet
|
||
mot live-treet først): ny-runde steg 2 sin "Antall hull"/"Avanserte
|
||
handicap-innstillinger" flyttet FØR formatrutenettet (var sist, lett
|
||
oversett). Score-registreringens "detaljer"-steg delt i "Retning"/
|
||
"Detaljer per hull"-seksjoner + en ny `ScrollFade`-hjelpekomponent
|
||
(bunn-fade + nedoverpil, ResizeObserver, auto-skjuler ved bunn) --
|
||
løser at brukere ikke visste de måtte skrolle. Anywayslag endret fra
|
||
tall-rutenett til +/−-stepper (samme mønster som Chip/Bunker/
|
||
Straffeslag nå). Netto par-markør (oransje prikk + kontrastring +
|
||
tekstlig forklaring, tilgjengelig uten fargesyn) på Slag-knappene,
|
||
kan opptre samtidig med "valgt"-tilstand. Putter-velgeren fikk egen
|
||
oransje aksent + flagg-ikon for å skille den fra Slag-velgeren.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent. Full scratch-stack (DB, MinIO,
|
||
API, frontend — ekte produksjonsbuild). Ekte testbruker, egen bane
|
||
opprettet via API med HCP 24 for å garantere synlig netto-par-
|
||
markør. Browserverifisert (mobil viewport, lys+mørk modus): ny
|
||
rekkefølge, seksjonsdeling, scroll-hint (bekreftet både synlig og
|
||
auto-skjult), netto-par-prikk alene og kombinert med valgt-tilstand,
|
||
Putter-aksent, nye putt-avstand-etiketter. Ingen konsollfeil.
|
||
Scratch-miljøet ryddet opp fullstendig, `teecup_db` bekreftet uendret.
|
||
|
||
**Rullet ut 2026-08-11**, bruker bekreftet eksplisitt ("Kjør på") —
|
||
`docker compose build teecup_frontend && up -d`, ren omstart,
|
||
bekreftet live på `teecup.golf`, ingen konsollfeil utover den
|
||
harmløse PWA-infomeldingen.
|
||
|
||
**Oppfølging samme dag — punkt 5 (ChoiceRow) ikke løst.** Bruker
|
||
rapporterte at "Avstand første putt" fortsatt viste 5+1 i stedet for
|
||
3+3. Root cause: `ChoiceRow` (delt komponent) brukte `flex flex-wrap`
|
||
fremfor et fast rutenett, pakket knapper etter tekstbredde i stedet
|
||
for et forutsigbart antall. Rettet til `grid grid-cols-3` (samme
|
||
mønster `NumberPicker` allerede brukte). Visuelt uendret for de fire
|
||
andre `ChoiceRow`-bruken (alle har 3 valg fra før). Browserverifisert
|
||
lys+mørk. Committet (`17e4793`), ikke deployet ennå.
|
||
|
||
72. **@-tagging av medspillere/venner i runde-feeden — 2026-08-11, se
|
||
ADR-063.** Bakt inn i FEATURE_BACKLOG.md som egen sak under
|
||
ADR-062, bruker ba om å sette den i gang.
|
||
|
||
Migrasjon 064: `round_message_tag`/`round_message_comment_tag` (to
|
||
parallelle tabeller, samme mønster som `round_message_reaction` vs.
|
||
`message_reaction`). Kun FAKTISKE relasjoner er taggbare (rundens
|
||
lenkede deltakere UNION avsenderens venner) — aldri fritekstsøk i
|
||
hele brukerbasen, håndhevet både i nytt søkeendepunkt
|
||
(`GET /rounds/{id}/taggable-people`) og server-side ved innsending.
|
||
Tag-posisjon lagres som tegn-offset inn i den (aldri redigerbare)
|
||
body-teksten — trygt, teksten kan aldri gå ut av synk. Ugyldige tags
|
||
forkastes stille, hele innlegget avvises aldri av den grunn. Varsel
|
||
(`type="round"`, gjenbruker eksisterende kategori) til hver gyldig
|
||
tagget person.
|
||
|
||
**Verifisert:** 8 nye pytest-tester
|
||
(`tests/test_message_tags.py`) — kandidatliste, søkefilter, gyldig
|
||
tag + varsel bekreftet, tre typer ugyldig-tag-forkastelse. Alle 31
|
||
backend-tester grønne. `teecup_db` uendret.
|
||
|
||
**Frontend — samme dag, egen-implementert.** Frontend var
|
||
opprinnelig sendt som eget V0-prompt, men bruker gikk tom for
|
||
V0-credits ("Kan du gi promptet til deg selv, og gjøre det beste
|
||
ut av det?") — eksplisitt, avgrenset unntak fra prosjektets
|
||
stående "frontend via V0"-konvensjon for akkurat denne funksjonen.
|
||
`frontend/lib/mentions.ts` (ren offset-/diff-logikk: @-deteksjon,
|
||
innsetting, diff-justering av eksisterende tags ved redigering,
|
||
tekst-segmentering — 15 enhetstester) +
|
||
`frontend/components/mention-input.tsx` (`MentionTextarea` med
|
||
debouncet autocomplete-dropdown + tastaturnavigasjon, `TaggedText`
|
||
som rendrer tagger som lenker til `/my-friends/{id}`). Koblet inn i
|
||
`round-messages.tsx` (innlegg), `post-engagement.tsx` (kommentarer
|
||
— delt komponent mellom FIRE meldingssystemer, `roundId`/`tags`
|
||
gjort valgfrie for å ikke bryte `feed.tsx`/`public-tournament.tsx`/
|
||
`team-chat.tsx`, hvorav de to siste bevisst IKKE får tagging) og
|
||
`feed.tsx` (aggregert `/my-feed`-visning). Fant og rettet et hull
|
||
underveis: backendens `GET /feed` manglet `tags` i `FeedEntryOut` —
|
||
kun `list_round_messages`/kommentar-endepunktene hadde det fra
|
||
før, selv om `/my-feed` var nettopp det opprinnelige eksempelet
|
||
("Fredrik i farta") som motiverte hele funksjonen.
|
||
|
||
**Verifisert (frontend):** `tsc --noEmit` rent, 45/45 vitest
|
||
grønt. Full scratch-stack (DB, MinIO, API, frontend — ekte
|
||
produksjonsbuild). To ekte testbrukere (venn-relasjon + felles
|
||
runde) browserverifisert: autocomplete-dropdown i både innleggs-
|
||
og kommentar-komposereren, tag rendret som lenke i selve
|
||
rundevisningen OG i aggregert feed, varsel bekreftet opprettet
|
||
(`GET /notifications`), personvern-scoping bekreftet i selve UI-et
|
||
(skriver "@" alene → kun faktisk relasjon tilbys, aldri seg selv
|
||
eller en fremmed), lys+mørk modus. Ingen konsollfeil. Scratch-
|
||
miljøet ryddet fullstendig opp, `teecup_db` bekreftet uendret før
|
||
og etter.
|
||
|
||
Org-turneringenes "Banter Board" (`message`/`message_comment`)
|
||
fikk IKKE samme funksjon — separat system, fortsatt bevisst
|
||
utenfor omfang.
|
||
|
||
**Rullet ut 2026-08-11**, bruker bekreftet eksplisitt ("ja").
|
||
Migrasjon 064 kjørt mot ekte `teecup_db` (kun nye tabeller, ingen
|
||
endring av eksisterende), deretter `docker compose build teecup_api
|
||
teecup_frontend && up -d` — samme kommando ryddet også inn den
|
||
tidligere upubliserte ChoiceRow-fiksen (`17e4793`). Ren omstart,
|
||
ingen feil i containerloggene, ingen konsollfeil på
|
||
`https://teecup.golf/logg-inn` etter omstart.
|
||
|
||
73. **Fiks: ScrollFade-scrollhintet (fra ADR-062 punkt 6) hadde to
|
||
reelle feil — 2026-08-11, se oppfølgingsnotatet i ADR-062.**
|
||
Bruker sendte skjermbilde: "Den sprettende ned-pilen fungerer
|
||
ikke. Den ligger OPPÅ en annen nedpil."
|
||
|
||
To separate rotårsaker i samme `ScrollFade`-komponent
|
||
(`round-detail.tsx`): (1) `ResizeObserver` observerte scroll-
|
||
beholderen i stedet for innholdet — beholderens boksstørrelse er
|
||
fast, så observeren fyrte aldri ved stegbytte i veiviseren, og
|
||
hintet virket dermed rett og slett ikke på steg med reelt
|
||
overflow-innhold (som Retning-steget). Rettet ved å observere en
|
||
egen `contentRef`-div rundt innholdet i stedet for beholderen selv.
|
||
(2) Fade-/piloverlayet (absolutt posisjonert, siste 64px av
|
||
scroll-området) hadde ingen garanti mot å dekke ekte innhold —
|
||
på Retning-steget landet "Kort"-knappens eget `ArrowDown`-ikon i
|
||
akkurat den sonen, to piler oppå hverandre. Rettet med en usynlig
|
||
64px-buffer etter innholdet (`hasMore`-utregningen trekker fra
|
||
samme høyde for å unngå falske positiver på kort innhold).
|
||
|
||
**Verifisert:** Full scratch-stack, testbruker med fullt
|
||
kølle-utvalg for å reprodusere nøyaktig samme rutenett som i
|
||
brukerens skjermbilde. Bekreftet: hintet vises (`opacity: 1`) når
|
||
steget faktisk overflower, forsvinner (`opacity: 0`) ved reell
|
||
bunn, "Kort" fullt synlig og klar av overlay-sonen ved skrolling,
|
||
lys+mørk, ingen konsollfeil. `tsc --noEmit` rent, 45/45 vitest
|
||
grønt. `teecup_db` urørt (ren frontend-endring). Committet
|
||
(`ee91d03`).
|
||
|
||
**Rullet ut 2026-08-11**, bruker bekreftet ("ja"). Kun
|
||
`teecup_frontend` bygget og restartet (ren frontend-endring, ingen
|
||
migrasjon). Ingen feil i containerloggene, ingen konsollfeil på
|
||
`https://teecup.golf/logg-inn` etter omstart.
|
||
|
||
74. **Statistikk: ekspandert trekkspill, spilt-til-HCP, omstrukturert
|
||
/my-rounds/stats — 2026-08-12.** Tre separate tilbakemeldinger fra
|
||
bruker etter å ha sett gjennom statistikksidene med skjermbilder.
|
||
|
||
**round-stats.tsx:** trekkspillseksjonene starter nå ekspandert.
|
||
Reverserer en eksplisitt tidligere beslutning (2026-07-25, "trekk-
|
||
spillet skal vises sammenslått") — kommentaren i koden oppdatert til
|
||
å forklare hvorfor, ikke bare strøket.
|
||
|
||
**Dashbordets "HCP nå"** viste tidligere det manuelt satte tallet i
|
||
profilen (`handicap_index`) — brukeren ville ha "spilt til"-HCP, dvs.
|
||
`computed_handicap_index` (allerede beregnet og eksponert på
|
||
`/auth/me`, brukt av Konto-sidens "Faktisk HCP (beregnet)"-kort, bare
|
||
ikke koblet inn på dashbordet). Historikk-sparklinen filtreres nå
|
||
også til kun `source="computed"` — den blandet tidligere manuelle og
|
||
beregnede punkter i samme linje.
|
||
|
||
**`/my-rounds/stats` fikk tre endringer:**
|
||
- Alle "vs. forrige periode"-deltaer (pil+tall+pp) er nå synlig
|
||
selvforklarende. Teksten fantes tidligere kun som skjermleser-tekst
|
||
— seende brukere så bare et ikon og et tall, ingen kontekst for hva
|
||
det ble sammenlignet mot. Bruker: "Selv ikke jeg... forstår helt
|
||
hva som menes."
|
||
- Greentreff (GIR) og Innspill splittet i to separate kort (GIR er en
|
||
beregnet andel, Innspill er en retningsbeskrivelse — samme prinsipp
|
||
som round-stats.tsx sin eksisterende splitt mellom "Greentreff
|
||
(GIR)" og "Bom-retning på green"). Utslag flyttet til å vises
|
||
først. Redning og "Til par: med vs. uten" flyttet ned under Annet.
|
||
- Ny stolpegraf med glidende snitt-trendlinje (snitt til par per
|
||
runde, eldste runde til venstre) — tidligere bevisst utsatt ("for
|
||
lite rundehistorikk til å vise noe meningsfullt", se punkt 2026-
|
||
07-28 tidligere i denne loggen), bygget nå som brukeren har nok
|
||
fullførte runder. Backend: `RoundStatsSummary` fikk et nytt
|
||
`round_series`-felt (kronologisk snitt-til-par per fullført runde
|
||
i valgt tidsvindu, sortert eldste først).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, 45/45 vitest, 31/31 backend-
|
||
tester (inkl. `round_series`-feltet, kjørt mot en scratch-database).
|
||
Full scratch-stack med seks syntetiske runder (varierende dato og
|
||
resultat, for å faktisk se en trend) browserverifisert lys+mørk:
|
||
HCP-bytte bekreftet (computed 5,3 vist i stedet for manuelt satt
|
||
15,0), ny kortrekkefølge, splittet GIR/Innspill, synlige delta-
|
||
bildetekster på et vindu med reell forrige-periode-sammenligning,
|
||
graf med trendlinje, trekkspill ekspandert på rundesiden. Ingen
|
||
konsollfeil. `teecup_db` urørt gjennom hele verifiseringen.
|
||
|
||
**Rullet ut 2026-08-12**, bruker bekreftet ("Jeg bekrefter"). Ingen
|
||
migrasjon (kun et nytt beregnet felt i et eksisterende API-svar) --
|
||
`docker compose build teecup_api teecup_frontend && up -d`. Ren
|
||
omstart, ingen feil i containerloggene, ingen konsollfeil på
|
||
`https://teecup.golf/logg-inn` etter omstart.
|
||
|
||
75. **Statistikk: lineær regresjon i stedet for glidende snitt, trend på
|
||
alle variabler — 2026-08-12.** Oppfølging til punkt 74, samme dag.
|
||
Bruker: "Trendlinjen din viser jo egentlig bare det samme som
|
||
stolpediagrammet" — korrekt, et 3-runders glidende snitt over så få
|
||
punkter følger bare stolpene med én runde forsinkelse, smoother
|
||
ingenting reelt.
|
||
|
||
Presenterte tre alternativer via AskUserQuestion (lineær regresjon /
|
||
eksponentielt glidende snitt / kumulativt løpende snitt) FØR noe ble
|
||
bygget, jf. brukerens eksplisitte "Gi meg forslag før vi
|
||
implementerer". Bruker valgte lineær regresjon (minste kvadraters
|
||
metode) — en rett linje, stigningstall = "endring per runde", vist i
|
||
bildeteksten (f.eks. "+7 pp per runde"). Visuelt en helt annen form
|
||
enn stolpene, løser selve klagen.
|
||
|
||
Bruker ba samtidig om trend på ALLE variabler. Lagt til stolpe+trend
|
||
for Fairwaytreff, Greentreff (GIR), Putt/18, Én-putt, Chip/runde,
|
||
Bunkerslag/runde, Straffeslag/runde og Anywayslag/runde, hver i sitt
|
||
eksisterende kort. Scrambling/sand save unntatt (brukervalg via
|
||
samme AskUserQuestion): for få kvalifiserte hull per runde til et
|
||
per-runde-tall, vises i stedet som én kumulativ linje.
|
||
|
||
Backend: `round_series` flyttet fra `RoundStatsSummary` til
|
||
`RoundStatsWindowSummary`-nivå, utvidet med per-runde-tall for alle
|
||
variabler pluss kumulativ scrambling/sand save. Fanget og rettet en
|
||
enhets-bug under egen scratch-verifisering (før commit): scrambling/
|
||
sand save ble først bygget på 0–1-skala i stedet for samme 0–100-
|
||
skala som resten av API-et.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, 45/45 vitest, 31/31 backend-
|
||
tester. Full scratch-stack med åtte syntetiske runder (bevisst
|
||
støyende fremgang) browserverifisert lys+mørk: regresjonslinjen
|
||
bekreftet rett og visuelt distinkt fra stolpene på alle variabler,
|
||
riktig fortegn/farge på stigningstall, ingen dobbel enhet i
|
||
bildetekstene, kumulative linjer konvergerer som forventet. Ingen
|
||
konsollfeil. `teecup_db` urørt.
|
||
|
||
**Rullet ut 2026-08-12**, bruker bekreftet ("Jeg bekrefter"). Ingen
|
||
migrasjon -- `docker compose build teecup_api teecup_frontend &&
|
||
up -d`. Ren omstart, ingen feil i containerloggene, ingen konsollfeil
|
||
på `https://teecup.golf/logg-inn` etter omstart.
|
||
|
||
76. **Statistikk: y-akse-benevnelser + verdi i begge ender på
|
||
trendgrafene — 2026-08-12.** Oppfølging til punkt 75, samme dag.
|
||
Bruker: "Y-axen må ha verdi i begge ender" + ønske om benevnelse på
|
||
y-aksen.
|
||
|
||
`BarTrendChart`/`CumulativeTrendLine` (`rounds-stats-summary.tsx`)
|
||
fikk en ny `yAxisLabel`-prop -- rendres som EKTE HTML-tekst over hver
|
||
graf (ikke roterte SVG-glyffer), slik at den skalerer med brukerens
|
||
skriftstørrelse i stedet for å bli uleselig ved zoom
|
||
(tilgjengelighetsregelen i CLAUDE.md). Benevnelser: "Slag til par"
|
||
(Utvikling), "%" (alle prosentgrafer: Fairwaytreff, Greentreff,
|
||
Én-putt, Scrambling/Sand save), "Putt" (Putt/18), "Antall" (Chip/
|
||
Bunkerslag/Straffeslag/Anywayslag).
|
||
|
||
`BarTrendChart` viste tidligere kun verdi-etikett på SISTE (nyeste)
|
||
stolpe. Lagt til samme etikett på FØRSTE stolpe også (dempet stil,
|
||
samme mønster som `CumulativeTrendLine` allerede brukte for sine
|
||
start-/sluttpunkt-etiketter) -- grafen kan nå leses uten en egen
|
||
tallskala i begge ender, ikke bare ved siste punkt.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, 45/45 vitest.
|
||
|
||
77. **Avstand til mål (rangefinder) via GolfAPI.io — Tjøme Golfklubb,
|
||
2026-08-12.** Ny, tredje banekilde (`app/golfapi_client.py`,
|
||
`app/golfapi_cache.py`) for baner utenfor TeeOffs dekning. Full
|
||
detalj og alle beslutninger i **ADR-064**, kort oppsummert her:
|
||
|
||
Brukeren fikk et GolfAPI.io-token (20 kall) og ba om avstands-
|
||
visning til grønn på baner TeeOff ikke dekker -- konkret testet mot
|
||
Tjøme Golfklubb, som brukeren bekreftet IKKE finnes i TeeOff. Live
|
||
validert mot ekte GolfAPI FØR noe ble bygget (søk, fullt scorekort,
|
||
167 koordinatpunkter for Tjøme -- grønn front/midt/bak, hindringer,
|
||
tee-punkter), 2,3 av 20 kall brukt på research.
|
||
|
||
Migrasjon 065: delt, globalt cache-lag (`golfapi_course`/`_hole`/
|
||
`_tee`/`_coordinate`) -- hentes KUN én gang noensinne per fysisk
|
||
bane (GolfAPIs avtale tillater eksplisitt permanent caching), via
|
||
én sentral chokepoint-funksjon som all import-kode må gå via.
|
||
`course_source` utvidet med `'international'` (org-turneringer);
|
||
`personal_course` fikk en ny `external_golfapi_course_id`-kolonne
|
||
(frittstående runder) -- bekreftet med bruker at BEGGE skal
|
||
støttes. Nye endepunkter: `international-search`/`-import` i både
|
||
`courses.py` (org) og `rounds.py` (personal), samt
|
||
`GET /rounds/{id}/holes/{n}/target-points` (returnerer RÅ
|
||
koordinater, aldri en ferdigregnet avstand -- klienten Haversine-
|
||
regner selv mot spillerens ferske GPS-posisjon, samme
|
||
`frontend/lib/geo.ts`-funksjon som ADR-048).
|
||
|
||
v1-visning: ren tall-/tekstvisning ("142 m front/151 m midt/163 m
|
||
bak"), intet kart -- bekreftet med bruker (AskUserQuestion), null
|
||
ekstra Mapbox-kostnad. En fremtidig freemium-idé (gratis=tall,
|
||
betalt=interaktivt kart) ble foreslått av bruker og notert i
|
||
ADR-064, ikke bygget (ingen betalingsinfrastruktur finnes).
|
||
|
||
Frontend: søk-og-koble-UI lagt til i `tournament-program.tsx`
|
||
(org-siden, to kallsteder) og `round-detail.tsx` (frittstående
|
||
runder, "Fant ikke banen? Søk internasjonalt"-fallback under egen
|
||
bane-søk).
|
||
|
||
**Rangefinder-visningen, samme dag:** V0-prompten
|
||
(`v0-prompt-rangefinder.md`) ble kjørt av bruker (zip 10,
|
||
`Temp-uploads/tee-cup (10).zip`) -- `components/target-distance.tsx`
|
||
kom tilbake nøyaktig som spesifisert (props-drevet, ingen egen GPS-/
|
||
fetch-logikk, clubhouse-paletten korrekt brukt). Koblet inn av
|
||
Claude via en ny wrapper, `components/hole-target-distance.tsx`
|
||
(henter `target-points`, kjører `watchPosition` -- samme
|
||
`enableHighAccuracy`/`maximumAge: 1000`/grasiøs-degraderings-mønster
|
||
som `map-point-picker.tsx` allerede bruker for slagmåling, ADR-048
|
||
tillegg 2026-08-08 del 2 -- og Haversine-regner selv), montert i
|
||
`ScoringWizard` sin hull-header i `round-detail.tsx`. Viser INGEN
|
||
rad i det hele tatt for baner uten GolfAPI-koordinatdata (det store
|
||
flertallet av runder) -- ikke en "ingen data"-lapp på hver eneste
|
||
scorekort-åpning.
|
||
|
||
**Reell driftsfeil fanget under denne utrullingen:**
|
||
`TEECUP_GOLFAPI_TOKEN` var satt i `.env`, men `docker-compose.yml`
|
||
sin `teecup_api`-tjeneste videreførte den aldri til containeren --
|
||
endepunktene ville svart "ikke konfigurert" i produksjon uansett
|
||
hva `.env` sa. Lagt til i miljø-blokken, samme mønster som de andre
|
||
`TEECUP_*`-hemmelighetene.
|
||
|
||
**Verifisert:** migrasjon 065 kjørt mot automatisert scratch-
|
||
database (`scripts/run_backend_tests.sh`), 31/31 eksisterende
|
||
backend-tester uendret grønne. Egen scratch-runde med de FAKTISKE
|
||
Tjøme-svarene som fixtures (monkeypatchet `golfapi_client`, for å
|
||
ikke bruke flere av de knappe API-kallene under iterasjon):
|
||
cache-chokepunktet bekreftet idempotent (henter GolfAPI nøyaktig én
|
||
gang, aldri på nytt ved gjentatte kall), 18 hull + 4 tees + 167
|
||
koordinater cachet korrekt, org-import ga riktig antall
|
||
hull/tee_rating-rader, duplikat-import avvist i BEGGE modeller,
|
||
target-points-oppslaget ga korrekte front/midt/bak-punkter. **Ett
|
||
bevisst, siste ekte kall** mot GolfAPI (2 kall) bekreftet at hele
|
||
kjeden -- inkl. `golfapi_client.py` sitt faktiske HTTP-kall, ikke
|
||
bare cache-logikken -- fungerer mot den virkelige tjenesten.
|
||
`teecup_db` bekreftet uendret gjennom hele verifiseringen
|
||
(scratch-database+rolle droppet etterpå). Frontend: `tsc --noEmit`
|
||
rent, 45/45 vitest.
|
||
|
||
**Full-stack scratch-verifisering av rangefinder-koblingen**
|
||
(separat scratch-database/API/frontend/MinIO-sett, GolfAPI-cachen
|
||
forhåndsseedet fra de samme lagrede Tjøme-fixturene -- null nye
|
||
API-kall): ekte innlogging, import av Tjøme via det virkelige
|
||
`POST /personal-courses/international-import`-endepunktet (cache-
|
||
treff, 0 kall), opprettet en runde, åpnet scoreførings-veiviseren
|
||
for hull 1 i nettleseren (Chrome DevTools MCP, geolocation-
|
||
emulering). Bekreftet visuelt i BÅDE lys og mørk modus: "Front
|
||
118 m / Midt 132 m / Bak 144 m" (Midt uthevet), "● Live"-indikator,
|
||
nærmeste hindring korrekt identifisert og vist ("Bunker (grønn)
|
||
(front): 104 m"), ingen konsollfeil. `tsc --noEmit` rent, 45/45
|
||
vitest. Scratch-database/rolle/containere ryddet opp etterpå,
|
||
`teecup_db` bekreftet uendret (`\l`-sjekk).
|
||
|
||
**Rullet ut 2026-08-12**, bruker bekreftet ("kjør" / "NÅ kan du
|
||
kjøre på" etter å ha lastet opp V0-eksporten). Migrasjon 065 kjørt
|
||
mot ekte `teecup_db` (additiv, ingen eksisterende data rørt) --
|
||
`docker exec -i teeoff_db psql ... -f 065_golfapi_courses.sql`,
|
||
deretter `docker compose build teecup_api teecup_frontend && up -d`
|
||
(kjørt TO ganger denne runden -- første gang før rangefinder-
|
||
komponenten/docker-compose-fiksen var klare, brukeren stanset
|
||
bevisst midtveis for å laste opp V0-eksporten først, andre gang
|
||
etter). Rene containerlogger begge ganger, ingen konsollfeil på
|
||
`https://teecup.golf/logg-inn` etter siste omstart. ~15,5 av 20
|
||
GolfAPI-kall gjenstår.
|
||
|
||
**Tillegg 2026-08-13 -- den PRIMÆRE "Ny runde"-veiviseren manglet
|
||
GolfAPI-søket, og en reell ruting-bug ble funnet og fikset.**
|
||
Brukeren spurte "hvor, nøyaktig, kan jeg se dette implementert?" --
|
||
undersøkelsen avdekket at søk-og-koble-UI-en fra 2026-08-12 KUN var
|
||
koblet inn i "endre bane på en allerede opprettet runde"-skjemaet
|
||
(`ChangeCourseForm`, round-detail.tsx), IKKE i den faktiske
|
||
`/my-rounds/new`-veiviseren (`components/ny-runde/step1-course-
|
||
time.tsx` + `lib/ny-runde/api.ts`) -- en helt separat komponenttre
|
||
brukeren faktisk møter FØRST. Lagt til der også: ny
|
||
`"international-search"`-delstilstand (`Sub1`-typen),
|
||
`InternationalSearch`-komponent (samme klubb->baner-todelte søk som
|
||
de to andre stedene, i `--nr-*`-designtokens), `searchInternationalClubs`/
|
||
`importInternationalCourse` i `lib/ny-runde/api.ts`.
|
||
|
||
**Ekte bug fanget under nettleserverifisering av DENNE
|
||
integrasjonen** (ikke funnet i forrige runde fordi kun POST
|
||
`/personal-courses/international-import` ble reelt HTTP-testet der,
|
||
aldri GET `/personal-courses/international-search`): 500-feil,
|
||
`asyncpg.exceptions.DataError: invalid input for query argument $1:
|
||
'international-search' (invalid UUID...)`. Årsak: FastAPI matcher
|
||
ruter i REGISTRERINGSREKKEFØLGE -- `GET /personal-courses/
|
||
{personal_course_id}` (allerede eksisterende, generisk) var
|
||
registrert FØR den nye, mer spesifikke `GET /personal-courses/
|
||
international-search`, så `{personal_course_id}` fanget grådig opp
|
||
"international-search" som en (ugyldig) UUID. Samme fellgruve
|
||
fantes IKKE på org-siden (courses.py) fordi det der ikke finnes noen
|
||
bar `/courses/{course_id}`-rute å kollidere med (kun `/{course_id}/
|
||
tees` og `/{course_id}/holes`, annen path-form). Fikset ved å flytte
|
||
de to nye endepunktene til FØR `get_personal_course` i
|
||
`rounds.py` -- lærdom kommentert direkte i koden for å unngå samme
|
||
feil neste gang en literal-path-rute legges til ved siden av en
|
||
eksisterende parameterisert rute.
|
||
|
||
**Verifisert på nytt, fullt ut, etter fiksen:** frisk scratch-stack,
|
||
ekte innlogging, full klikk-gjennom-flyt i den FAKTISKE "Ny
|
||
runde"-veiviseren (Egen bane -> "Fant ikke banen? Søk
|
||
internasjonalt" -> søk «Tjøme» -> Tjøme Golfklubb -> importer ->
|
||
landet korrekt på "Bane & tid"-steget med "Tjøme Golfklubb – Bane",
|
||
4 utslag), ingen konsollfeil, bekreftet via nettverksfane at søket
|
||
nå returnerer 200 med reelt klubbtreff (ikke lenger 401/500).
|
||
`tsc --noEmit` rent, 45/45 vitest, 31/31 backend-tester. Scratch-
|
||
ressurser ryddet opp, `teecup_db` bekreftet uendret.
|
||
|
||
**Rullet ut 2026-08-13**, samme økt. `docker compose build
|
||
teecup_api teecup_frontend && up -d`. Rene containerlogger, ingen
|
||
konsollfeil på `https://teecup.golf/logg-inn` etter omstart.
|