playerListReached-effekten i round-detail.tsx hadde en stale "if (!mapExpanded) return"-vakt igjen fra før knappens synlighets- trigger ble frikoblet fra mapExpanded (se CHANGELOG #146). Siden banekartet er kollapset som standard, kjørte effekten aldri for de fleste brukere -- playerListReached ble værende false for alltid, og knappen sluttet aldri å vises igjen etter å ha dukket opp forbi side-flyt-knappen, inkl. over Kommentarer-seksjonen lengre ned. Fjernet vakten, effekten måler nå alltid. Verifisert i full-stack scratch med samme scenario som brukerens skjermdump (3 spillere + 6 kommentarer), aria-hidden bekreftet korrekt gjennom hele scrollet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
14673 lines
911 KiB
Markdown
14673 lines
911 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.
|
||
|
||
**Tillegg samme dag (del 3) -- feil skjerm.** Bruker sendte et
|
||
skjermbilde fra ekte produksjon (hadde selv importert Tjøme og
|
||
opprettet en runde) og pekte ut at avstandsindikatoren skulle vært
|
||
synlig i HOVEDVISNINGEN (`PlayerHoleCards`, hull-navigator-kortet
|
||
over spillerlisten -- det brukeren faktisk ser når de går ut på
|
||
hullet), ikke inne i scoreførings-veiviseren (dit den ble montert
|
||
2026-08-12 -- feil skjermøyeblikk, en spiller åpner den veiviseren
|
||
ETTER hullet er spilt, ikke før tilnærmingsslaget). Ba også om mer
|
||
bredde enn kompaktvarianten som satt der.
|
||
|
||
`HoleTargetDistance` sin egen rad-wrapper (border/padding, tilpasset
|
||
veiviser-headeren) fjernet -- kalleren styrer nå plassering/bredde
|
||
selv. Flyttet fra `ScoringWizard`-headeren til `PlayerHoleCards`,
|
||
rett under hull-navigatoren (Forrige/Hull N/Neste + hurtig-hopp-
|
||
stripen) og over spillerkortene -- nøyaktig der brukeren pekte,
|
||
`size="full"` i stedet for `"compact"`.
|
||
|
||
**Verifisert:** ny scratch-runde (samme fixture-cachede Tjøme-data,
|
||
0 nye API-kall), ekte innlogging, ekte runde, geolocation-emulering
|
||
+ `initScript`-injisert `watchPosition`-shim (persisterer over en
|
||
reload, ulikt en engangs `evaluate_script`-injeksjon). Bekreftet
|
||
visuelt lys+mørk: full-bredde "Til grønn"-kort rett under hull-
|
||
navigatoren, front/midt/bak + hindringslinje + live-indikator,
|
||
ingen konsollfeil. `tsc --noEmit` rent, 45/45 vitest. Scratch-
|
||
ressurser ryddet opp, `teecup_db` uendret.
|
||
|
||
**Rullet ut 2026-08-13**, samme økt. Ren frontend-endring (ingen
|
||
backend/migrasjon rørt) -- `docker compose build teecup_frontend &&
|
||
up -d teecup_frontend`. Ingen konsollfeil på
|
||
`https://teecup.golf/logg-inn` etter omstart.
|
||
|
||
**Tillegg samme dag (del 4) -- strømsparing.** Bruker rapporterte
|
||
høyt batteriforbruk nå som GPS-en er aktivert. To justeringer i
|
||
`hole-target-distance.tsx`, foreslått med tradeoffs FØR bygging (jf.
|
||
"Gi meg forslag før..."-arbeidsmåten): (1) `maximumAge` økt fra
|
||
1000ms til 4000ms -- en golfer beveger seg ~1,5 m/s, oppdatering
|
||
hvert 4. sekund er umerkelig men ber GPS-brikken om et ferskt fix
|
||
langt sjeldnere. (2) `watchPosition` pauses nå helt når fanen/appen
|
||
ikke er synlig (`document.visibilitychange`) -- ingen vits å holde
|
||
GPS aktiv for en skjerm ingen ser på (skjerm låst, annen app i
|
||
forgrunnen) -- og gjenopptas automatisk når brukeren kommer tilbake.
|
||
`enableHighAccuracy` bevisst UENDRET -- lav nøyaktighet ville gitt
|
||
titalls meters feilmargin, for upresist til at front/midt/bak-
|
||
tallene gir mening.
|
||
|
||
**Verifisert:** ny scratch-runde (samme fixture-cachede Tjøme-data),
|
||
ekte innlogging/import/runde, geolocation-emulering +
|
||
`initScript`-injisert `watchPosition`/`clearWatch`-spion (telte
|
||
faktiske kall). Bekreftet i nettleseren: `document.hidden = true` +
|
||
`visibilitychange` → `clearWatch` kalt umiddelbart, visningen gikk
|
||
til "Ikke live — sist målte posisjon" (siste kjente tall beholdt,
|
||
ikke nullstilt); `document.hidden = false` + `visibilitychange` →
|
||
`watchPosition` kalt på nytt (kall-telleren gikk fra 1 til 2), "●
|
||
Live" gjenopprettet. `tsc --noEmit` rent, 45/45 vitest, ingen
|
||
konsollfeil. Scratch-ressurser ryddet opp, `teecup_db` uendret.
|
||
|
||
**Rullet ut 2026-08-13**, samme økt. Ren frontend-endring --
|
||
`docker compose build teecup_frontend && up -d teecup_frontend`.
|
||
Ingen konsollfeil på `https://teecup.golf/logg-inn` etter omstart.
|
||
|
||
78. **Per-deltaker-fullføring av frittstående runder + scorekort på
|
||
e-post — 2026-08-13, se ADR-065.** Bruker: fire spillere sammen,
|
||
to går 18 hull, en 9, en må avslutte etter 14 -- disse rundene
|
||
skulle kunne markeres fullført per spiller, uten å måtte fullføre
|
||
hele runden, og uten å låse scoreføring for de andre.
|
||
|
||
Migrasjon 066: `round_participant.completed_at timestamptz`.
|
||
Backend (`app/routers/rounds.py`): `ParticipantUpdate.completed`,
|
||
ny `_FLIGHT_MANAGED_PARTICIPANT_FIELDS = {"completed"}` -- speiler
|
||
`update_hole`s "lenket medspiller kan registrere for hele
|
||
flighten"-filosofi, IKKE eier-/selv-only. `ALREADY_COMPLETED` (409)
|
||
hvis `round.completed_at` allerede er satt. `_send_guest_round_
|
||
summaries` erstattet av `_send_participant_round_summary(conn,
|
||
round_id, participant_id, round_label) -> bool`, som nå dekker
|
||
BÅDE gjester og lenkede kontoer (LEFT JOIN `app_user`) -- kun
|
||
første `null -> true`-transisjon trigger e-post (idempotent).
|
||
`complete_round` har en fallback-løkke som fullfører+e-poster
|
||
enhver deltaker som IKKE allerede ble individuelt avsluttet, ingen
|
||
dobbel-sending for de som var det. `app/email.py`s `send_round_
|
||
summary_email` fikk `is_linked_account`/`round_id` -- lenkede
|
||
mottakere får direktelenke til `/my-rounds/{id}` og hilsen med
|
||
`first_name` (navneformat-regelen), gjester beholder magic-link.
|
||
|
||
**Tillegg samme dag under samtalen:** må også kunne sende
|
||
scorekort til en enkelt spiller i ETTERTID, lenge etter at runden
|
||
er fullført. Nytt endepunkt `POST /rounds/{id}/participants/{id}/
|
||
send-scorecard` -- samme flight-styrte tilgang, men bevisst INGEN
|
||
`ALREADY_COMPLETED`-sperre (leser/sender, muterer aldri
|
||
databasetilstand). 204 ved suksess, `NO_EMAIL_ADDRESS` (400) uten
|
||
registrert adresse.
|
||
|
||
**Verifisert:** `python3 -m py_compile` + full
|
||
`./scripts/run_backend_tests.sh`. Egen scratch-database: 4-spiller
|
||
runde (lenket bruker + gjest med e-post), ulikt hull-antall,
|
||
avsluttet to deltakere via PATCH -- bekreftet e-post umiddelbart
|
||
med riktig mottaker-variant, `ALREADY_COMPLETED` etter
|
||
rundefullføring, fallback-løkken sender kun til ikke-avsluttede,
|
||
lenket medspiller (ikke eier) kan avslutte ANNEN deltaker
|
||
(flight-tilgang bekreftet), angre (`completed: false`) fungerer
|
||
før rundefullføring, on-demand-endepunktet fungerer også lenge
|
||
etter fullføring.
|
||
|
||
**Rullet ut 2026-08-13**, samme økt, sammen med migrasjon 067
|
||
(punkt 79 under). `docker compose build teecup_api && up -d`.
|
||
|
||
**Tillegg samme dag -- frontend-UI bygget og rullet ut.**
|
||
`Player.completed` (fra `ApiParticipant.completed`), delt
|
||
`ParticipantCompletionActions`-komponent i BÅDE `PlayerHoleCards`
|
||
(Score-fanen) og `PlayerList` ("Spillere og runde"-fanen):
|
||
"Avslutt for [navn]" (skjult når runden er fullført),
|
||
"Ferdig ✓ (N hull)" + "Angre" (kun før rundefullføring),
|
||
"Send scorekort" (alltid synlig, uansett fullføringsstatus).
|
||
`allHolesEnteredForEveryone` + hurtig-hopp-stripen teller en
|
||
`completed`-spiller som ferdig uansett faktisk hull-dekning.
|
||
`advanceWizardPlayer` hopper over fullførte spillere. Et ikke-spilt
|
||
hull for en fullført spiller viser "Ferdig" i stedet for
|
||
"Registrer".
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, 45/45 vitest. Ekte
|
||
nettleserverifisering (ekte innlogging inkl. reell TOTP-2FA-kode
|
||
generert fra kontoens lagrede secret, ekte runde på Tjøme -- cachet
|
||
bane, 0 nye GolfAPI-kall -- to deltakere): avslutning av gjesten
|
||
trigget e-post umiddelbart (ingen feil i loggen), UI oppdaterte til
|
||
"Ferdig ✓"/"Angre"/"Ferdig"-tekst korrekt, "Send scorekort" for
|
||
eieren (lenket konto, annen kodesti enn gjesten) ga "Sendt ✓",
|
||
"Angre" tilbakestilte og var persistent etter fane-bytte. Lys+mørk
|
||
bekreftet visuelt, ingen konsollfeil. Testrunden slettet etter
|
||
verifisering, bekreftet fjernet fra `teecup_db`.
|
||
|
||
**Rullet ut 2026-08-13**, samme økt. Ren frontend-endring (ingen ny
|
||
migrasjon) -- `docker compose build teecup_frontend && up -d
|
||
teecup_frontend`.
|
||
|
||
79. **To nye baner (Larvik/Seasidebanen, Nesbyen/Nesfjellet) via
|
||
GolfAPI + en reell datakvalitetsbug funnet under importen —
|
||
2026-08-13, se ADR-064-tillegg.** Bruker ba om å mappe Larvik
|
||
(Seasidebanen) og Nesbyen, sistnevnte manuelt GPS-registrert (124
|
||
feltbefarte punkter, CSV) siden GolfAPI selv mangler koordinater
|
||
for banen (`hasGPS=0`).
|
||
|
||
To nye `poi_type`-verdier (migrasjon 067): `rock` (fjellknaus --
|
||
hinder) og `layup` (til fairway -- kun informativt). Bruker
|
||
korrigerte terminologi underveis: "Bunker (green)" ikke "Bunker
|
||
(grønn)" (green er et låneord, ikke oversatt), og "Over vann"
|
||
skal telle som "Bakkant vann" (samme `poi_type`/`location`).
|
||
|
||
**Reell bug funnet under import:** GolfAPI returnerer noen ganger
|
||
tom STRENG (`""`) i stedet for `null`/fraværende for course
|
||
rating/slope-felt -- ikke bare for kvinner (ADR-019s tilsvarende
|
||
antakelse for teeoff), Nesbyen manglet `slopeMen` også. Fikset på
|
||
tre nivåer: `golfapi_cache.py` normaliserer `""` -> `None` for
|
||
alle fire ratingfelt, migrasjon 068 gjør `golfapi_course_tee.
|
||
course_rating_men`/`slope_men` nullable (var feilaktig NOT NULL),
|
||
og begge import-endepunktene (`courses.py`, `rounds.py`) bygger nå
|
||
rating per utslag defensivt -- hopper over et utslag uten brukbar
|
||
rating, avviser hele importen med `EXTERNAL_DATA_INCOMPLETE` FØR
|
||
transaksjonen hvis INGEN utslag har noen brukbar rating. Nesbyens
|
||
6 utslag hadde faktisk ingen rating i det hele tatt hos GolfAPI --
|
||
bruker oppga alle 6 (herre+dame, ett uten damerating) manuelt fra
|
||
klubbens scorekort, satt inn i `golfapi_course_tee`-cachen før
|
||
import fullførte.
|
||
|
||
**Verifisert:** `python3 -m py_compile` rent, full
|
||
`./scripts/run_backend_tests.sh` (31/31, migrasjon 068 bekreftet
|
||
anvendbar). Selve importen kjørt mot ekte `teecup_db` via et
|
||
engangsskript i `teecup_api`-containeren (speiler
|
||
`import_international_personal_course`s kopier-inn-logikk).
|
||
Etterpå bekreftet med read-only spørring: Larvik (18 hull, 6
|
||
utslag, 11 tee-ratinger, 95 koordinater) og Nesbyen (18 hull, 6
|
||
utslag, 11 tee-ratinger, 124 koordinater), begge eid av Erol
|
||
Haagenrud, `golfapi_course.has_gps`/`num_coordinates` korrigert
|
||
for Nesbyen.
|
||
|
||
**Rullet ut 2026-08-13**, samme økt. Migrasjon 067+068 kjørt mot
|
||
ekte `teecup_db`, `docker compose build teecup_api && up -d`, ren
|
||
oppstartslogg.
|
||
|
||
80. **Scorekort-e-posten (punkt 78/ADR-065) erstattet med DET FULLE
|
||
scorekortet, matcher selve Scorekort-siden visuelt — 2026-08-13/14,
|
||
se ADR-065-tillegg.** Bruker, rett etter at punkt 78 var rullet ut:
|
||
ville ha "det fulle scorekortet, det som inneholder all statistikk
|
||
for runden" i e-posten (den sendte da kun slag/netto), pluss dato/
|
||
klokkeslett/spilletid i hilsenen. Et første forsøk (enkel vertikal
|
||
hull-per-rad-tabell) ble erstattet samme kveld etter at bruker sendte
|
||
et skjermbilde av selve appens Scorekort-side og ba om at e-posten
|
||
skal SE UT SOM den, ikke bare inneholde de samme tallene.
|
||
|
||
`send_round_summary_email` (email.py) bygget helt om: hull som
|
||
KOLONNER i to 9-hulls Ut/Inn-blokker (Tot for 9-hullsrunder), porterer
|
||
`ScoreBlock`/`TotalTile` fra frontend/components/round-scorecard.tsx
|
||
til inline-styled HTML -- fargede score-merker (eagle/birdie/par/
|
||
bogey/double), Fw/Innspill-glyffer (◎/↖/↗/↑/↓/←/→), GIR-hake, fire
|
||
total-tiles (Par/Score/Til par/Poeng) nederst. Rader graderer seg
|
||
(kun tatt med når minst ett hull har data). `RoundSummaryHole`
|
||
utvidet med `stroke_index`/`picked_up`/`points`(Stableford)/`putts`/
|
||
`tee_shot_result`/`approach_result`/`chip_count`/`bunker_shot_count`/
|
||
`penalty_strokes`/`anyway_strokes`. Rad-etikettene `Score`/`Netto`/
|
||
`Poeng` kortet til `Scr`/`Net`/`Pnt` etter enda et bruker-tilbakemeldt
|
||
skjermbilde av selve e-posten (for trangt i en 9-kolonners tabell).
|
||
|
||
**To reelle bugs funnet under ekte ende-til-ende-testing (ikke bare
|
||
isolert forhåndsvisning):** (1) `round.played_at` er KUN en
|
||
`date`-kolonne, ikke timestamptz -- "Utslagstid" i ny-runde-veiviseren
|
||
fanges opp av wizard-state men sendes faktisk ALDRI til backend
|
||
(`wizard-context.tsx` sender kun `played_at: s.date`), egen
|
||
allerede-eksisterende feil, ikke rettet her. Løsning: brukte
|
||
`round.started_at` for klokkeslettet i stedet, med graceful fallback.
|
||
(2) Samme rot-årsak forplantet seg til spilletid-utregningen
|
||
(`duration_start` falt tilbake til `played_at`, en `date`, ville
|
||
krasjet mot en ekte `datetime` ved subtraksjon) -- fjernet fallbacken,
|
||
ingen varighet vises i stedet for å blande typer.
|
||
|
||
**Verifisert:** `python3 -m py_compile` + 31/31 backend-tester ved
|
||
hver iterasjon. Isolert forhåndsvisning (monkeypatchet `_send_sync`,
|
||
18 hull med data for ALLE graderte felt inkl. eagle/dobbel bogey/
|
||
plukket opp) nettleser-rendret og skjermbilde-sammenlignet direkte
|
||
mot brukerens eget skjermbilde av Scorekort-siden -- bekreftet
|
||
visuelt samsvar. Ekte ende-til-ende `POST .../send-scorecard` mot en
|
||
reell, tidligere fullført runde i produksjon, gjentatt etter hver
|
||
fiks -- endte med 204 og ren `teecup_api`-logg.
|
||
|
||
**Rullet ut 2026-08-14** (flere delrunder samme kveld/natt, spenner
|
||
over datogrensen) -- ren backend-endring, ingen ny migrasjon,
|
||
`docker compose build teecup_api && up -d teecup_api` hver gang.
|
||
|
||
81. **FEATURE_BACKLOG.md-gjennomgang: hele filen (4457 linjer) lest linje
|
||
for linje, seks stale "ikke bygget"-seksjoner rettet — 2026-08-14.**
|
||
Bruker spurte om alt arbeid siste dager faktisk var registrert i
|
||
.md-filene (foranlediget av at punkt 80 over nesten ble glemt
|
||
committet, se dét punktets historie) — svaret utvidet seg til et
|
||
ønske om å gå gjennom hele `FEATURE_BACKLOG.md` for glemte/utdaterte
|
||
punkter. Full, systematisk gjennomgang (ikke stikkprøver) skilte
|
||
reelt åpne oppgaver (15 punkter, bl.a. Bøtekasse, per-hull-historikk,
|
||
OOM sin eclectic-/lag-/offentlig-visning, scramble-mot-enkeltspiller
|
||
for org-lagturneringer, det nyoppdagede `played_at`-klokkeslett-hullet)
|
||
fra seks steder der filen selv var stale (markert "ikke bygget" på
|
||
noe som faktisk var levert, bare uten oppdaterings-notat):
|
||
- **Internasjonale baner** — foreslo GolfAPI.io presist, som senere
|
||
faktisk ble valgt og bygget som ADR-064/065 (Tjøme/Larvik/Nesbyen,
|
||
live). Den klart viktigste av de seks.
|
||
- **Individuelle turneringer** — siste notat sa "ikke rullet ut mot
|
||
ekte teecup_db", men migrasjon 040 er bekreftet live (verifisert
|
||
direkte mot skjemaet) -- må ha blitt rullet ut et sted mellom
|
||
2026-07-30 og 2026-08-04 (Order of Merit, migrasjon 055, forutsetter
|
||
det) uten at et eget "rullet ut"-notat noensinne ble skrevet.
|
||
- **PWA-app-ikon** — merket "MIDLERTIDIG, skal erstattes senere";
|
||
et ekte ikon ble designet og rullet ut som ADR-055 2026-08-10.
|
||
- **Push-varsler** — en tidlig, isolert "📋 planlagt"-linje som aldri
|
||
ble fjernet etter at en egen, senere seksjon i samme fil bygde og
|
||
rullet ut hele varsel-systemet (in-app + push) 2026-07-28.
|
||
- **Dashboard-blokkforslaget** — merket "IKKE BYGGET", men bekreftet
|
||
bygget nesten ordrett i `dashboard.tsx` (samme dag, som del av
|
||
ADR-035) -- rekkefølgen i filen ga bare et misvisende inntrykk.
|
||
- **Flaggturnering sin GPS-kobling** — noterte en avhengighet til GPS
|
||
som senere ble bygget (ADR-048/064); selve formatet var allerede
|
||
live, kun GPS-posisjon-ved-siste-slag-ideen er fortsatt åpen.
|
||
|
||
Hver rettelse skrevet som et eksplisitt **RETTELSE**-avsnitt (samme
|
||
disiplin filen selv allerede etablerte andre steder, f.eks.
|
||
"RETTELSE 2026-08-03" i turneringsformat-seksjonen) -- historikken
|
||
under hver rettelse er beholdt uendret, ikke slettet. `Sist
|
||
oppdatert`-datoen øverst i filen (stod på 2026-07-17) rettet
|
||
tilsvarende. Ingen kodeendring, ren dokumentasjonsopprydding.
|
||
|
||
82. **Flaggturnering "Del A": GPS-flaggplanting + runde 2+-scoreføring
|
||
(ADR-066) — 2026-08-14.** Første av tre bedt-om utvidelser
|
||
(Del A GPS-planting/runde 2, Del B kartoversikt+synlighetsbryter,
|
||
Del C Eclectic-format) -- kun Del A bygget denne runden, se ADR-066
|
||
for full begrunnelse/avklaringer og plan-filens spesifikasjon av
|
||
Del B/C.
|
||
|
||
Motor: ny `flag_lap_and_hole()`-hjelpefunksjon i
|
||
`handicap_engine.py` (`flag_result()` selv trengte ingen endring --
|
||
kalleren konkatenerer lap 1+lap 2s gross_strokes til én flat liste
|
||
før kallet). 5 nye tester, full suite 122/122.
|
||
|
||
Migrasjon 069 (`round_participant_flag_plant` +
|
||
`round_hole_flag_overflow`, frittstående) og 070 (samme par,
|
||
org-individuelle turneringer, RLS-beskyttet) -- to HELT ISOLERTE
|
||
tabellpar, bevisst IKKE en `lap`-kolonne på delte
|
||
`round_hole`/`tournament_round_hole` (se ADR-066 for
|
||
blindsone-begrunnelsen).
|
||
|
||
API: speilende `flag-plant`/`flag-overflow`-endepunkter i
|
||
`rounds.py` og `individual_tournaments.py`. Server beregner faktisk
|
||
"hull i gang" fra registrert scoredata og avviser
|
||
(`VALIDATION_FAILED`) ethvert forsøk som ikke stemmer -- klientens
|
||
"Hullet du ut?"-flyt er kun en UX-guide. To ulike, bevisst beholdte
|
||
autorisasjonsmodeller: flight-styrt (frittstående) vs. selv-only
|
||
(org), speiler hver fils eksisterende `update_hole`-mønster.
|
||
|
||
Frontend: `FlagPlantSheet`/`FlagPlantSheetOrg` -- ny UI-overflate,
|
||
derfor bygget via en Claude-skrevet V0-prompt (sendt til bruker),
|
||
med en tydelig MERKET midlertidig håndkodet versjon (samme
|
||
props-kontrakt) i mellomtiden for å kunne verifisere hele
|
||
backend-flyten uten å vente på eksporten. **V0-eksporten (zip 11)
|
||
kom tilbake samme dag** og erstattet den midlertidige versjonen i
|
||
begge filer (org-varianten fikk i tillegg `expectedLap`-prop lagt
|
||
til kallstedet, som den manglet før).
|
||
|
||
**Verifisert:** `python3 -m py_compile` + full
|
||
`./scripts/run_backend_tests.sh` (46/46, 15 nye tester i to nye
|
||
testfiler). Egen scratch-database + scratch `teecup_api`-container
|
||
(port 18000) + lokal `next dev` (port 13000): ende-til-ende for
|
||
BEGGE hjem inkl. server-side avvisning av for-tidlig planting og
|
||
full runde 2-scoreføring, geolocation emulert via Chrome DevTools
|
||
MCP (måtte omgå at `emulate()` kun setter koordinater, ikke
|
||
Permissions API-tilstanden -- løst med `navigate_page({type:
|
||
"reload", initScript: ...})`), lys+mørk bekreftet, lap 2 sin
|
||
`(runde {lap})`-markering bekreftet live. Etter V0-swap: `tsc
|
||
--noEmit` rent, 45/45 vitest, begge filer. Scratch-stacken revet
|
||
ned igjen etterpå.
|
||
|
||
**Rullet ut 2026-08-14** -- migrasjon 069/070 kjørt mot ekte
|
||
`teecup_db` som `teeoff_admin` (`teecup_app` har bevisst ingen
|
||
`CREATE`-rett på schema public, jf. ADR-002 -- migrasjoner kjøres av
|
||
admin-rollen, aldri runtime-rollen), etter eksplisitt bekreftelse fra
|
||
bruker. Alle fire nye tabeller verifisert direkte i skjemaet
|
||
etterpå. `docker compose build teecup_api teecup_frontend && up -d`
|
||
-- begge containere startet rent (`Application startup complete.`
|
||
henholdsvis `✓ Ready`), ingen feil i logg.
|
||
|
||
83. **Flaggturnering "Del B": kartoversikt over plantede flagg +
|
||
synlighetsbryter (ADR-067) — 2026-08-14.** Andre av tre bedt-om
|
||
utvidelser (se punkt 82/ADR-066 for Del A). Underveis avdekket:
|
||
org-individuelle turneringer har INGEN offentlig tilskuer-side i det
|
||
hele tatt (`/t/[id]/live` er rendyrket for lagturneringer) --
|
||
avklart med bruker via AskUserQuestion, som valgte å avgrense Del B
|
||
sin org-side til org-medlemmer/deltakere fremfor å bygge en helt ny
|
||
spectator-infrastruktur bare for kartet. Frittstående runder fikk
|
||
full tilskuerstøtte (gjenbruker eksisterende `/watch/[id]`/
|
||
`/public/rounds/*`-mønster).
|
||
|
||
Migrasjon 071: to boolean-kolonner (`round.flag_map_visible`,
|
||
`tournament.flag_map_visible`, begge default false) -- ingen nye
|
||
tabeller, gjenbruker flagg-plant-tabellene fra migrasjon 069/070
|
||
direkte. Bryteren endres via de allerede eksisterende PATCH-
|
||
endepunktene (kun nye felt på whitelist-drevne modeller, ingen ny
|
||
handler-kode). Nytt leseendepunkt per hjem (`GET .../flag-map`),
|
||
delt `_build_flag_map()`-hjelper mellom autentisert og offentlig
|
||
variant -- alle flagg når bryteren er PÅ, ellers kun requesterens
|
||
eget.
|
||
|
||
Frontend: enda en ny UI-overflate via V0-prompt (`flag-map-
|
||
overview.tsx`, FØRSTE flerpunkts-Mapbox-kart i appen), midlertidig
|
||
håndkodet versjon bygget parallelt for å unngå å vente. Tre tynne
|
||
"seksjon"-wrappere (`FlagMapSection`/`FlagMapSectionOrg`/
|
||
`WatchFlagMap`) i de tre relevante skjermene, samme
|
||
"egen fetch + next/dynamic(ssr:false)"-mønster som `NassauPanel`.
|
||
Lap 1/2+ skilles med BÅDE farge OG en "R{lap}"-tekstbadge på selve
|
||
markøren (aldri farge alene), pluss alltid synlig tekst-legende.
|
||
|
||
**Verifisert:** `python3 -m py_compile` + full
|
||
`./scripts/run_backend_tests.sh` (50/50, 4 nye tester). `tsc
|
||
--noEmit` rent + 45/45 vitest. Egen scratch-database + scratch
|
||
`teecup_api`-container (port 18000, live-mountet kode for rask
|
||
iterasjon) + lokal `next dev` (port 13000): to fulle scenarioer
|
||
(frittstående + org, hver med to spillere, ett lap 1- og ett lap
|
||
2-flagg med on-green-avstand) sådd via `tests/conftest.py` sine
|
||
hjelpefunksjoner. Bekreftet i ekte nettleser: bryter av/på for begge
|
||
hjem, korrekt "kun eget flagg"-banner, lap 2-merking, popup-innhold
|
||
(navn/hull/runde/avstand), tilskuer-siden viser begge flagg uten
|
||
innlogging når bryteren er på. Lys+mørk bekreftet. Samme `Referer`-
|
||
header-omgåelse for Mapbox sin URL-restrikterte token som tidligere
|
||
(se punkt 51). Scratch-stacken (db/rolle/container/MinIO-bucket)
|
||
fullstendig revet ned -- ekte `teecup_db`/`teecup_api`/
|
||
`teecup_frontend` urørt.
|
||
|
||
**Rullet ut 2026-08-14** -- migrasjon 071 kjørt mot ekte `teecup_db`
|
||
som `teeoff_admin`, etter eksplisitt bekreftelse fra bruker. Begge
|
||
nye kolonner verifisert direkte i skjemaet. `docker compose build
|
||
teecup_api teecup_frontend && up -d` -- begge containere startet
|
||
rent. Del C (Eclectic) gjenstår.
|
||
|
||
84. **Eclectic-format for org-individuelle turneringer "Del C" (ADR-068)
|
||
+ tre ikke-relaterte UI-rettelser — 2026-08-14.** Siste del av den
|
||
tredelte utvidelsen (se punkt 82/83, ADR-066/067 for Del A/B).
|
||
Eclectic: spillerens beste resultat PER HULL på tvers av ALLE
|
||
turneringens runder, summert -- brutto/netto/Stableford-varianter.
|
||
Krever samme bane for alle runder (avvises tydelig ved
|
||
rundeopprettelse ellers, ADR-019-filosofi).
|
||
|
||
Migrasjon 072: kun tre nye verdier i
|
||
`tournament_scoring_method_check` -- ingen nye tabeller/kolonner,
|
||
Eclectic regnes ut ved lesing fra eksisterende `tournament_round_
|
||
hole`/`course_handicap`. Motor: ny `eclectic_best_per_hole()` i
|
||
handicap_engine.py, 5 nye tester (inkl. ett scenario som beviser
|
||
at beste-per-hull faktisk plukkes hullvis, ikke "beste hele
|
||
runde") -- full motor-suite 127/127.
|
||
|
||
**Bevisst avvik fra opprinnelig plan:** planla først et helt nytt
|
||
leseendepunkt, men fant at Eclectic passer renere inn som en ny
|
||
gren i den ALLEREDE eksisterende `_compute_individual_standings`
|
||
(samme funksjon bak `individual_leaderboard`, brukt av alle andre
|
||
scoring_method-verdier) -- INGEN nytt endepunkt bygget, frontend
|
||
gjenbruker det samme kallet. 5 nye pytest-tester -- full
|
||
backend-suite 55/55.
|
||
|
||
Frontend (individual-tournament-detail.tsx, håndkodet -- filens
|
||
egen konvensjon): ny utvidbar "vis hull-for-hull"-rad per deltaker
|
||
i leaderboardet (`EclecticHoleTable`), viser hull/par/verdi/
|
||
kilde-runde -- beviser visuelt at totalen er satt sammen på tvers
|
||
av runder.
|
||
|
||
**Verifisert:** `python3 -m py_compile` + full
|
||
`./scripts/run_backend_tests.sh` (55/55) + standalone
|
||
`test_handicap_engine.py` (127/127). `tsc --noEmit` rent + 45/45
|
||
vitest. Egen scratch-database + scratch `teecup_api`-container
|
||
(port 18001, live-mountet kode) + lokal `next dev` (port 13001):
|
||
to runder samme bane, hull-for-hull-forhåndsberegnet total (7)
|
||
stemte nøyaktig med visningen, samme-bane-avvisningen ga tydelig
|
||
feilmelding ved forsøk på annen bane. Lys+mørk bekreftet.
|
||
Scratch-stacken revet ned -- ekte `teecup_db`/`teecup_api`/
|
||
`teecup_frontend` urørt.
|
||
|
||
**Samme runde, tre små UI-rettelser** (brukerrapportert via
|
||
skjermbilder, ikke Eclectic-relatert): avstandsindikatoren
|
||
(`target-distance.tsx`) rettet fra "grønn"/"Midt" til de riktige
|
||
golf-uttrykkene "green"/"senter", "Oppdateres live"-badgen (og det
|
||
nå ubrukte `isLive`-sporet) fjernet helt. "Antall hull"-bryteren i
|
||
Ny runde-veiviseren (`Segmented`-primitiven) hadde en avvikende
|
||
hvit valgt-stil sammenlignet med resten av samme skjerm -- rettet
|
||
til samme grønne aksent-stil som `ChoiceCard`/`ToggleButton`,
|
||
gjelder alle `Segmented`-instanser appen-vidt.
|
||
|
||
**Rullet ut 2026-08-14** -- migrasjon 072 kjørt mot ekte `teecup_db`
|
||
som `teeoff_admin`, etter eksplisitt bekreftelse fra bruker.
|
||
`docker compose build teecup_api teecup_frontend && up -d` -- begge
|
||
containere startet rent (ruller også ut de tre UI-rettelsene).
|
||
Dette var siste del av den tredelte Flaggturnering-utvidelsen --
|
||
Del A/B/C alle bygget OG rullet ut.
|
||
|
||
85. **Offline kommentar-/bildeposting i rundefeeden (ADR-069) —
|
||
2026-08-14.** Bruker spurte om installasjonsbannerets "virker
|
||
delvis uten nett"-påstand faktisk stemte -- undersøkelse bekreftet
|
||
at den gjorde det, men bruker ønsket å tette det største gjenværende
|
||
gapet: kun hull-scoreføring hadde offline-støtte, kommentarer/bilder
|
||
krevde fortsatt nett. Valgte dette fremfor to andre foreslåtte
|
||
retninger (proaktiv full-runde-precaching, generelt app-skall for
|
||
aldri-besøkt kaldstart).
|
||
|
||
Migrasjon 073: `round_message.client_message_id` (nullbar uuid) +
|
||
delvis unik indeks -- IKKE en ny funksjon i seg selv, men
|
||
idempotens. Ulikt hull-score-køens PATCH+expected_version (naturlig
|
||
trygg å gjenta), er `POST /messages` IKKE idempotent -- en avbrutt
|
||
synk kunne skrevet samme kommentar to ganger uten dette. Klienten
|
||
sender en `crypto.randomUUID()`-generert id ved både første forsøk
|
||
OG et evt. køet gjenforsøk; et gjentatt kall med samme id returnerer
|
||
den allerede opprettede meldingen fremfor å duplisere. 3 nye
|
||
backend-tester -- full suite 58/58.
|
||
|
||
`offline-queue.ts` utvidet med et `isMultipart`-flagg -- `body` blir
|
||
da et flatt string/Blob-objekt (IndexedDB lagrer Blob/File nativt)
|
||
i stedet for JSON, og `flushQueue` bygger `FormData` ved synk.
|
||
`round-messages.tsx` fikk sin EGEN, isolerte kø (`matchId:
|
||
\`${roundId}:messages\``, ikke bare `roundId`) -- blandes aldri med
|
||
round-detail.tsx sin hull-score-kø selv om begge er montert
|
||
samtidig. Optimistisk "Venter på synk"-kort med egen bilde-object-
|
||
URL (ikke composerens, som revokes med det samme).
|
||
|
||
**Verifisert:** full `./scripts/run_backend_tests.sh` (58/58,
|
||
migrasjon 073 inkludert). `tsc --noEmit` rent + 45/45 vitest. Egen
|
||
scratch-database + scratch `teecup_api`-container (port 18002,
|
||
live-mountet kode) + lokal `next dev` (port 13002), ekte
|
||
nettverk-frakoblet-emulering: tekst-only kommentar offline → synk →
|
||
nøyaktig 1 rad i databasen (ingen duplikat); samme med ekte
|
||
opplastet bilde (AVIF-konvertert av MinIO etter synk); reload-mens-
|
||
offline-scenario (postet offline, lastet siden på nytt MENS
|
||
fortsatt offline, satt online) -- den køede skrivingen ble funnet
|
||
og synket automatisk ved mount, nøyaktig 3 rader totalt (ingen
|
||
duplikat på tvers av en reload). Lys+mørk bekreftet. Scratch-stacken
|
||
fullstendig revet ned -- ekte `teecup_db`/`teecup_api`/
|
||
`teecup_frontend` urørt.
|
||
|
||
**Rullet ut 2026-08-14** -- migrasjon 073 kjørt mot ekte `teecup_db`
|
||
som `teeoff_admin`, etter eksplisitt bekreftelse fra bruker.
|
||
`docker compose build teecup_api teecup_frontend && up -d` -- begge
|
||
containere startet rent.
|
||
|
||
86. **Utvidet spillerskjema i "Deltakere" (ADR-070) — 2026-08-14.**
|
||
Bruker viste skjermbilde av "Deltakere"-flyten i en org-individuell
|
||
turnering: søkefeltet tilbød KUN "Opprett ny spiller: «X»", ingen
|
||
måte å velge en eksisterende spiller i org-poolen. Ba samtidig om at
|
||
nye spillere skal fange Fornavn, Etternavn, Kjønn, Fødselsdato,
|
||
E-post, Medlemsnummer i hjemmeklubb, Hjemmeklubb, Land, Hcp, Betalt,
|
||
Kommentar.
|
||
|
||
Rotårsak for "kan ikke velge eksisterende": `AddParticipantControl`
|
||
sin `matches`-liste ble kun utledet ved ikke-tomt søk -- ingen måte å
|
||
bla i poolen uten å kjenne et treffende navn fra før. Fikset med et
|
||
`available`-memo (pool minus allerede lagt til) som `matches` faller
|
||
tilbake til ved tomt søk.
|
||
|
||
Migrasjon 074: 7 av 11 ønskede felt fantes allerede på `player`
|
||
(migrasjon 007), kun `first_name`/`last_name`/`paid`/`comment` var
|
||
reelt nye. Samme for-/etternavn-splitt-mønster som migrasjon 052
|
||
(`display_name` uendret som det faktisk viste navnet, `first_name`/
|
||
`last_name` en ny valgfri kilde ved siden av) -- men her beregner
|
||
FRONTEND `display_name` før sending i stedet for at backend synker
|
||
det, for å unngå å røre `create_player`/`update_player` sin
|
||
eksisterende generiske PATCH-kontrakt. Backfill av eksisterende
|
||
spillere med samme heuristikk som migrasjon 052.
|
||
|
||
Frontend: progressivt avslørt skjema i `AddParticipantControl`
|
||
("+ Flere detaljer"), `splitName()`-hjelper forhåndsutfyller Fornavn/
|
||
Etternavn fra søkefeltets frittekst ved "Opprett ny spiller".
|
||
|
||
**Fant og fikset underveis (selvfunnet, ikke brukerrapportert):**
|
||
misvisende tom-tilstand-tekst når alle pool-spillere allerede var
|
||
lagt til turneringen ("ingen spillere i org ennå" -- faktisk feil,
|
||
de var bare allerede lagt til). Splittet i riktige meldinger for de
|
||
tre reelle tilstandene.
|
||
|
||
**Verifisert:** `python3 -m py_compile` + full
|
||
`./scripts/run_backend_tests.sh` (58/58, migrasjon 074 inkludert).
|
||
`tsc --noEmit` rent + 45/45 vitest. Egen scratch-database + scratch
|
||
`teecup_api`-container (live-mountet kode) + lokal `next dev`: bla-i-
|
||
eksisterende-fikset bekreftet med en forhåndsopprettet spiller
|
||
synlig direkte ved tomt søk, alle 11 felt bekreftet lagret korrekt
|
||
for en ny spiller opprettet via det utvidede skjemaet. Lys+mørk
|
||
bekreftet inkl. avkrysningsboks-styling. Scratch-stacken fullstendig
|
||
revet ned -- ekte `teecup_db`/`teecup_api`/`teecup_frontend` urørt.
|
||
|
||
**Verifiseringsfallgruve, ikke app-bug:** Chrome DevTools MCP sin
|
||
`fill`-verktøy satte visuelt riktig verdi på et natively multi-
|
||
segment `<input type="date">`, men trigget ikke Reacts `onChange`
|
||
pålitelig -- `birth_date` lagret som `null` til tross for korrekt
|
||
visning rett før innsending. Bekreftet ved tastatur-drevet
|
||
`press_key` inn i dato-feltets spinbuttons i stedet -- lagret
|
||
korrekt. Kun et automatiseringsverktøy-kvirk med native dato-inputs,
|
||
ingen kodeendring nødvendig.
|
||
|
||
**Rullet ut 2026-08-14** -- migrasjon 074 kjørt mot ekte `teecup_db`
|
||
som `teeoff_admin` (4 eksisterende spillere backfillet), etter
|
||
eksplisitt bekreftelse fra bruker. `docker compose build teecup_api
|
||
teecup_frontend && up -d` -- begge containere startet rent.
|
||
|
||
**Tillegg samme dag:** flagget den strukturelt identiske "bla i
|
||
eksisterende"-begrensningen i lag-turneringers roster-tillegg
|
||
(`tournament-detail.tsx`) som bevisst utenfor omfang -- bruker ba om
|
||
samme fiks der. Samme "available"-fallback-mønster lagt til
|
||
`AddPlayerControl` (spillere på DET ANDRE laget forblir synlige,
|
||
nedgradert/deaktivert -- kun de på DETTE laget ekskluderes). Ren
|
||
frontend-endring, ingen migrasjon. `tsc --noEmit` rent + 45/45
|
||
vitest. Scratch-database + scratch `teecup_api` (port 18004) +
|
||
lokal `next dev` (port 13004): to lag, tre poolspillere (én allerede
|
||
rostret på det andre laget) -- bekreftet browsing, søk-innsnevring
|
||
og eksisterende-spiller-tillegg alle fungerer, lys+mørk. Ingen
|
||
migrasjon å rulle ut. **Rullet ut 2026-08-14** -- `docker compose
|
||
build teecup_frontend && up -d`, etter eksplisitt bekreftelse fra
|
||
bruker. Containeren startet rent.
|
||
|
||
87. **Full statistikkdybde i org-turneringers hull-scoring, "Steg 1" av
|
||
spillerens per-hull-historikk (ADR-071) — 2026-08-15.** Bruker ba om
|
||
å se full statistikk basert på ALLE tidligere ganger en spiller har
|
||
spilt et gitt hull. `tournament_round_hole` har aldri hatt noe utover
|
||
`gross_strokes` (uendret siden migrasjon 040) -- historikk-funksjonen
|
||
kan derfor kun bli "full" for turneringssiden hvis denne datadybden
|
||
bygges først. Bekreftet med bruker: begge datakilder (frittstående +
|
||
turnering) skal telle med, med full dybde i begge.
|
||
|
||
Migrasjon 075: `tournament_round_hole` fikk samme nye felt som
|
||
`round_hole` (putts, klubb, utslag-/innspillretning, chip/bunker/
|
||
straffe-/anywayslag, puttavstand-bøtte) pluss en NY `version`-kolonne
|
||
(optimistisk lås -- endepunktet var til nå rent siste-skriver-vinner).
|
||
`tournament_participant` fikk `stat_level` (samme tre nivåer som
|
||
`round_participant`), lagt PÅ TURNERING-NIVÅ (ikke per runde) siden en
|
||
deltaker normalt spiller flere runder i samme turnering. Trygg
|
||
default (strokes_only) -- ingen eksisterende turnering endrer
|
||
oppførsel.
|
||
|
||
Backend (`individual_tournaments.py`): `HoleUpdate`/`RoundHoleOut`
|
||
utvidet feltnavn-for-feltnavn etter `rounds.py`. `update_hole` sin
|
||
upsert fikk en versjonssjekk på DO UPDATE-grenen -- MERK reell
|
||
forskjell fra `round_hole`: raden opprettes først ved FØRSTE score
|
||
(ikke ved rundestart som `round_hole`), så INSERT-grenen er
|
||
ubetinget -- en fersk innsending 409-blokkeres aldri av en
|
||
`expected_version` som ennå ikke gir mening. `TournamentParticipant*`
|
||
+ `RoundParticipantOut` fikk `stat_level` som ren whitelist-
|
||
tilføyelse, samme mønster som migrasjon 074.
|
||
|
||
Frontend: `NumberPicker`/`ChoiceRow`/`DirectionCross`/`Stepper`/
|
||
`WizardSection` trukket ut av `round-detail.tsx` til ny delt fil
|
||
`hole-stat-inputs.tsx` (ren mekanisk utrekking, ingen atferdsendring
|
||
for `ScoringWizard`) -- begge scoringsflytene trenger nå identiske
|
||
trykkbaserte inputs. Nytt `HoleStatsSheet` i
|
||
`individual-tournament-detail.tsx`: ETT skjermbilde (ikke en
|
||
flerstegs-veiviser -- HoleGrid har allerede valgt ett hull for én
|
||
deltaker). `HoleGrid` sin opprinnelige inline-tallfelt-redigering er
|
||
BEVISST UENDRET for `strokes_only`-deltakere. `stat_level` redigeres
|
||
via ny `<select>` i deltakerlisten under Oppsett.
|
||
|
||
**Bevisst avgrenset:** `ownBagClubs` sendes tom -- "din egen
|
||
kølle-bag" krever å vite om scoreren ER spilleren selv, som denne
|
||
filen ikke allerede sporer. `ClubPicker` faller tilbake til fritekst
|
||
uten den, en utelatt nicety, ikke en mangel.
|
||
|
||
**Verifisert:** `python3 -m py_compile` + full
|
||
`./scripts/run_backend_tests.sh` (64/64, 6 nye tester i
|
||
`test_tournament_hole_stats.py`). `tsc --noEmit` rent + 45/45 vitest.
|
||
Scratch-database + scratch `teecup_api` (port 18005, live-montert
|
||
kode) + lokal `next dev` (port 13005): to deltakere (strokes_only og
|
||
full) i samme runde -- HoleStatsSheet åpner/lagrer/gjenåpner med
|
||
forhåndsutfylte verdier korrekt for full-deltakeren, strokes_only-
|
||
deltakerens opprinnelige inline-felt uendret (ingen regresjon).
|
||
Lys+mørk bekreftet. Scratch-stacken fullstendig revet ned -- ekte
|
||
`teecup_db`/`teecup_api`/`teecup_frontend` urørt.
|
||
|
||
**Neste steg (Steg 2, egen ADR):** selve historikk-aggregeringen på
|
||
tvers av alle spilte runder/turneringer.
|
||
|
||
**Rullet ut 2026-08-15** -- migrasjon 075 kjørt mot ekte `teecup_db`
|
||
som `teeoff_admin`, etter eksplisitt bekreftelse fra bruker.
|
||
`docker compose build teecup_api teecup_frontend && up -d` -- begge
|
||
containere startet rent.
|
||
|
||
88. **Spillerens per-hull-historikk på tvers av alle runder/turneringer,
|
||
"Steg 2" (ADR-072) — 2026-08-15.** Selve historikk-funksjonen bruker
|
||
opprinnelig ba om (se #87/ADR-071 for statistikkdybde-forutsetningen).
|
||
Bekreftet med bruker: custom baner ekskluderes helt, ordinære
|
||
(teeoff-/GolfAPI-importerte) baner telles med fra BÅDE frittstående
|
||
runder og org-turneringer, slått sammen.
|
||
|
||
Ny delt modul `app/hole_history.py` -- kjernen er bane-broen mellom
|
||
to identitetssystemer: teeoff (`round.teeoff_facility_slug`+
|
||
`teeoff_course_id` mot `course.external_course_ref = "facility:
|
||
course_id"` når `source='official'`, eksakt samme format som
|
||
`import_official_course()` bygger) og GolfAPI (`personal_course.
|
||
external_golfapi_course_id` mot samme rå ID i `course.external_
|
||
course_ref` når `source='international'`). Custom gir bevisst `None`
|
||
fra begge resolve-funksjonene -- historikk-panelet skjules stille.
|
||
|
||
Turneringssiden må håndtere at spilleren kan ha spilt i FLERE
|
||
organisasjoner -- `tournament_round_hole` er org-scopet/RLS-
|
||
beskyttet. Løst med samme N+1-per-org-mønster som `/auth/me`
|
||
allerede bruker: `player_organizations_for_user()` (migrasjon 015,
|
||
smal SECURITY DEFINER-bro) gir org-listen trygt, ett `org_
|
||
connection()`-kall per org deretter. Ingen bypass-RLS-snarvei.
|
||
|
||
GIR/fairway-formlene speiler den etablerte `score - putts <= par -
|
||
2` (samme presisering som ADR-071, IKKE skjema-kommentarens avvikende
|
||
formel). `summarize_hole_history()` er en ren funksjon -- regner
|
||
GIR%/fairway%/snitt-putter kun over instanser MED faktisk registrert
|
||
data, sorterer mest-nylig-først.
|
||
|
||
To tynne endepunkt (ett i `rounds.py`, ett i `individual_
|
||
tournaments.py`) kaller begge inn i samme `hole_history_for_user()`
|
||
og returnerer SAMME kombinerte historikk uansett hvilken side
|
||
spørringen kom fra. Historikken er for DELTAKEREN, ikke nødvendigvis
|
||
innlogget bruker (samme tilgang som selve scoringen) -- gjester
|
||
(`user_id IS NULL`) gir `None`, samme kontrakt som en custom bane.
|
||
|
||
Frontend: nytt `HoleHistoryPanel` i `hole-stat-inputs.tsx` (henter
|
||
selv, viser ingenting ved `null`-respons). Ekspanderbar: lukket
|
||
viser ett sammendrag, åpen viser hver enkeltinstans. Lagt til i
|
||
`ScoringWizard` (frittstående) og det nye `HoleStatsSheet`
|
||
(turnering, ADR-071) -- samme komponent, ulik URL.
|
||
|
||
**Verifisert:** `python3 -m py_compile` + full
|
||
`./scripts/run_backend_tests.sh` (73/73 -- 12 nye tester i
|
||
`test_hole_history.py`: 6 rene enhetstester av aggregeringsformlene,
|
||
pluss integrasjonstester som BEVISER selve bane-broen -- samme
|
||
spiller/samme teeoff-bane spilt frittstående OG i en turnering i en
|
||
ANNEN organisasjon, kombinert historikk viser begge; custom bane og
|
||
gjeste-deltaker gir korrekt `None`). `tsc --noEmit` rent + 45/45
|
||
vitest. Scratch-database + scratch `teecup_api` (port 18006, live-
|
||
montert kode) + lokal `next dev` (port 13006): samme spiller/samme
|
||
teeoff-bane, ett frittstående hull + ett turneringshull i en ANNEN
|
||
org -- bekreftet BEGGE kontekster viser identisk, håndregnet-korrekt
|
||
kombinert historikk ("2 ganger, 1 frittstående/1 turnering, snitt 4.5
|
||
slag, GIR 50%, 1.5 putter"), ekspandert instansliste riktig merket,
|
||
et uspilt hull viste ingen panel. Lys+mørk bekreftet. Scratch-stacken
|
||
fullstendig revet ned -- ekte `teecup_db`/`teecup_api`/
|
||
`teecup_frontend` urørt.
|
||
|
||
**Ingen migrasjon** -- rent lesefunksjon oppå migrasjon 075 sitt
|
||
skjema.
|
||
|
||
**Rullet ut 2026-08-15** -- ingen migrasjon, `docker compose build
|
||
teecup_api teecup_frontend && up -d` etter eksplisitt bekreftelse fra
|
||
bruker. Begge containere startet rent.
|
||
|
||
89. **Fiks: eierens statistikknivå (og utslag ved endring) ble ikke
|
||
lagret i "Ny runde"-veiviseren (ADR-073) — 2026-08-15.** Bruker
|
||
rapporterte at de "til stadighet må" sette utslag/statistikktype på
|
||
nytt, og at "det må ha blitt borte i prosessen, for det fungerte
|
||
tidligere" -- vedla en skjermopptak-video som viste to symptomer.
|
||
|
||
Rotårsak: eierens spillerobjekt i veiviserens `state.players[]` har
|
||
EGNE `teeId`/`statLevel`-felt, atskilt fra `state.teeId`/`state.
|
||
statLevel` (de faktiske feltene steg 1/steg 2 redigerer) -- kun
|
||
synkronisert ÉN gang, da banen først velges. For utslag var
|
||
konsekvensen rent kosmetisk (steg 3 viste et gammelt utslag, men
|
||
selve innsendingen leste `state.teeId` direkte for eieren) -- for
|
||
statistikknivå var det en REELL datafeil: `submit()` leser eierens
|
||
`stat_level` fra spillerobjektet (hardkodet `"score"` fra `/auth/me`-
|
||
lastingen), ALDRI fra `state.statLevel` -- "Statistikk for deg
|
||
selv"-valget i steg 2 var dermed virkningsløst for eieren, kun
|
||
default for NYE medspillere lagt til senere.
|
||
|
||
Fiks: én samlet `useEffect` i `wizard-context.tsx` som holder
|
||
eierens spillerobjekt kontinuerlig i synk med `state.teeId`/`state.
|
||
statLevel`, uansett hvor mange ganger de endres eller fra hvilket
|
||
steg -- erstatter den tidligere punktvise engangs-synken i
|
||
`pickCourse()` (fjernet, nå overflødig).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest (ingen eksisterende
|
||
testinfrastruktur for `ny-runde/`-veiviseren i dette repoet -- ingen
|
||
ny automatisert test lagt til, browserverifisering ansett
|
||
tilstrekkelig for en ren tilstandssynk-fiks). Scratch-database +
|
||
scratch `teecup_api` (port 18007, live-montert kode) + lokal `next
|
||
dev` (port 13007) med en egen bane med to utslag: gjenskapte
|
||
videoens eksakte scenario (bytt utslag etter førstevalg, velg "All
|
||
statistikk") -- steg 3 viste nå korrekt utslag+nivå, OG bekreftet
|
||
direkte i databasen at både `round.tee_name_snapshot` og
|
||
`round_participant.stat_level='full'` faktisk ble lagret (ikke bare
|
||
riktig i visningen) -- scoreregistreringen auto-hoppet deretter
|
||
videre til Putter-steget, kun mulig for `full`. Lys+mørk bekreftet.
|
||
Scratch-stacken fullstendig revet ned -- ekte `teecup_db`/
|
||
`teecup_api`/`teecup_frontend` urørt.
|
||
|
||
**Ingen migrasjon** -- ren frontend-tilstandssynk-fiks.
|
||
|
||
**Rullet ut 2026-08-15** -- ingen migrasjon, `docker compose build
|
||
teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker.
|
||
Containeren startet rent.
|
||
|
||
90. **Order of Merit: lag-leaderboard + eclectic-total-bug fikset
|
||
(ADR-074) — 2026-08-15.** Går videre med de gjenstående OOM-punktene
|
||
fra ADR-043 (2026-08-04): lag-OOM sin leaderboard, eclectic-
|
||
aggregering på tvers av turneringer, og (nytt oppdaget) offentlig
|
||
visning. Denne runden: bug-fiks + lag-OOM. Se ADR-075/076 for de to
|
||
andre.
|
||
|
||
Bifunn fikset FØRST: `_contribution_for_entry` leste alltid rå
|
||
gross/net/stableford_total for en lenket turnering, ALDRI
|
||
`eclectic_total` -- en klubb som lenket en eclectic-scoret turnering
|
||
(ADR-068, bygget etter OOM selv) inn i en stableford/brutto/netto-OOM
|
||
fikk stille feil tall (rå slagsum i stedet for turneringens faktiske
|
||
"drømmerunde"-resultat). Fikset ved å gi funksjonen tilgang til
|
||
scoring_method.
|
||
|
||
Lag-OOM: ny CRUD for `order_of_merit_team`/`_team_member` (tabellene
|
||
fantes fra migrasjon 055, ingen ny migrasjon). Leaderboard-grenen for
|
||
`kind='team'` regner ut hvert medlems individuelle OOM-sluttresultat,
|
||
slår dem sammen med SAMME `order_of_merit_aggregate`-kall gjenbrukt
|
||
på lagnivå -- bevisst: ett lagret aggregeringsvalg styrer begge
|
||
nivåer (count_best_n=1 betyr "beste turnering per spiller" OG "beste
|
||
medlem per lag" samtidig, bekreftet eksplisitt i test). Frontend:
|
||
fjernet "ikke bygget ennå"-plassholderen -- den eksisterende
|
||
leaderboard-tabellen var allerede generisk nok til å vise lag
|
||
uendret. Ny "Lag"-administrasjonsseksjon lagt til.
|
||
|
||
**Verifisert:** full `./scripts/run_backend_tests.sh` (77/77 -- 5 nye
|
||
tester, inkl. håndregnet lag-sum 162+198=360 og en eksplisitt test av
|
||
to-nivås count_best_n-anvendelsen). `tsc --noEmit` rent + 45/45
|
||
vitest. Scratch-database + scratch `teecup_api` (port 18008, live-
|
||
montert kode) + lokal `next dev` (port 13008): opprettet ekte lag-OOM
|
||
i nettleseren, lenket to turneringer, la til to medlemmer ett om
|
||
gangen -- leaderboardet oppdaterte seg live til riktig verdi ved hver
|
||
endring. Lys+mørk bekreftet. Scratch-stacken fullstendig revet ned --
|
||
ekte `teecup_db`/`teecup_api`/`teecup_frontend` urørt.
|
||
|
||
**Ingen migrasjon.**
|
||
|
||
**Rullet ut 2026-08-15** -- ingen migrasjon, `docker compose build
|
||
teecup_api teecup_frontend && up -d` etter eksplisitt bekreftelse fra
|
||
bruker. Begge containere startet rent.
|
||
|
||
91. **Order of Merit: eclectic-aggregering på tvers av lenkede
|
||
turneringer (ADR-075) — 2026-08-15.** Tredje av de gjenstående OOM-
|
||
punktene fra ADR-043/074 -- sesong-"drømmerunde": beste resultat PER
|
||
HULL på tvers av ALLE lenkede turneringer (kun `result_type IN
|
||
('stableford','gross','net')`), tidligere eksplisitt avvist i
|
||
leaderboard-endepunktet.
|
||
|
||
Samme-bane-håndheving lagt til (ny, ingen migrasjon): en eclectic-
|
||
OOM kan ikke lenke en turnering på en annen bane enn de allerede
|
||
lenkede, verken ved lenking eller ved å bytte `aggregation_mode` til
|
||
eclectic med ulike baner allerede lenket. Datainnsamlingen (ny
|
||
`_compute_eclectic_oom_values`) gjenbruker `eclectic_best_per_hole()`
|
||
fra `handicap_engine.py` HELT UENDRET -- samme motor som ADR-068
|
||
(flere runder i én turnering), kun datainnsamlingen foran er
|
||
tilpasset til å samle på tvers av turneringer i stedet, keyed på
|
||
`player_id` med en sammensatt (turnering-id, runde-sekvens)-kilde-
|
||
indeks. Frontend: "Eclectic" lagt til som tredje aggregeringsvalg
|
||
både i opprettelsesskjemaet (`order-of-merit-list.tsx`, med auto-
|
||
reset til Sum hvis kind/resultattype endres til noe eclectic ikke
|
||
støtter) og innstillinger for eksisterende OOM-er (`order-of-merit-
|
||
detail.tsx`, ingen reset nødvendig der siden kind/resultattype er
|
||
låst etter opprettelse) -- begge gated identisk til (og speilende)
|
||
serverens regel.
|
||
|
||
**Verifisert:** full `./scripts/run_backend_tests.sh` (80/80 -- 3 nye
|
||
tester: cross-tournament eclectic velger faktisk beste-per-hull på
|
||
TVERS av turneringer med hånd-utregnet forventet verdi, avvisning
|
||
ved lenking på annen bane, avvisning ved modus-bytte med ulike baner
|
||
allerede lenket). `tsc --noEmit` rent + 45/45 vitest. Scratch-
|
||
database + scratch `teecup_api` (port 18102, live-montert kode,
|
||
`TEECUP_DEV_LOG_MAGIC_LINKS=true` for innlogging uten SMTP) + lokal
|
||
`next dev` (port 13102): opprettet ekte eclectic-OOM i nettleseren,
|
||
lenket to turneringer på samme bane -- leaderboard viste korrekt
|
||
kryss-turnering-tall (72). Forsøk på å lenke en turnering på en annen
|
||
bane ga en tydelig feilmelding i UI-et. Opprettelsesskjemaets gating
|
||
verifisert live (Eclectic dukker opp/forsvinner + nullstiller
|
||
korrekt ved endring av type/resultattype). Lys+mørk bekreftet.
|
||
Scratch-stacken fullstendig revet ned (database, rolle, container,
|
||
MinIO-bøtte, `next dev`) -- ekte `teecup_db`/`teecup_api`/
|
||
`teecup_frontend` urørt.
|
||
|
||
**Ingen migrasjon.**
|
||
|
||
**Rullet ut 2026-08-15** -- ingen migrasjon, `docker compose build
|
||
teecup_api teecup_frontend && up -d` etter eksplisitt bekreftelse fra
|
||
bruker. Begge containere startet rent.
|
||
|
||
92. **Order of Merit: offentlig/delt visning (ADR-076) — 2026-08-15.**
|
||
Siste av de tre gjenstående OOM-punktene fra ADR-043/074/075.
|
||
`order_of_merit.public_visible` fantes fra migrasjon 055, aldri brukt
|
||
til noe før nå.
|
||
|
||
Migrasjon 076: ny SECURITY DEFINER-bro `public_order_of_merit_by_id`
|
||
(fjerde i rekken etter public_org_by_slug/public_tournament_by_code/
|
||
link_player_by_email), samme NULL-for-begge-tilfeller-anti-
|
||
enumerering. Backend: ny `/public/order-of-merits/{id}`-router i
|
||
`order_of_merit.py` selv, gjenbruker `order_of_merit_leaderboard()`
|
||
direkte i stedet for å duplisere beregningen. Frontend: ny
|
||
`app/order-of-merit/[id]/page.tsx` + `components/public-order-of-
|
||
merit.tsx`, modellert på `public-club.tsx`. "Del offentlig lenke"-
|
||
knapp lagt til i innstillinger (samme kopier-lenke-mønster som
|
||
`JoinCodeChip`), gated på den faktisk lagrede `public_visible`-
|
||
verdien.
|
||
|
||
**Verifisert:** full `./scripts/run_backend_tests.sh` (83/83 -- 3 nye
|
||
tester: 404 for ikke-eksisterende id, 404 for privat OOM -- bevist
|
||
IDENTISK statuskode, reell anti-enumerering -- og 200 med korrekt
|
||
resultatliste for offentlig OOM). `tsc --noEmit` rent + 45/45 vitest.
|
||
Scratch-database (migrert fra bunnen, inkl. 076) + scratch
|
||
`teecup_api` (port 18103) + lokal `next dev` (port 13103): offentlig
|
||
side nådd i en EGEN isolert nettleser-kontekst UTEN session-cookie
|
||
(reelt inkognito-scenario) -- viste korrekt navn/resultatliste,
|
||
`generateMetadata` satte riktig fanetittel server-side. Privat OOM ga
|
||
identisk feilside som en oppdiktet id. Av-toggling av "Offentlig
|
||
synlig" fjernet lenke-knappen i UI-et OG ga umiddelbar 404 fra API-et
|
||
(bekreftet direkte). Lys+mørk bekreftet. Scratch-stacken fullstendig
|
||
revet ned -- ekte `teecup_db`/`teecup_api`/`teecup_frontend` urørt.
|
||
|
||
**Migrasjon 076 vist og bekreftet av bruker FØR kjøring mot ekte
|
||
teecup_db** (additiv -- én ny funksjon, ingen skjemaendring).
|
||
|
||
**Rullet ut 2026-08-15** -- migrasjon 076 kjørt mot ekte `teecup_db`
|
||
etter eksplisitt bekreftelse (verifisert med `\df` + et direkte NULL-
|
||
kall før kode-utrullingen), deretter `docker compose build teecup_api
|
||
teecup_frontend && up -d`. Begge containere startet rent. Bekreftet
|
||
direkte mot `teecup.golf` etterpå: `/public/order-of-merits/<ukjent>`
|
||
ga 404, `/order-of-merit/<ukjent>` ga 200 (feil-tilstanden i siden).
|
||
|
||
Med dette er alle tre punktene fra ADR-043 sin opprinnelige
|
||
"gjenstår"-liste fullført.
|
||
|
||
93. **Tilbake-navigering til en tidligere spiller i scoringsveiviseren
|
||
(ADR-077) — 2026-08-16.** Første av tre forbedringer i score-
|
||
registreringen brukeren ba om etter OOM. I samlebånd-flyten
|
||
(`ScoringWizard`, frittstående runder) var det umulig å rette opp en
|
||
feilregistrering hos en tidligere spiller uten å fullføre hele
|
||
veiviseren og lete opp riktig kort manuelt etterpå.
|
||
|
||
Kontekst-raden med alle spillerne (bevisst ikke-interaktiv ved bygging
|
||
i 2026-07-26-runden) er nå interaktiv -- trykk på en annen spiller
|
||
(inkl. "Ferdig"-markerte) bytter veiviseren dit umiddelbart. Trygt
|
||
fordi hvert felt allerede lagres for seg med en gang (PATCH per felt,
|
||
ikke batch) -- ingen datatap ved bytte. Round-only (org-turneringer
|
||
har ingen tilsvarende kjede-flyt).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest. Scratch-database +
|
||
scratch `teecup_api` (port 18104) + lokal `next dev` (port 13104): tre
|
||
spillere, byttet spiller midt i steg uten datatap, rettet en allerede
|
||
lagret score etter bytte, bekreftet "Ferdig"-markerte spillere fortsatt
|
||
er trykkbare og viser korrekt lagret data (ikke nullstilt). Lys+mørk
|
||
bekreftet. Scratch-stacken fullstendig revet ned.
|
||
|
||
**Ingen migrasjon.**
|
||
|
||
**Rullet ut 2026-08-16** -- ingen migrasjon, `docker compose build
|
||
teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker.
|
||
Containeren startet rent.
|
||
|
||
94. **Chip/Bunker/Straffeslag/Anywayslag som egen skjerm (ADR-078) —
|
||
2026-08-16.** Andre av tre forbedringer i score-registreringen. Disse
|
||
fire tellerne var klemt inn under retningsvalget for utslag/innspill i
|
||
begge scoringsflytene (frittstående runder OG org-turneringer, etter
|
||
eksplisitt "opplevelsen skal være lik uansett").
|
||
|
||
`ScoringWizard`s "details"-steg splittet i "direction"/"holeDetails".
|
||
`HoleStatsSheet` (org-turneringer) bygget om fra ÉN lang skjerm til en
|
||
tilsvarende liten intern steg-flyt -- lagringen selv er BEVISST
|
||
uendret (fortsatt én batched "Lagre" der, ikke konvertert til runde-
|
||
sidens PATCH-per-felt). Visuell polering via V0 (Claude-skrevet
|
||
prompt): de fire tellerne fikk et 2x2-rutenett av flis-kort i stedet
|
||
for en 1-kolonne-stabel med mye tomrom -- `Stepper`-komponenten (delt
|
||
av begge flyter) oppdatert ett sted, automatisk identisk begge steder.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest. To scratch-runder
|
||
(strukturell splitt, så V0-integrering): begge flyter testet
|
||
ende-til-ende gjennom alle steg, GIR-auto-inferens fortsatt virker,
|
||
lagring bekreftet i begge flyter, gjenåpning viste korrekt lagrede
|
||
verdier, 2x2-rutenettet identisk og fungerende i begge flyter.
|
||
Lys+mørk bekreftet. Scratch-stackene fullstendig revet ned.
|
||
|
||
**Ingen migrasjon.**
|
||
|
||
**Rullet ut 2026-08-16** -- ingen migrasjon, `docker compose build
|
||
teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker.
|
||
Containeren startet rent.
|
||
|
||
95. **Personlig hull-historikk ut av slagvinduet + full historikk med
|
||
grafer (ADR-079) — 2026-08-16.** Tredje og siste av tre forbedringer i
|
||
score-registreringen. Ingen backend-endring -- `app/hole_history.py`
|
||
(ADR-072) returnerte allerede alt som trengs.
|
||
|
||
Flyttet historikk-panelet ut av selve slagvinduet/-arket i begge
|
||
flyter: frittstående runder -- inn i `PlayerHoleCards`-kortet, rett
|
||
før "Avslutt for {navn}". Org-turneringer -- som et nytt FØRSTE steg
|
||
(`"overview"`) i ADR-078 sin steg-flyt, med automatisk hopp forbi når
|
||
det ikke er noe å vise (unngår en ekstra obligatorisk trykk-runde for
|
||
hver hull-registrering). Klikk åpner nå en ny delt fullskjerm-
|
||
komponent (`hole-history-detail.tsx`) i stedet for å ekspandere en
|
||
liste inline -- stat-fliser, en score-fordeling som fargede stolper
|
||
(samme `CategoryBar`-mønster/farger som `round-stats.tsx`, bekreftet
|
||
med bruker som ØNSKET graf-form), og full instansliste under.
|
||
|
||
Bevisst avvik fra planen: ingen V0-runde her (i motsetning til
|
||
ADR-078) -- brukeren hadde allerede navngitt og bekreftet det
|
||
eksakte mønsteret å gjenbruke, ingenting å utforske via V0.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent + 45/45 vitest. Scratch-database +
|
||
scratch `teecup_api` (port 18107): to frittstående runder + en
|
||
org-turnering på matchende bane-nøkkel, kombinert historikk bekreftet
|
||
i BEGGE flyter, aggregerte tall og score-fordelingsgraf bekreftet
|
||
håndregnet-korrekt, hull uten historikk bekreftet hoppet automatisk
|
||
forbi "overview"-steget. Lys+mørk bekreftet. Scratch-stacken
|
||
fullstendig revet ned.
|
||
|
||
Med dette er alle tre forbedringene i score-registreringen fullført.
|
||
|
||
**Ingen migrasjon.**
|
||
|
||
**Rullet ut 2026-08-16** -- ingen migrasjon, `docker compose build
|
||
teecup_frontend && up -d` etter eksplisitt bekreftelse fra bruker.
|
||
Containeren startet rent.
|
||
|
||
96. **Kontosammenslåing, selvbetjent (ADR-080, "Del 2" av flere
|
||
e-postadresser ADR-032) — 2026-08-17.** `request_secondary_email`
|
||
avviste tidligere en adresse som allerede eide av en ANNEN konto med
|
||
en 409 og "Ekte konto-sammenslåing støttes ikke ennå." Bekreftet med
|
||
bruker (AskUserQuestion): selvbetjening (ikke superadmin-verktøy),
|
||
hele sammenslåingen i én runde (forhåndsvisning + utførelse).
|
||
|
||
Ny migrasjon 077: `account_merge_token` (samme token-mønster som
|
||
`email_change_token`/`secondary_email_token`). Ny delt hjelpefunksjon
|
||
`resolve_user_id_by_email()` i `app/auth.py`, faktorert ut av
|
||
duplisert primær/sekundær-oppslag i `verify_magic_link`/`login_with_
|
||
password`. Ny modul `app/account_merge.py`: `compute_merge_preview()`
|
||
(les-only) + `execute_merge()` (N+1-transaksjoner -- én global
|
||
`plain_connection()` + én `org_connection(org_id)` per berørt org,
|
||
påkrevd av RLS-grensen, se ADR-080 for fullt konfliktkart). Nye
|
||
endepunkter i egen `app/routers/account_merge.py` (`/preview`,
|
||
`/request`, `GET /token/{token}`, `/confirm`). Ny frontend-seksjon
|
||
"Slå sammen med en annen konto" i `account-settings.tsx` + ny side
|
||
`app/kontosammenslaing/[token]/page.tsx`.
|
||
|
||
**Fant og fikset underveis (før produksjon, ren testsuite-fangst):**
|
||
seks RLS-beskyttede tabeller (`tournament`, `lineup_lock`,
|
||
`organization_invitation`, `message`, `message_comment`,
|
||
`tournament_round_participant_flag_plant`) + `message_reaction` var
|
||
feilaktig lagt i den globale "trygt å re-peke uten RLS"-løkken.
|
||
Krasjet (ikke stille no-op) med `invalid input syntax for type uuid:
|
||
""` -- en Postgres GUC-kvirk der `current_setting('app.current_org',
|
||
true)` returnerer tom streng, ikke NULL, på en pooled forbindelse som
|
||
tidligere hadde en committet `SET LOCAL` fra en annen transaksjon.
|
||
Flyttet til egen `_repoint_rls_scoped_safe_tables()`, kjørt inni riktig
|
||
`org_connection(org_id)`. Testsuite gikk fra 5 feilet/90 bestått til
|
||
95/95 bestått.
|
||
|
||
**Verifisert:** `tests/test_account_merge.py`, 12 nye tester (hver
|
||
rad i konfliktkartet + full ende-til-ende), full backend-suite 95/95.
|
||
`tsc --noEmit` rent + 45/45 vitest. Scratch-database + scratch
|
||
`teecup_api` (port 18108) + lokal `next dev` (port 13108): to ekte
|
||
kontoer med org-rolle-kollisjon (medlem→eier), org-repeking uten
|
||
kollisjon (→administrator), player/team_roster-kollisjon, felles
|
||
venn. Forhåndsvisning i kontoinnstillinger og fersk forhåndsvisning
|
||
på bekreftelsessiden (åpnet i egen, helt uautentisert nettleser-
|
||
kontekst) viste identisk, korrekt konfliktkart. Etter bekreftelse:
|
||
taperens `app_user`-rad bekreftet slettet direkte mot databasen,
|
||
taperens e-post nå keeperens sekundæradresse, org-roller korrekte,
|
||
player/team_roster-dedup korrekt, venn-selvlenke slettet. Innlogging
|
||
med taperens gamle e-post routet korrekt til keeper-kontoen (utløste
|
||
umiddelbart 2FA-oppsett, siden keeper nå er org-eier -- god indirekte
|
||
bekreftelse på at oppslaget faktisk traff riktig konto). Lys+mørk
|
||
bekreftet på bekreftelsessiden. Scratch-stacken (Docker-container,
|
||
database+rolle, MinIO-bucket, `next dev`) fullstendig revet ned --
|
||
ekte `teecup_db` urørt gjennom hele verifiseringen.
|
||
|
||
**Rullet ut 2026-08-17** -- bruker bekreftet ("Git Commit og deretter
|
||
ja"). Migrasjon 077 kjørt mot ekte `teecup_db` (additiv -- kun
|
||
tabellen `account_merge_token`, ingen eksisterende data rørt),
|
||
deretter `docker compose build teecup_api teecup_frontend && up -d`.
|
||
Rene containerlogger, `https://teecup.golf/logg-inn` bekreftet 200 OK
|
||
etterpå.
|
||
|
||
97. **Tjømes GolfAPI-koordinater erstattet med feltbefarte data (migrasjon
|
||
078) — 2026-08-17.** Bruker lastet opp en CSV (`Temp-uploads/Regneark
|
||
uten navn - Ark 2.csv`, 180 punkter) med koordinater samlet inn på
|
||
selve banen -- mer presise og mer detaljerte enn GolfAPI.io sin
|
||
automatiske cache (167 punkter, hentet 2026-08-12, se punkt 79/
|
||
ADR-064). Berører KUN `teecup_db` sin egen tredjeparts-cache
|
||
(`golfapi_course_coordinate`, course_id `0121250146602173`) -- ingen
|
||
kode endret, ingen container-restart nødvendig.
|
||
|
||
Egen research (Explore-agent) bekreftet FØRST hvor "eksisterende"
|
||
koordinater faktisk bor (IKKE teeoff -- Tjøme finnes ikke der, jf.
|
||
ADR-064) og at skjemaet allerede støtter den detaljerte per-hazard-
|
||
strukturen CSV-en beskriver. Flere reelle tolkningsspørsmål avklart
|
||
med bruker (AskUserQuestion) FØR noe ble skrevet:
|
||
- Ett "Tee"-punkt per hull (ikke to) → lagret som `tee_front` alene.
|
||
- Hull 4/13 hadde tre kandidat-par for "Forkant/bakkant høyre
|
||
fairwaybunker" (to i lat/long, ett i UTM/EPSG:25833) -- egen manuell
|
||
UTM→WGS84-konvertering (ingen `pyproj` tilgjengelig, implementert
|
||
fra Snyder-formlene) viste ~35 m avstand til nærmeste av de to
|
||
andre, for langt til å være GPS-støy. Bruker bekreftet: tre reelle,
|
||
atskilte bunkere, alle beholdt.
|
||
- "Voll" (steingjerde med gress over, tverrs over fairwayen, hull
|
||
3+12) → `rock` (samme kategori som fjellknaus).
|
||
- "Over vei"/"Over tre" (hull 7/8/16/17/18 og 17/18) → bruker
|
||
bekreftet "carry road"/"carry tree" -- mappet til eksisterende
|
||
`road`/`trees`, samme `back`/`center`-konvensjon som GolfAPIs egne
|
||
eksisterende road-punkter på hull 7/8/16/17.
|
||
- "Bjella" (hull 5+14, en fysisk bjelle spillerne ringer i for å
|
||
signalisere til gruppen bak) passet ikke noen av de 14 eksisterende
|
||
`poi_type`-verdiene -- ny verdi `landmark` lagt til (migrasjon 078,
|
||
samme mønster som `rock`/`layup` i migrasjon 067), rent informativt,
|
||
ikke en hindring (se `_HAZARD_LABELS` i `hole-target-distance.tsx`
|
||
-- viser i dag uansett kun green_bunker/fairway_bunker/water/rock,
|
||
`landmark` er dermed ikke synlig i appen ennå, samme status som
|
||
trees/marker_*/dogleg/road/tee_front/tee_back/layup allerede har).
|
||
- "Fairway" (rene referansepunkter uten hindringsbetydning, 6 stk) →
|
||
`layup` (samme bruk som Nesbyen-importen i punkt 79).
|
||
|
||
**Verifisert:** transformasjons-mappingen (Python, ren funksjon --
|
||
ikke committet, engangsskript samme mønster som Nesbyen-importen i
|
||
punkt 79) ga 180/180 rader mappet (ingen uidentifiserte navn), talt
|
||
opp per `poi_type` og kryssjekket for hånd mot CSV-ens egne
|
||
navnetellinger. `./scripts/run_backend_tests.sh` (95/95, migrasjon
|
||
078 bekreftet anvendbar). Selve slett+sett-inn-operasjonen KJØRT FØRST
|
||
mot en egen scratch-database (egen `golfapi_course`-rad seedet,
|
||
samme engangsskript kjørt der via `teecup_api`-containerens asyncpg),
|
||
verifisert der (180 rader, riktig `landmark`/UTM-konverterte
|
||
koordinater, `num_coordinates` oppdatert) FØR noe rørte ekte
|
||
`teecup_db`. Scratch-databasen droppet etterpå.
|
||
|
||
**Rullet ut 2026-08-17.** Migrasjon 078 kjørt mot ekte `teecup_db`.
|
||
Deretter kjørt mot ekte data: 167 gamle rader slettet, 180 nye satt
|
||
inn, `golfapi_course.num_coordinates` oppdatert til 180. Bekreftet
|
||
direkte mot databasen etterpå: riktig `poi_type`-fordeling, `landmark`-
|
||
punktene og de tre hull-4-bunkerne til stede med korrekte
|
||
koordinater. Ingen kodeendring -- ingen container-restart nødvendig.
|
||
|
||
98. **Rangefinder-koordinater for offisielle (TeeOff-koblede) baner
|
||
(ADR-081, migrasjon 079) — 2026-08-17.** Bruker påpekte at forrige
|
||
rundes Tjøme-koordinater havnet på Erol sin PERSONLIGE GolfAPI-bane,
|
||
ikke den offisielle TeeOff-koblede banen "Tjøme Gents" faktisk
|
||
bruker til org-turneringer -- og ba om at koordinatene også legges
|
||
inn der, OG at dette blir en generell mulighet for alle offisielle
|
||
baner fremover.
|
||
|
||
Rangefinder fantes til nå kun for GolfAPI-importerte personlige
|
||
baner -- verken org-turneringer (individuell slagspill/lag-matchplay)
|
||
eller frittstående runder på en EKTE teeoff-bane hadde noen
|
||
koordinatkilde. Bekreftet med bruker (AskUserQuestion): begge
|
||
org-turnering-scoringsflytene wires inn i samme runde, ikke faset.
|
||
|
||
Migrasjon 079: delte domener (`course_poi_type`/`location`/`side`)
|
||
for å stoppe et allerede observert vedlikeholdsproblem (067 og 078
|
||
måtte begge utvide samme dupliserte CHECK-liste), ny global tabell
|
||
`teeoff_course_coordinate` (nøkkel `course.external_course_ref`, samme
|
||
"delt cache, ikke per-org"-begrunnelse som `golfapi_course_
|
||
coordinate`). Ny delt modul `app/target_points.py` +
|
||
`resolve_match_course_key()` (tredje søster til de to eksisterende
|
||
bane-bro-funksjonene i `hole_history.py`, ADR-072) -- alle tre
|
||
kallesteder (frittstående runder, individuell org-turnering,
|
||
lag-matchplay) bruker nå samme `CourseKey`-abstraksjon. `rounds.py`
|
||
sitt eksisterende endepunkt refaktorert til samme mønster (villet
|
||
sideeffekt: frittstående runder på en ekte teeoff-bane får nå også
|
||
rangefinder). Ny skrive-vei `PUT`/`GET .../courses/{id}/coordinates`
|
||
i `courses.py` -- generell, gjenbrukbar, ikke en engangsfiks.
|
||
`HoleTargetDistance` generalisert (`roundId` -> `baseUrl`-prop) og
|
||
wiret inn i `HoleStatsSheet` (individuell slagspill) og `session-
|
||
scorecard.tsx` (lag-matchplay, samme mønster som `NassauPanel`).
|
||
|
||
**Verifisert:** `tests/test_target_points.py`, 11 nye tester (106/106
|
||
backend totalt). `tsc --noEmit` rent + 45/45 vitest. Scratch-database
|
||
+ scratch `teecup_api` + lokal `next dev`: koordinater satt via det
|
||
nye PUT-endepunktet over ekte HTTP, rangefinder bekreftet korrekt i
|
||
BEGGE org-turnering-UI-ene (riktig data på hull med koordinater,
|
||
ingen synlig rad på hull uten -- nettverksspor bekreftet nøyaktig
|
||
hvilke punkter som ble hentet). Lys+mørk bekreftet begge steder.
|
||
Regresjonssjekk: eksisterende GolfAPI-personlig-bane-rangefinder
|
||
satt opp i samme scratch-miljø, bekreftet uendret oppførsel etter
|
||
refaktoreringen. Scratch-stacken fullstendig revet ned.
|
||
|
||
**Rullet ut 2026-08-17** -- bruker bekreftet ("Git commit og rull
|
||
ut"). Migrasjon 079 kjørt mot ekte `teecup_db` (eksisterende
|
||
`golfapi_course_coordinate`-data verifisert uendret, 399 rader),
|
||
deretter `docker compose build teecup_api teecup_frontend && up -d`.
|
||
Rene containerlogger, `https://teecup.golf/logg-inn` 200 OK. Tjømes
|
||
offisielle bane fylt med de samme 180 koordinatene fra forrige runde
|
||
(samme mapping gjenbrukt, ikke re-avklart) via et engangsskript i
|
||
`teecup_api`-containeren mot det nye endepunktets tabell -- bekreftet
|
||
180/180 rader med korrekt `poi_type`-fordeling.
|
||
|
||
99. **Massimport av spillere til turneringer (CSV) + redigerbar
|
||
spillertabell (ADR-082) — 2026-08-17.** Bruker ba om en CSV-basert
|
||
vei inn i stedet for én-og-én-skjema. Bekreftet med bruker: begge
|
||
steg samlet (org-pool + påmelding til turneringen du står i), og
|
||
både lagturneringer (lagtildeling via CSV-kolonne) og individuelle
|
||
turneringer (valgfri klasse-kolonne) dekket samtidig.
|
||
|
||
Nytt endepunkt `POST /orgs/{id}/players/bulk` -- samme "match på
|
||
e-post, fyll kun tomme felt"-mønster som selvregistreringens
|
||
ADR-017 Beslutning B, utvidet til alle felt. `display_name`/`paid`
|
||
røres aldri på en eksisterende match. Selve turnering-/lag-
|
||
påmeldingen gjenbruker de eksisterende participants-/roster-
|
||
endepunktene i en frontend-løkke -- ingen ny bulk-registrerings-vei,
|
||
ingen migrasjon. Ny `papaparse`-avhengighet, CSV parses klient-side,
|
||
enkel fuzzy kolonnegjetting (norsk/engelsk), overstyrbar per
|
||
kolonne. Nytt "Importer fra CSV"-inngangspunkt i BÅDE
|
||
`tournament-detail.tsx` og `individual-tournament-detail.tsx`.
|
||
|
||
**Midlertidig hånd-kodet UI (samme rekkefølge som FlagPlantSheet):**
|
||
`player-import-panel.tsx` bygget hånd-kodet FØRST for å bevise hele
|
||
kjeden ende-til-ende, venter nå på en V0-eksport for den polerte
|
||
visningen -- selve logikken (CSV-parsing/kolonne-gjetting/API-
|
||
orkestrering) forblir uendret ved bytte, komponenten er allerede en
|
||
kontrollert "dum" visning utad.
|
||
|
||
**Verifisert:** `tests/test_players_bulk.py`, 5 nye tester (111/111
|
||
backend totalt). `tsc --noEmit` rent + 45/45 vitest.
|
||
Scratch-database + scratch `teecup_api` + lokal `next dev`: én org
|
||
med lagturnering (to lag) + individuell turnering (én klasse), CSV
|
||
med blandede rader (ny/matchende-på-e-post/ukjent-lagnavn) importert
|
||
i lagturneringen -- bekreftet direkte mot databasen at match-på-
|
||
e-post kun fyller tomme felt, ukjent lagnavn flagget uten å
|
||
blokkere. Individuell-import med klasse-kolonne bekreftet riktig
|
||
tilknytning. Lys+mørk bekreftet på importpanelet. Scratch-stacken
|
||
revet ned.
|
||
|
||
**Kjent begrensning (ikke rettet, UI-et erstattes uansett):**
|
||
kolonnegjettingen normaliserer ikke æ/ø/å -- "Kjønn" traff ikke
|
||
automatisk, måtte rettes manuelt i kolonnevalget (fungerte som
|
||
forventet, ikke blokkerende).
|
||
|
||
**Ingen migrasjon.**
|
||
|
||
**V0-eksport mottatt og integrert samme dag.** Delt i `player-
|
||
import-view.tsx` (V0, ren visning) + `player-import-panel.tsx`
|
||
(Claude, orkestrering) -- FlagPlantSheet-mønsteret. Fant og fikset
|
||
én reell integreringsfeil i scratch: V0s kjønn-nedtrekk bruker
|
||
`female`/`male`/`other`, rå CSV-tekst ("m"/"f") ble kopiert inn
|
||
uendret og ville stille tapt data ved lagring -- fikset med egen
|
||
normalisering ved kolonnetilknytningen. æøå-diakritikk-begrensningen
|
||
fra tidligere fikset samtidig. Ny scratch-runde bekreftet begge
|
||
turneringsformater med den polerte visningen, korrekt lagret
|
||
`m`/`f` i databasen, lys+mørk. Se ADR-082 for full detalj.
|
||
|
||
Underveis: bruker ga en ny STÅENDE regel (turneringsoppsett skal
|
||
designes "stor skjerm først", ikke mobil-først, siden organisatoren
|
||
sitter foran en PC) -- lagt til i CLAUDE.md, V0-prompten for denne
|
||
komponenten oppdatert og sendt på nytt før kjøring i v0.app.
|
||
|
||
**Rullet ut 2026-08-17** -- bruker bekreftet ("Git Commit og rull
|
||
ut"). `docker compose build teecup_api teecup_frontend && up -d`,
|
||
ingen migrasjon. Rene containerlogger, `https://teecup.golf/logg-
|
||
inn` 200 OK.
|
||
|
||
100. **Visuelt hull-diagram for rangefinderen (ADR-083) — 2026-08-17.**
|
||
Bruker rapporterte fra et ekte skjermbilde (produksjon) at kun ÉN
|
||
hindring vises selv om hullet har flere, og ba om at tee vises
|
||
nederst/senter green øverst -- bekreftet som ønske om et ekte
|
||
visuelt diagram med full 2D-plassering (langs + venstre/høyre), ikke
|
||
bare listerekkefølge.
|
||
|
||
To atskilte fikser: (1) `hole-target-distance.tsx` sin "kun
|
||
nærmeste"-reduksjon erstattet med en full, sortert hindringsliste
|
||
(`target-distance.tsx` fikk en `HazardList`) -- gjelder ALLE baner,
|
||
uavhengig av diagram. (2) Ny geometri-modul
|
||
`frontend/lib/hole-geometry.ts` (`projectOntoAxis`, bygget på
|
||
eksisterende `haversineMeters`/`bearingDegrees`) projiserer
|
||
hindringer+spiller ned på en FAST tee->green-akse (tee_front/
|
||
tee_back-midtpunkt, IKKE spillerens bevegelige posisjon). 10 nye
|
||
enhetstester beviste geometrien matematisk riktig FØR noe visuelt
|
||
ble bygget. Valgt ren SVG/CSS-skjematisk fremstilling fremfor et nytt
|
||
Mapbox-kart (kostnadskontroll-prinsipp fra ADR-048, konsistent
|
||
orientering uansett gangretning).
|
||
|
||
Midlertidig hånd-kodet visning (`hole-diagram-view.tsx`) bygget
|
||
først for å bevise kjeden -- venter på V0-eksport for den polerte
|
||
versjonen, samme rekkefølge som FlagPlantSheet/PlayerImportPanel.
|
||
Rendres kun når tee-geometri finnes; `target-distance.tsx` sin flate
|
||
visning (nå med full hindringsliste) forblir uendret fallback.
|
||
Gjelder automatisk alle tre rangefinder-kallesteder (frittstående
|
||
runder, individuell org-turnering, lag-matchplay).
|
||
|
||
**Verifisert:** `hole-geometry.test.ts` 10/10, full `vitest run`
|
||
55/55, `tsc --noEmit` rent. Scratch-database med ETT hull med
|
||
presist kjente syntetiske koordinater -- GPS simulert via
|
||
`initScript` (headless Chrome nekter ekte geolokasjon). ALLE viste
|
||
tall stemte eksakt med håndregnede fasitverdier (front 140/senter
|
||
150/bak 160/hindringer 54 m og 131 m). Visuelt bekreftet riktig
|
||
venstre/høyre- og langs-plassering av begge hindringene og
|
||
spilleren. Fallback bekreftet på hull uten tee-koordinater. Lys+mørk
|
||
bekreftet. Scratch-stacken revet ned.
|
||
|
||
**Ingen migrasjon** -- ren frontend, backend uendret (target-points
|
||
returnerte allerede tee_front/tee_back, bare ikke brukt før nå).
|
||
|
||
**V0-eksport mottatt og integrert samme dag.** Traff props-
|
||
kontrakten eksakt -- ingen integreringsfeil å rette denne gangen
|
||
(til forskjell fra ADR-082/spillerimportens kjønn-mismatch), `tsc`
|
||
rent på første forsøk. To ekstra kvalitetstillegg fra V0 utover det
|
||
som ble bedt om: full `aria-label`-oppsummering av hele diagrammet
|
||
for skjermlesere, og en lett kollisjons-unngåelse for hindrings-
|
||
etiketter som havner nær hverandre. Re-verifisert i scratch mot
|
||
samme kjente fixture -- identiske tall/posisjoner som den
|
||
midlertidige versjonen. Lys+mørk bekreftet.
|
||
|
||
**Rullet ut 2026-08-17** -- bruker bekreftet. `docker compose build
|
||
teecup_frontend && up -d`, ingen migrasjon. Ren containerlogg,
|
||
`https://teecup.golf/logg-inn` 200 OK.
|
||
|
||
101. **Hull-diagram v2: kategoriske akser + hazard_group (ADR-084) —
|
||
2026-08-17.** Bruker viste et ekte skjermbilde fra Tjøme hull 18:
|
||
ADR-083 sin kontinuerlige `crossMeters`-forskyvning ga for lite
|
||
synlig venstre/høyre-forskjell på ekte koordinater -- bunker
|
||
(faktisk venstre) og vann (faktisk senter) havnet begge nesten på
|
||
senterlinjen. Løsning: tre FASTE kategoriske baner
|
||
(venstre/senter/høyre) drevet direkte av `side_fairway`, ikke av
|
||
utledet `crossMeters`. Green og tee alltid senterakse (uansett egen
|
||
`side_fairway`); spiller ("Deg") også alltid senterakse, kun
|
||
langs-hull-posisjon. Carry-hindringer (front+bak-par) -> ett ikon,
|
||
forkant under/bakkant over; enkeltpunkt -> én avstand under.
|
||
|
||
Første forsøk grupperte forkant/bakkant via `poi_type`+
|
||
`side_fairway`-heuristikk -- bruker avviste eksplisitt: to atskilte
|
||
hindringer av samme type på samme side (f.eks. to venstre-bunkere)
|
||
må ALDRI slås sammen. Undersøkt: ingen eksisterende gruppe-/
|
||
hindrings-id fantes i verken `golfapi_course_coordinate` eller
|
||
`teeoff_course_coordinate`. Løst med ny migrasjon 080:
|
||
`hazard_group text` (nullable) på begge tabeller -- `NULL` (alt
|
||
eksisterende data i dag) = alltid enkeltstående, aldri slått
|
||
sammen; ikke-null = eksplisitt menneskelig bekreftet pardata, satt
|
||
ved data-inntasting (samme tillitsnivå som `poi_type`/`location`,
|
||
ADR-081). Eksponert i `app/target_points.py` og
|
||
`app/routers/courses.py` (`CoursePointIn`/`Out`,
|
||
`_COORDINATE_COLUMNS`, INSERT i `replace_course_coordinates` --
|
||
eneste skrive-vei, kun offisielle TeeOff-baner; Tjømes personlige
|
||
`golfapi_course_coordinate`-data har ingen skrive-endepunkt, kun
|
||
engangsskript).
|
||
|
||
Nye ikoner fra bruker (PNG, 32×31 sand/vann, 40×40 green) i
|
||
`frontend/public/hole-diagram/` -- IKKE de tre opplastede
|
||
landskaps-SVG-ene (fulle illustrerte banekart, 237-495 path-
|
||
elementer hver), avvist som for detaljerte til å være lesbare
|
||
nedskalert til 32-40px (CLAUDE.md sin ståenede tilgjengelighets-
|
||
regel). Typer uten dedikert ikon (i dag kun `rock`) faller tilbake
|
||
til `TriangleAlert`.
|
||
|
||
`hole-target-distance.tsx` fikk `groupDiagramHazards()` (grupperer
|
||
UTELUKKENDE på delt, ikke-null `hazard_group`) som erstatter den
|
||
gamle per-punkt-mappingen til `HoleDiagram`; den flate `hazards`-
|
||
listen til `TargetDistance`-fallback (baner uten tee-koordinater)
|
||
er uendret. `hole-diagram-view.tsx` skrevet om fra kontinuerlig
|
||
`crossToLeftPct` til `grid-cols-3` med tre uavhengige baner, hver
|
||
med egen kollisjons-forskyvning.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, `pytest`
|
||
111/111 (migrasjon 080 kjørt i scratch-testsuiten). Egen scratch-
|
||
database+rebygd scratch `teecup_api`+lokal `next dev`, presist
|
||
kjent syntetisk datasett med to ATSKILTE venstre fairway-bunkere
|
||
(én paret via `hazard_group`, én enkeltstående) -- bekreftet TO
|
||
atskilte ikoner (ikke slått sammen), riktig forkant(54m)/
|
||
bakkant(63m) på den parede, riktig enkelttall(103m) på den
|
||
enkeltstående. Vannhinder (senter, 130m) og rock (høyre, generic-
|
||
ikon, 18m) riktig plassert. Green/tee bekreftet alltid midtbane.
|
||
Lys+mørk bekreftet. Scratch-stacken revet ned.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet migrasjon 080
|
||
eksplisitt ("Jeg bekrefter"). `ALTER TABLE ... ADD COLUMN
|
||
hazard_group text` kjørt mot ekte `teecup_db` (begge tabeller),
|
||
deretter `docker compose build teecup_api teecup_frontend && up
|
||
-d` (egen bekreftelse). Rene containerlogger, `https://teecup.golf/
|
||
logg-inn` 200 OK.
|
||
|
||
**Gjenstår:** liste over `_HAZARD_CONFIG`-typer uten eget ikon
|
||
(i dag kun `rock`/"Fjellknaus") leveres til bruker. V0-prompt for
|
||
polert visning skrives etter godkjenning av den hånd-kodede
|
||
versjonen.
|
||
|
||
102. **Hull-diagram v2, retting fra ekte produksjonsbruk (ADR-084 del
|
||
2) — 2026-08-18.** Bruker viste et ekte skjermbilde av v2-
|
||
diagrammet i produksjon og meldte fem avvik: (1) hindringer med
|
||
forkant+bakkant viste to ikoner i stedet for ett -- skyldtes at
|
||
ekte Tjøme-data ennå ikke hadde `hazard_group` satt, ikke en
|
||
kode-feil. (2) Et ikon overlappet green-ikonet i midtbanen -- ekte
|
||
kode-feil, `layoutLane()` tok ikke hensyn til green-ikonets egen
|
||
plass; fikset med en reservert sone (`CENTER_TRACK_TOP`). (3-4)
|
||
Listen under diagrammet manglet forkant/senter/bakkant- og
|
||
venstre/senter/høyre-info, og viste hvert par to ganger -- skrevet
|
||
om til én rad per hindring med begge avstander på samme rad, pluss
|
||
undertekst for bane/posisjon. (5) Ikonene i listen byttet fra
|
||
universelt 20px `TriangleAlert` til type-spesifikke ikoner ved
|
||
36px.
|
||
|
||
Underveis, da hull 4 sin "bakre greenbunker" skulle pares: bruker
|
||
påpekte at den fysisk ligger BAK selve greenen og derfor må vises
|
||
OVER green-ikonet, ikke i vanlig-sonen. `alongPercent` fra
|
||
`projectOntoAxis` klippes bevisst ikke i geometrimodulen (kan bli
|
||
>100), men visningen klippet den likevel -- fikset ved å flytte
|
||
green-ikonet ned og åpne en egen sone over det
|
||
(`GREEN_TOP_PCT_PUSHED`/`BEHIND_GREEN_ZONE_*`,
|
||
`layoutBehindGreenLane` kaskaderer oppover) når minst én
|
||
midtbane-hindring faktisk ligger bak green.
|
||
|
||
Fem nye ikoner (fjellknaus/trær/dogleg/layup/landemerke, alle
|
||
32px) integrert -- disse typene var i domenet fra før men manglet
|
||
egen visning i diagrammet (fjellknaus hadde generisk ikon, resten
|
||
var usynlige der).
|
||
|
||
**Ny ekte `hazard_group`-data for Tjøme, fra kildedata, ikke
|
||
heuristikk:** bruker lastet opp klubbens eget koordinat-regneark,
|
||
som viste seg å ha en EKSPLISITT gruppenummer-kolonne som allerede
|
||
parer forkant/bakkant -- akkurat identiteten `hazard_group` (del 1)
|
||
var designet for. Kryssjekket regnearkets 38 grupper mot databasens
|
||
180 punkter via lat/long (engangs Python-script) -- løste
|
||
DEFINITIVT de to sakene forrige runde bevisst lot stå
|
||
(hull 4: tre høyre fairway-bunkere, hull 13: to) ved eliminasjon,
|
||
til tross for at 2 rader i regnearket hadde feil koordinatformat
|
||
(UTM/EPSG:25833 -- trolig kopi-lim-feil, uskadelig siden kun
|
||
gruppenummeret var nødvendig). Fant i tillegg at `rock` også har
|
||
forkant/bakkant-par på fire hull (ikke fanget opp i del 1). Bruker
|
||
bekreftet manuelt én ekstra paring uten gruppenummer i regnearket
|
||
(hull 4 sin bakre greenbunker). Resultat: 78 rader satt til 39
|
||
distinkte `hazard_group`-verdier (`{hull}-{regneark-gruppe}`,
|
||
sporbart til kilden) via en frittstående `UPDATE ... FROM
|
||
(VALUES ...)` -- datainnhold, ikke skjema, ingen ny migrasjon.
|
||
|
||
**Bevisst utsatt:** bruker lastet opp et bekk (creek)-ikon, men
|
||
domenet `course_poi_type` skiller ikke bekk fra dam (begge
|
||
`water`) -- å vise dem ulikt krever en ny domeneverdi +
|
||
reklassifisering av spesifikke punkter. Bruker ba eksplisitt om at
|
||
dette blir en EGEN runde, ikke bakt inn her.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55 (to ganger).
|
||
Ny scratch-runde med presist konstruert "bak green"-scenario --
|
||
bekreftet visuelt at bakre greenbunker rendres over green-ikonet,
|
||
normal-sonens hindringer uendret ved siden av. Lys+mørk bekreftet
|
||
begge runder. Begge scratch-stackene revet ned.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet kode og data hver for
|
||
seg. `docker compose build teecup_frontend && up -d` (kun
|
||
frontend-kode, ingen migrasjon), deretter UPDATE-en mot ekte
|
||
`teecup_db`. Rene containerlogger, `https://teecup.golf/logg-inn`
|
||
200 OK, 78/78 rader bekreftet satt (39 distinkte grupper).
|
||
|
||
**Gjenstår:** bekk som egen `poi_type` (egen runde, bedt om
|
||
eksplisitt av bruker). Ikon-mangel-lista er nå kun `road`. V0-
|
||
prompt for polert visning fortsatt ikke skrevet.
|
||
|
||
103. **Egen poi_type "creek" for bekk (ADR-084 del 3) — 2026-08-18.**
|
||
Del 2 samme dag lot bekk-ikonet ligge ukoblet -- domenet
|
||
`course_poi_type` skilte ikke bekk fra dam (begge `water`), og
|
||
bruker ba om egen runde for det. Migrasjon 081 la til `creek` i
|
||
det delte domenet (`ALTER DOMAIN ... DROP/ADD CONSTRAINT`, gjelder
|
||
automatisk begge koordinattabellene). Egen `UPDATE` (ikke
|
||
migrasjon) reklassifiserte de 8 kjente Tjøme-bekk-punktene (hull
|
||
2/3/11/12) fra `water` til `creek` -- identifisert presist via
|
||
samme koordinat-regneark+lat/long-matching som ga hazard_group-
|
||
dataen i del 2, `hazard_group` urørt. Nytt ikon
|
||
(`hazard-creek.png`) koblet inn i `_HAZARD_KIND`/`_ICON_SRC` under
|
||
egen `HazardKind`, label "Bekk". Ingen backend-endring nødvendig
|
||
(`poi_type` er `str`, ikke et strengt enum, i både
|
||
`target_points.py` og `courses.py`).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, full
|
||
`./scripts/run_backend_tests.sh` 111/111 (migrasjon 081 i scratch-
|
||
testsuitens egen database). Ny scratch-runde med konstruert bekk-
|
||
par -- bekreftet eget, distinkt ikon (ikke forvekslet med vann),
|
||
lys+mørk. Scratch-stacken revet ned.
|
||
|
||
Samtidig ryddet: flere gamle, TOMME MinIO scratch-bøtter fra
|
||
tidligere økter (`teecup-scratch-hh/hs/oom/players/roster/wz`) som
|
||
aldri ble fjernet ved forrige teardown -- bekreftet adskilt fra
|
||
ekte data (`teecup-media`) og tomme før sletting, ingen andre
|
||
glemte scratch-ressurser funnet.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet migrasjon,
|
||
reklassifisering og kode hver for seg. Kjørt mot ekte `teecup_db`,
|
||
deretter `docker compose build teecup_frontend && up -d`. Rene
|
||
containerlogger, `https://teecup.golf/logg-inn` 200 OK, 8/8 rader
|
||
bekreftet `poi_type = 'creek'`.
|
||
|
||
104. **Road-ikon, siste hull i ikon-mangel-lista (ADR-084 del 4) —
|
||
2026-08-18.** Bruker lastet opp `road.png` (32×32) -- eneste
|
||
gjenstående klassifiserte hindringstype uten eget ikon. Ren
|
||
frontend-endring: `road` fantes allerede som domeneverdi og som
|
||
ekte data (5 enkeltpunkter på Tjøme, hull 7/8/16/17/18), men var
|
||
usynlig i diagrammet siden `_HAZARD_LABELS`/`_HAZARD_KIND` ikke
|
||
inkluderte den. Lagt til med identisk mønster som de fem
|
||
foregående ikonene denne økten -- ingen migrasjon, ingen
|
||
datareklassifisering.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Ingen ny
|
||
scratch-visningsrunde -- identisk, gjentatte ganger allerede
|
||
bekreftet kodevei.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. `docker compose
|
||
build teecup_frontend && up -d`, ingen migrasjon. Rene
|
||
containerlogger, `https://teecup.golf/logg-inn` 200 OK.
|
||
Ikon-mangel-lista er nå tom.
|
||
|
||
105. **Sortering, side_fairway-diagnose, standardisert CSV-format
|
||
(ADR-084 del 5) — 2026-08-18.** Hindringslisten manglet en
|
||
eksplisitt "nærmest spilleren først"-sortering (falt tilbake til
|
||
rå API-rekkefølge) -- fikset med `sort((a,b) => a.below - b.below)`
|
||
i `groupDiagramHazards()`. Kryssjekket samtidig alle punkter i den
|
||
gamle CSV-en med eksplisitt venstre/høyre mot databasens
|
||
`side_fairway` -- fant 12 avvik (satt til `center` når kilden sa
|
||
noe annet), som bekreftet at enkeltpunkt-lapping ikke holdt.
|
||
Avtalte i stedet et standardisert kolonneformat
|
||
(`ID,HULL,TYPE,PLASS,PUNKT,LAT/LNG`) med bruker for fremtidig
|
||
reimport -- `ID` unik kun innenfor ett hull, samme prinsipp som
|
||
`hazard_group`.
|
||
|
||
106. **Full reimport av Tjøme, to nye poi_type (ADR-084 del 6) —
|
||
2026-08-18.** Bruker leverte hele 18-hulls-fila i det avtalte
|
||
formatet. Validering avdekket og avklarte seks ting: to nye
|
||
TYPE-verdier (`Fairway`/`Voll`, egne ikoner fra bruker + migrasjon
|
||
082 for `fairway`/`stone_fence`), ett par uten PUNKT løst via
|
||
brukerens regel (siste siffer i koordinaten avgjør forkant/
|
||
bakkant), én bekreftet kopier-lim-feil (hull 11), seks UTM/
|
||
EPSG:25833-korrupte rader løst med ordentlig `pyproj`-projeksjon
|
||
(konvergerte på under 1mm avvik fra forrige rundes elimineringsvis
|
||
gjettede verdier -- god kryssbekreftelse), og ett bevisst fjernet
|
||
veipunkt (hull 18).
|
||
|
||
Hull 4 sine green-/greenbunker-koordinater viste seg vesentlig
|
||
endret mellom database og ny fil -- inkludert det forrige runde
|
||
identifiserte som "bakre greenbunker" via eliminasjon, som var feil
|
||
koordinater. For risikabelt å patche inkrementelt -- kjørte i
|
||
stedet full `DELETE`+`INSERT` (180 gamle rader → 194 nye) i én
|
||
transaksjon, beregnet direkte fra kolonnene.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, full
|
||
`pytest` 111/111 (migrasjon 082). Reimport-SQL kjørt FØRST mot en
|
||
scratch-kopi av ekte Tjøme-data -- 194/39 riktig, fordeling
|
||
matchet plan eksakt. Visuell bekreftelse av `fairway`/
|
||
`stone_fence`-ikonene og sorteringsfiksen med ekte reimportert
|
||
data (hull 1/2/3), lys+mørk. Scratch-stacken revet ned.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet migrasjon, reimport
|
||
og kode hver for seg. Migrasjon 082 + reimport (DELETE 180/INSERT
|
||
194, én transaksjon) kjørt mot ekte `teecup_db`, deretter `docker
|
||
compose build teecup_frontend && up -d`. Hull 7 sitt vann
|
||
bekreftet `side_fairway='left'` etter reimport.
|
||
|
||
107. **Tjøme fantes i to atskilte tabeller -- kun én reimportert
|
||
(ADR-084 del 7) — 2026-08-18.** Bruker rapporterte at en gruppert
|
||
hindring fortsatt viste to ikoner -- viste seg at forrige rundes
|
||
reimport kun traff `golfapi_course_coordinate` (frittstående
|
||
runder). Fant én til: `teeoff_course_coordinate` for
|
||
`external_course_ref='tjome-golfklubb:140'` (den "official"
|
||
org-tilknyttede banen "Hovedbanen", brukt av turneringer/lag-
|
||
matchplay) -- fortsatt med de gamle 180 punktene, 0 hazard_group.
|
||
Ingen tredje representasjon funnet (bekreftet via `course`/
|
||
`golfapi_course`/`personal_course`).
|
||
|
||
Samme 194-rads datasett fra forrige runde satt inn i
|
||
`teeoff_course_coordinate` i stedet -- identiske kolonner/domener
|
||
siden migrasjon 079. Kjørt FØRST mot en scratch-kopi av de ekte
|
||
180 radene, 194/39 bekreftet identisk med forrige runde. Ingen
|
||
kode-endring nødvendig (samme deployerte kode leser nå riktig
|
||
data fra begge kilder).
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. Reimport-SQL (DELETE
|
||
180/INSERT 194, én transaksjon) kjørt mot ekte `teecup_db`.
|
||
Bekreftet 194/39/poi_type-fordeling identisk med forrige runde,
|
||
hull 7 sitt vann `side_fairway='left'`.
|
||
|
||
**Lærdom:** Tjøme har to helt separate koordinat-datasett (én for
|
||
frittstående runder, én for org-turneringer) -- verdt å huske ved
|
||
fremtidige datakorreksjoner, for denne eller andre baner med
|
||
begge tilkoblingstyper.
|
||
|
||
108. **Hull-valget overlevde ikke en refresh — 2026-08-18.** Bruker
|
||
rapporterte at en refresh mens man sto på f.eks. hull 7 alltid
|
||
hoppet tilbake til starthullet. Årsak: `currentHole` i
|
||
`round-detail.tsx` levde KUN i React-state (seedet fra
|
||
`round.start_hole` ved hver mount, se den eksisterende `null`- vs
|
||
`1`-forklaringen fra 2026-07-24) -- aldri i URL-en, så en full
|
||
remount (refresh) mistet valget fullstendig.
|
||
|
||
Fikset ved å gjenbruke det samme `?param`-i-URL-mønsteret siden
|
||
allerede har for fane-valget (`?tab=manage`): ny `?hole=N`-
|
||
parameter, lest ved mount (`initialHole`), holdt i sync ved
|
||
senere navigasjon via en ny felles `setHoleAndUrl()`-funksjon
|
||
(erstatter ALLE direkte `setCurrentHole`-kall utenom selve
|
||
URL-seedingen i `loadRound`) som oppdaterer BÅDE state og URL med
|
||
`router.replace` (ikke `push` -- hull-bytte skal ikke fylle
|
||
historikken med ett tilbake-steg per hull). En egen `useEffect`
|
||
speiler `?hole=`-parameteren tilbake inn i state ved eksterne
|
||
URL-endringer (nettleserens frem/tilbake).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Scratch-
|
||
runde (database+API+next dev): naviger til hull 7 (URL bekreftet
|
||
`?hole=7`), hard refresh (`ignoreCache`) -- siden viste fortsatt
|
||
hull 7, ikke hull 1. "Neste hull" bekreftet også oppdaterer URL-en
|
||
(`?hole=8`). Scratch-stacken revet ned.
|
||
|
||
**Ingen migrasjon** -- ren frontend-endring
|
||
(`frontend/components/round-detail.tsx`).
|
||
|
||
109. **Rediger organisasjonens hele spillerpool, når som helst (ADR-085)
|
||
— 2026-08-18.** "Lettere krise": bruker kunne ikke redigere spillere
|
||
i org-turneringer utenom en gammel tre-felts inline-form (navn/HCP/
|
||
kjønn). Ba om at spillerimport-tabellen (ADR-082) skal være
|
||
tilgjengelig for redigering til enhver tid.
|
||
|
||
**Kritisk funn:** `POST .../players/bulk` (import-lagringen) bruker
|
||
`COALESCE` per felt -- fyller KUN tomme felt på en eksisterende
|
||
match, rører ALDRI `display_name`/`email`/`paid`. Ubrukelig som
|
||
lagre-vei for ekte redigering (en korrigering av et allerede utfylt
|
||
navn/HCP ville stille feilet). Løsning: gjenbruker selve
|
||
`PlayerImportView`-tabellen UENDRET via en ny `mode.kind===
|
||
"roster"`, men ny orkestrering (`org-player-roster-panel.tsx`) som
|
||
laster fra `GET /orgs/{id}/players` og lagrer med ekte `PATCH`
|
||
per endret felt (kun det som faktisk er rørt) -- nye rader
|
||
oppretter med vanlig `POST`. Ingen backend-endring, ingen
|
||
migrasjon. Ny rute `/organizations/[id]/players`, lenket fra
|
||
Medlemmer-siden.
|
||
|
||
Ingen ny V0-runde -- selve tabellen er 100% uendret gjenbruk av en
|
||
allerede V0-designet komponent, kun ny data-orkestrering.
|
||
|
||
**Bevisst utenfor omfang:** ekte sletting av en spiller -- "Slett"
|
||
fjerner kun raden fra redigeringsøkten, ikke fra databasen (ingen
|
||
slette-endepunkt finnes), gjort eksplisitt i UI-teksten.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Scratch-
|
||
runde med tre eksisterende spillere -- bekreftet at en HCP-
|
||
korrigering (12.4→10.5) OG en navneendring på en allerede utfylt
|
||
spiller begge faktisk persisterte ved reload (nøyaktig det bulk-
|
||
endepunktet ikke kunne), og at en ny rad korrekt opprettet en ny
|
||
spiller. Lys+mørk bekreftet. Scratch-stacken revet ned.
|
||
|
||
**Rullet ut:** venter på bekreftelse (ren frontend, ingen
|
||
migrasjon).
|
||
|
||
110. **Slett en hel turnering, permanent (ADR-086) — 2026-08-18.**
|
||
Bruker: "Jeg mangler mulighet til å slette HELE turneringen."
|
||
Bekreftet: genuint manglende -- ingen `DELETE`-endepunkt for selve
|
||
`tournament`-raden fantes, kun for under-ressurser (som i tillegg
|
||
nekter sletting så snart de har barn).
|
||
|
||
Nytt `DELETE /orgs/{id}/tournaments/{id}` (`app/routers/
|
||
tournaments.py`) -- strengere sperre enn resten av turnerings-
|
||
oppsettet (kun org-eier/admin, `is_org_admin`, ikke bare medlemskap
|
||
som alt annet her), siden dette er kategorisk mer ødeleggende.
|
||
**Kritisk skjemafunn:** to RESTRICT-FK-er sitter MIDT INNI
|
||
kaskade-grafen (`match_participant.team_roster_id`,
|
||
`tournament_round_participant.tournament_participant_id`) -- en
|
||
naiv enkelt-setnings-kaskade kunne feilt avhengig av Postgres sin
|
||
interne rekkefølge, KUN når det faktisk fantes ekte kamper/runder.
|
||
Løst med eksplisitt sletting i riktig rekkefølge i stedet for å
|
||
stole blindt på kaskaden. Spillerne selv (`player`) røres aldri --
|
||
kun turneringens koblinger til dem.
|
||
|
||
UI: søppelbøtte-ikon i header ved siden av statusvelgeren, i begge
|
||
turneringstyper (lag- og individuell-format), kun synlig for eier/
|
||
admin. `confirm()`-bekreftelse med sterk ordlyd, samme mønster som
|
||
runde-sletting/medlem-fjerning -- ingen ny bekreftelses-konvensjon.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Fire nye
|
||
backend-tester (111→115) -- begge formater med EKTE populerte data,
|
||
bekrefter RESTRICT-diamant-scenarioet faktisk fungerer, pluss 403/
|
||
404-grensetilfeller. Full nettleser-scratch-verifisering med ekte
|
||
2FA-fullføring (TOTP-kode beregnet med `pyotp` fra oppsettskjermens
|
||
hemmelighet) -- bekreftet knapp, `confirm()`-tekst, og at ALT
|
||
(turnering/lag/roster/kamper/match_participant/hole_score/økt)
|
||
faktisk ble borte mens spillerne selv besto uendret. Scratch-
|
||
stacken revet ned.
|
||
|
||
**Rullet ut:** venter på bekreftelse.
|
||
|
||
111. **To bugfikser i individuell-turnering-leaderboardet (bredde +
|
||
autoscroll) — 2026-08-18.** Bruker rapporterte at leaderboardet "ser
|
||
forferdelig ut", ikke fyller hele skjermbredden, og ikke autoscroller.
|
||
Undersøkt (Explore-agent) FØR fiksing:
|
||
|
||
**Bredde:** `StrokePlayLeaderboard` (bygget 2026-08-04, allerede med
|
||
egne `lg:`-brytningspunkt for storskjerm-utfylling) satt inni
|
||
`individual-tournament-detail.tsx` sin DELTE `max-w-4xl`-hovedwrapper
|
||
-- samme wrapper som skjema-tunge Oppsett-/Presentasjon-fanene bruker.
|
||
Komponenten fikk dermed ALDRI sjansen til å fylle en stor skjerm, selv
|
||
om den var bygget og testet isolert for akkurat det. Fikset:
|
||
`<main>` sin breddebegrensning er nå betinget av aktiv fane --
|
||
`max-w-[110rem]` kun for leaderboard-fanen, uendret `max-w-4xl` for de
|
||
tre andre.
|
||
|
||
**Autoscroll:** eksisterende ticker-logikk (`requestAnimationFrame`
|
||
+ `scrollTop`) var reell, ikke fiktiv -- men skrolle-containeren
|
||
byttet til `lg:max-h-none lg:overflow-visible` på store skjermer, ut
|
||
fra en (feilaktig, for et stort felt) antakelse om at hele tabellen
|
||
alltid ville få plass uten skrolling der. Uten noen skrollbar
|
||
differanse (`scrollHeight - clientHeight ≤ 1`) ble ticker-loopen en
|
||
permanent no-op -- "Rull automatisk"-knappen vekslet kun tilstand,
|
||
ingen synlig bevegelse. Fikset: et vindu-relativt tak
|
||
(`lg:max-h-[calc(100vh-18rem)]`) på ALLE bredder i stedet, aldri
|
||
`overflow-visible` -- et lite felt får uansett plass (samme
|
||
sluttresultat som før), et stort felt får nå en ekte skrollbar flate.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Ingen
|
||
scratch-browserrunde denne gangen -- begge er smale, mekaniske
|
||
Tailwind-/grense-justeringer, og selve VISUELLE polishen er uansett
|
||
bevisst utsatt til en kommende V0-redesign av samme komponent
|
||
(samme brukerforespørsel, se eget notat/prompt levert samme dag).
|
||
|
||
**Overtatt av punkt 112 samme dag** -- V0-redesignet under løser
|
||
bredde/autoscroll på egen, bedre måte (reell måling i stedet for
|
||
brytningspunkt-antakelse), denne håndfiksen er ren mellomstasjon.
|
||
|
||
112. **V0-redesign av StrokePlayLeaderboard mottatt og integrert —
|
||
2026-08-18.** V0-prompten fra punkt 111 kjørt av bruker, full
|
||
app-eksport lastet opp til Temp-uploads. Kun `components/stroke-
|
||
play-leaderboard.tsx` hentet ut (samme mønster som tidligere V0-
|
||
rundetter denne økten) -- eksporten traff props-kontrakten
|
||
(`LeaderboardRow`/`RoundCell`/`StrokePlayLeaderboard`) helt
|
||
eksakt, identisk eksportert overflate som før (kun `rows`/
|
||
`caption` gjort valgfrie med defaults, ikke et brudd siden
|
||
kallestedet uansett alltid sender begge eksplisitt) -- rent
|
||
innbytte, ingen endring nødvendig i `individual-tournament-
|
||
detail.tsx`.
|
||
|
||
**Løste bredde-/autoscroll-bugsene fra punkt 111 på en mer
|
||
robust måte** enn hånd-fiksen: `canScroll` måles nå med en ekte
|
||
`ResizeObserver` (`scrollHeight - clientHeight > 4`) i stedet for
|
||
en antatt brytningspunkt-grense, og skrolleregionen bruker ett
|
||
`max-h-[62vh]` på ALLE bredder (ingen `lg:`-spesialtilfelle i det
|
||
hele tatt) -- autoscroll fungerer nå garantert når feltet faktisk
|
||
er for stort, uansett skjermbredde. Andre kvalitetstillegg utover
|
||
det som ble bedt om: autoscroll STARTER automatisk (`playing:
|
||
true` som default, riktig for et ubetjent kiosk-oppsett), en
|
||
myk paus ved bunnen før den hopper tilbake til toppen (900ms
|
||
hold), og gjenkjenning av status-etiketter (WD/DQ/DNS/NR/CUT) som
|
||
gir hele raden en dempet, "–"-fylt visning -- går lenger enn
|
||
"cut-linje senere"-hintet i prompten.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Full
|
||
scratch-runde (database+API+next dev) med et rikt, syntetisk felt
|
||
(22 deltakere, 2 runder, ulik fremdrift) på en 1920×1080
|
||
"storskjerm"-visning -- bekreftet full breddeutfylling, ekte
|
||
golfscore-språk (sirkel/firkant/ren E-tekst), lederrad fastholdt
|
||
øverst i gull med troféikon mens feltet under faktisk beveget seg
|
||
(to skjermbilder tatt med mellomrom, ulike rader synlige -- ekte
|
||
bevegelse, ikke bare en veksling av knappe-tilstand). Lys+mørk
|
||
bekreftet, smal mobilbredde bekreftet (sticky POS/SPILLER,
|
||
horisontal skrolling for resten). Scratch-stacken revet ned.
|
||
|
||
**Lærdom fra egen scratch-seeding (ikke en app-bug):** rå
|
||
`INSERT`-er i `tournament_round_hole` alene holder IKKE --
|
||
leaderboard-spørringen leser fra `tournament_round_score` (et
|
||
per-runde-aggregat), som normalt fylles som en bieffekt av den
|
||
ekte score-lagrings-API-en. Måtte etterfylle aggregat-tabellen
|
||
manuelt for at scratch-dataen skulle vise seg riktig -- verdt å
|
||
huske for fremtidig scratch-seeding av individuelle turneringer.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet ("Ja, bygg og rull ut").
|
||
`docker compose build teecup_frontend && docker compose up -d
|
||
teecup_frontend`, rene logger ("Ready in 0ms"). `curl -I
|
||
https://teecup.golf/logg-inn` -> 200 etter utrulling.
|
||
|
||
113. **Nøytral "ikke startet"-rad + tee-tid/starthull i leaderboardet —
|
||
2026-08-18.** Bruker: "De som ikke har begynt å spille ennå må være
|
||
nøytrale på et vis, i leaderboardet. Det bør kanskje komme frem
|
||
teetime og starthull på de, dersom det er angitt?" Rot funnet i
|
||
`_attach_stroke_play_columns` (`individual_tournaments.py`): en
|
||
deltaker som ikke hadde spilt ETT eneste hull ennå (`total_par_played
|
||
== 0`) fikk likevel `total_label = "E"` (brutto/netto) eller `"0 p"`
|
||
(stableford) -- til forveksling likt en ekte, jevn/poengløs SCORE,
|
||
ikke "ingen data ennå". Samme deltaker fikk også en tallfestet
|
||
plassering (nederst i feltet, siden NO_SCORE-nøkkelen sorterer sist)
|
||
-- så de så ut som en reelt rangert, dårlig presterende spiller i
|
||
stedet for en som rett og slett ikke har startet.
|
||
|
||
**Backend (`app/routers/individual_tournaments.py`):** ny
|
||
`LeaderboardEntry.next_tee_time`/`next_start_hole` (kun individuelle
|
||
turneringer har en `tournament_round`-tabell å hente disse fra --
|
||
ETT klokkeslett/starthull PER RUNDE, delt av hele feltet, ikke per
|
||
deltaker -- ingen per-deltaker utslettidspunkt finnes noe sted i
|
||
denne turneringsformen). Populeres når `thru_label == "-"` (samme
|
||
betingelse som already brukes for "ikke startet gjeldende runde"):
|
||
referanserunden er `today_sequence` (runden som er i gang) hvis
|
||
turneringen er underveis, ellers runde 1 (ingen har startet noe
|
||
ennå). `next_start_hole` vises alltid når referanserunden finnes
|
||
(kolonnen har alltid en verdi, default 1); `next_tee_time` vises kun
|
||
når runden faktisk har et satt `scheduled_at` (nullable -- ikke alle
|
||
turneringer setter det). `total_label` er nå `None` (ikke "E"/"0 p")
|
||
når deltakeren ikke har spilt noe -- **kun** når `total_par_played
|
||
== 0`, IKKE når de bare venter på neste runde etter å ha fullført en
|
||
tidligere (den deltakeren beholder sin ekte total, kun THRU viser
|
||
"-", eksisterende oppførsel uendret). `position`/`is_leader` settes
|
||
til `None`/`False` for hele NO_SCORE-gruppen i stedet for en
|
||
tallfestet sisteplass -- inkl. kant-tilfellet der INGEN i feltet har
|
||
spilt noe ennå (da ville rank 1 ellers blitt feilaktig kåret til
|
||
"leder").
|
||
|
||
**Frontend (`stroke-play-leaderboard.tsx`, liten, veloverveid
|
||
håndkodet utvidelse av en eksisterende V0-komponent -- IKKE en ny
|
||
UI-flate, se [[feedback_frontend_via_v0]]):** `totalLabel` er nå
|
||
`string | null`; når `null` (og raden ikke allerede er en WD/DQ-
|
||
statusrad) rendres en ny `TeeInfoTd` i TOTALT-kolonnen i stedet for
|
||
en `ValueTd` -- en nøytral pille (klokke-ikon, `bg-muted`) med
|
||
`"{tee-tid} · Hull {starthull}"`, eller bare det ene hvis kun det er
|
||
satt, eller en ren dash hvis ingen av delene er satt. Raden får
|
||
samme dempede `opacity-70`-behandling som WD/DQ-rader (bevisst
|
||
gjenbruk -- samme "dette er ikke et konkurransedyktig resultat ennå"-
|
||
signal). Ny "Ikke startet"-forklaring lagt til i toppens Legend
|
||
(klokke-ikon), siden pillen introduserer en ny visuell betydning
|
||
Legend ikke dekket fra før.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Backend:
|
||
to nye pytest-tester i `tests/test_individual_leaderboard.py` (én
|
||
med en reell deltaker som HAR spilt runde 1 ved siden av én som
|
||
ikke har startet noe -- bekrefter begge veier samtidig -- og én for
|
||
alle-ikke-startet-kant-tilfellet), kjørt via
|
||
`scripts/run_backend_tests.sh` (egen scratch-database, rører aldri
|
||
ekte `teecup_db`) -- 117/117 grønt (opp fra 115). Frontend: en
|
||
midlertidig lokal `/leaderboardpreviewtmp`-side (kun i en `next dev`-
|
||
prosess, ALDRI committet) rendret `StrokePlayLeaderboard` med
|
||
`MOCK_ROWS` utvidet med to nye rader (én med både tee-tid og
|
||
starthull, én med KUN starthull) -- skjermbilde + a11y-snapshot
|
||
bekreftet riktig pille-tekst, riktig dempet radstil, og at
|
||
posisjonen viser "-" i stedet for et tall. Midlertidig side slettet
|
||
igjen etter verifisering.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. `docker compose build
|
||
teecup_api teecup_frontend && up -d` for begge, rene logger, 200
|
||
OK.
|
||
|
||
114. **Duplikat-spørring ved spillerimport uten e-post — 2026-08-18.**
|
||
Bruker meldte fra, med skjermbilde av org 8db22cb9 sin spillerpool:
|
||
"Dobbel-import erstatter ikke eksisterende, men blir liggende to
|
||
ganger." Undersøkt (les-only mot ekte `teecup_db`, ingen skriving):
|
||
ekte bug -- ni spillere i den organisasjonen (Anders Sperre, Daniel
|
||
Rypdal, Einar Vegheim, m.fl.) dublert, alle med IDENTISKE felt i
|
||
begge kopiene, alle uten e-post. Rotårsak i `create_players_bulk`
|
||
(`app/routers/players.py`): matching mot en eksisterende spiller
|
||
skjedde UTELUKKENDE på e-post -- en rad uten e-post hadde derfor
|
||
INGEN matchingsnøkkel og ble alltid satt inn på nytt, uansett om
|
||
en identisk spiller allerede fantes.
|
||
|
||
Bruker presiserte eksplisitt hvordan dette skal løses: "Det bør
|
||
spørres om den enkelte av de det gjelder skal merges, overskrives
|
||
eller importeres som en tilsynelatende dobbeloppføring" -- IKKE en
|
||
stille automatisk sammenslåing på navn alene (for risikabelt: to
|
||
ulike ekte personer kan dele navn).
|
||
|
||
**Backend:** `/orgs/{id}/players/bulk` sitt request-format endret
|
||
fra `list[PlayerCreate]` til `list[PlayerBulkRow]` (`data` + nye,
|
||
valgfrie `match_player_id`/`duplicate_action`). E-post-matching
|
||
UENDRET (fortsatt automatisk, entydig nok til at spørring ikke
|
||
trengs der). Når frontend har spurt organisator og fått svar for en
|
||
e-postløs rad, sendes svaret som `match_player_id`/`duplicate_
|
||
action`: "merge" = samme COALESCE-oppførsel som e-post-matching
|
||
alltid har hatt (fyll kun tomme felt), "overwrite" = NY oppførsel,
|
||
eneste sti som erstatter allerede-satte felt, kun for denne ene
|
||
raden, "create_new" = uendret opprinnelig oppførsel (bevisst
|
||
dobbeloppføring, ikke en utilsiktet bug lenger). Ubesvarte/uten-
|
||
e-post-rader uten noe svar oppfører seg akkurat som før (opprett ny)
|
||
-- ingen regresjon for eksisterende kallere.
|
||
|
||
**Frontend (`player-import-panel.tsx`):** "Lagre" i redigerings-
|
||
tabellen sjekker nå FØRST om noen rader mangler e-post OG
|
||
navnetreffer en spiller som allerede finnes i org-poolen (egen
|
||
`GET .../players`-sjekk). Finnes slike, stoppes lagringen og en ny
|
||
`DuplicateStep` vises -- én radiogruppe (Slå sammen/Overskriv/
|
||
Importer som ny) per funnet duplikat, forhåndsvalgt "Slå sammen"
|
||
(tryggeste, ikke-destruktive standardvalg dersom organisator ikke
|
||
endrer det). Bevisst HÅNDKODET av Claude, ikke V0-eksportert som
|
||
resten av import-visningen (`player-import-view.tsx`) -- vurdert
|
||
som en liten, funksjonell, avgrenset spørre-skjerm satt inn i en
|
||
eksisterende lagre-flyt, ikke en ny selvstendig UI-flate (se
|
||
[[feedback_frontend_via_v0]]), og bygget med de samme globale
|
||
design-tokenene (border/bg-card/bg-primary) som resten av appen.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Backend:
|
||
`tests/test_players_bulk.py` utvidet med tre nye tester (merge/
|
||
overwrite/create_new), alle eksisterende oppdatert til ny
|
||
`PlayerBulkRow`-form -- 120/120 grønt (opp fra 117), kjørt via
|
||
`scripts/run_backend_tests.sh`. Frontend: midlertidig lokal
|
||
`/dupsteppreviewtmp`-side (kun i `next dev`, ALDRI committet)
|
||
rendret `DuplicateStep` med to falske duplikater -- skjermbilde +
|
||
a11y-snapshot bekreftet ekte radiogruppe-semantikk (label/legend,
|
||
ikke bare visuell styling), og at å klikke "Overskriv" faktisk
|
||
endrer valgt tilstand for riktig rad uendret for den andre.
|
||
Midlertidig side slettet igjen etter verifisering.
|
||
|
||
**Produksjonsopprydding (de ni allerede dublerte spillerne i org
|
||
8db22cb9) -- utført 2026-08-18, bruker bekreftet.** Alle ni par var
|
||
byte-identiske i alle felt; den ene kopien av hvert par var allerede
|
||
meldt på turneringen (`tournament_participant`), den andre helt
|
||
ubrukt. Ni eksplisitte `DELETE FROM player WHERE id = ...` (kun de
|
||
ni ubrukte id-ene, vist til bruker før kjøring) mot ekte `teecup_db`
|
||
-- bekreftet etterpå: 60 -> 51 spillere i org-poolen (matcher
|
||
"51"-tallet i brukerens eget skjermbilde av turneringen), ingen
|
||
display_name med count > 1 igjen.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. `docker compose build
|
||
teecup_api teecup_frontend && up -d` for begge, rene logger, 200
|
||
OK.
|
||
|
||
115. **Cut i individuelle turneringer -- 2026-08-18 (ADR-087).** Bruker:
|
||
"Sett i gang cut-funksjonen" (design bekreftet tidligere samme dag:
|
||
topp N og delt plass, etter et valgt rundenummer, egen "Cut"-seksjon
|
||
på leaderboardet, blokkert fra videre scoring). Ett gjenstående
|
||
teknisk valg avklart med bruker: cutten anvendes MANUELT (organisator
|
||
trykker "Anvend cut"), ikke automatisk ved fullspilt runde.
|
||
|
||
Migrasjon 083 (`tournament.cut_after_round`/`cut_size`/
|
||
`cut_applied_at`, `tournament_participant.cut`), ny `POST .../apply-
|
||
cut` (idempotent, regner alt på nytt fra bunnen hver gang), sperre i
|
||
`update_hole` mot score-registrering i runder etter cut-punktet for
|
||
kuttede deltakere (historikken t.o.m. cut-punktet forblir
|
||
redigerbar). Leaderboard: kuttede deltakere sorteres alltid sist
|
||
(stabil sortering, ikke basert på deres frosne til-par-tall alene),
|
||
`total_label="CUT"` gjenbruker den eksisterende WD/DQ-status-
|
||
mekanismen i `stroke-play-leaderboard.tsx`, pluss en ny synlig "CUT"-
|
||
skillelinje satt inn rett før første kuttede rad (nøyaktig det
|
||
V0-eksporten sin egen kommentar fra punkt 112 forutså plass til). Ny
|
||
"Cut"-kort i Oppsett-fanen (`individual-tournament-detail.tsx`),
|
||
håndkodet i samme stil som de tre andre allerede håndkodede
|
||
innstillingskortene rett over (scoringsmetode/BBB-bonus/flaggkart) --
|
||
se ADR-087 for full detalj, inkl. hvorfor V0 ikke ble brukt her.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, 6 nye
|
||
backend-tester i `tests/test_cut.py` -- 126/126 grønt (opp fra 120).
|
||
Skjermbilde-bekreftet cut-linje-skillelinjen. Se ADR-087 for full
|
||
verifiseringsdetalj, inkl. hva som IKKE ble browser-klikket-gjennom
|
||
denne runden (selve Oppsett-skjemaet -- lavt vurdert restrisiko,
|
||
gjenbruker et identisk, tre ganger allerede fungerende mønster).
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. Migrasjon 083 kjørt mot
|
||
ekte `teecup_db` (`docker exec teeoff_db psql -U teeoff_admin -d
|
||
teecup_db -f 083_tournament_cut.sql`, begge `ALTER TABLE`-setningene
|
||
OK), deretter `docker compose build teecup_api teecup_frontend &&
|
||
up -d` for begge, rene logger, 200 OK.
|
||
|
||
116. **Utslagsgrupper for individuelle turneringer -- 2026-08-18
|
||
(ADR-088).** Bruker: "Jeg må jo kunne bulksjekke hvem som skal
|
||
spille fra hvor, med hvem, klokka når?" Da tilbudt en scoping-runde
|
||
(som Cut fikk): "Jeg vil ha den nå, takk." Design: tradisjonelt
|
||
utslag (samme starthull, staggerte klokkeslett med fast intervall),
|
||
manuell gruppesammensetning med et forslag å justere, fast
|
||
gruppestørrelse.
|
||
|
||
Migrasjon 084 (ny `tournament_round_group` + `tournament_round_
|
||
participant.tournament_round_group_id`), ny `PATCH .../rounds/
|
||
{round_id}` (rundeplanlegging kunne tidligere KUN settes ved
|
||
opprettelse, aldri endres etterpå), `GET`/`POST .../groups/suggest`
|
||
/`PUT .../groups` for selve gruppene. Fire ekte bugs funnet og
|
||
fikset FØR utrulling (alle via egen scratch-verifisering, ingen
|
||
nådde produksjon) -- se ADR-088 for full detalj: (1) en sammensatt
|
||
FK med `ON DELETE SET NULL` som ville korrumpert `organization_id`
|
||
ved gruppesletting, (2) `scheduled_at` typet `str` i stedet for
|
||
`datetime` (latent siden migrasjon 040, aldri truffet før
|
||
`update_round` faktisk ble testet), (3) en tidssone-bug i selve
|
||
frontend-skjemaet (`datetime-local`-verdi sendt uten UTC-konvertering
|
||
-- 2 timer feil i testmiljøet), (4) en React-state-bug der en flyttet
|
||
spiller forsvant sporløst pga. en `setState`-oppdateringsfunksjon
|
||
lest synkront rett etterpå.
|
||
|
||
Ny fil `round-groups-panel.tsx`, håndkodet direkte (ikke sendt via
|
||
V0 først -- bruker ba om funksjonen nå, se ADR-088 for
|
||
resonnementet, samme avveining som `DuplicateStep` i punkt 114).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, 5 nye
|
||
backend-tester i `tests/test_round_groups.py` + 1 ny regresjonstest
|
||
i `test_delete_tournament.py` -- 131/131 grønt (opp fra 126). FULL
|
||
nettleser-scratch-verifisering (database+API+next dev, ekte
|
||
passord-innlogging) -- satte utslagstidspunkt, foreslo grupper,
|
||
flyttet en spiller manuelt, lagret, bekreftet nøyaktig
|
||
gruppesammensetning direkte i databasen. Scratch-stacken revet ned.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. Migrasjon 084 kjørt mot
|
||
ekte `teecup_db`, deretter `docker compose build teecup_api
|
||
teecup_frontend && up -d` for begge, rene logger, 200 OK.
|
||
|
||
117. **Tiebreak ved likt resultat -- 2026-08-18 (ADR-089).** Bruker,
|
||
etter Cut: bekreftet at "topp N og delt plass" allerede fungerer
|
||
riktig for "mange kan dele 12.-plassen" (rangering, ikke hodetall).
|
||
Ekte gap derimot: ingen tiebreak-logikk fantes -- delte plasseringer
|
||
ble alltid vist delt, for vinner OG felt likt. Bruker: metoden skal
|
||
"defineres av arrangøren ved oppsett", som TO uavhengige
|
||
innstillinger (vinner/felt kan ha ulik metode), og skal ALDRI
|
||
påvirke Cut-grensen -- kun sluttresultatet.
|
||
|
||
Migrasjon 085 (`tournament.tiebreak_winner_method`/`tiebreak_field_
|
||
method`, default `'none'`). Ny `_countback_sort_keys` (kaskaderende
|
||
siste 9->6->3->1 hull i siste runde, standard klubbgolf-metode,
|
||
Python tuple-sammenligning gir kaskaden gratis) og `_lowest_hcp_
|
||
sort_keys`, begge integrert i den eksisterende POS-løkken i
|
||
`_attach_stroke_play_columns`. Cut-uavhengigheten er strukturelt
|
||
håndhevet (portet bak `max_sequence is None`, ikke bare avtalt ved
|
||
konvensjon) -- `apply_cut` kan aldri trigge tiebreak-grenen uansett
|
||
konfigurasjon. Ny `TiebreakCard` i Oppsett, samme mønster som
|
||
`CutCard`.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, 4 nye
|
||
backend-tester i `tests/test_tiebreak.py` (countback, laveste HCP,
|
||
uendret `'none'`-oppførsel, og en eksplisitt bekreftelse på at Cut
|
||
IKKE påvirkes selv med countback slått på) -- 135/135 grønt (opp fra
|
||
131).
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. Migrasjon 085 kjørt mot
|
||
ekte `teecup_db`, deretter `docker compose build teecup_api
|
||
teecup_frontend && up -d` for begge (samme utrulling som punkt 118/119
|
||
under, alt samlet i én runde). Rene logger, 200 OK.
|
||
|
||
118. **`name`/`start_date`/`end_date` gjort PATCH-bare + org-oppsettet
|
||
sitt store gjenstående gap kartlagt -- 2026-08-18.** Bruker: "HVOR
|
||
er grensesnittet for å sette opp alle parametrene når det gjelder
|
||
en turnering? ... Vi mangler jo nesten alt." Undersøkt: `name`/
|
||
`start_date`/`end_date` var IKKE en gang i `TournamentUpdate`-
|
||
modellen (kolonnene har eksistert siden migrasjon 001/006, aldri
|
||
PATCH-bare). `visibility`/`description`/`registration_deadline`/
|
||
`registration_capacity`/`registration_overflow_policy`/
|
||
`registration_requires_approval` derimot: FULLT fungerende backend
|
||
(ekte påmeldingsfrist-/kapasitet-/venteliste-/godkjenning-logikk i
|
||
`registration.py`), men INGEN UI noe sted til å sette dem -- en
|
||
ferdigbygd funksjon ingen organisator kan nå. Kun `hero_image`
|
||
(Presentasjon-fanen) og de allerede bygde kortene (scoringsmetode/
|
||
runder/cut/tiebreak/klasser/deltakere) var reelt dekket.
|
||
|
||
La kun til `name`/`start_date`/`end_date` i `TournamentUpdate`
|
||
(samme generiske PATCH-endepunkt som resten) -- selve UI-en for
|
||
HELE denne innstillings-samlingen er bevisst IKKE håndkodet (dette
|
||
er en ny, betydelig UI-flate, ikke en liten tilføyelse til et
|
||
allerede håndkodet kort, se [[feedback_frontend_via_v0]]). V0-prompt
|
||
skrevet og levert til bruker samme dag for en ny
|
||
`TournamentSettingsCard` øverst i Oppsett-fanen, IKKE bygget ennå.
|
||
|
||
**Verifisert:** to nye backend-tester i `tests/test_tournament_
|
||
update.py` (navn/datoer, samt en eksplisitt bekreftelse på at
|
||
påmeldings-/synlighets-feltene FAKTISK var funksjonelle bak
|
||
kulissene hele tiden) -- 137/137 grønt (opp fra 135).
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet, samlet med punkt
|
||
117/119 sin utrulling.
|
||
|
||
119. **TournamentSettingsCard integrert (V0-eksport, samme dag) --
|
||
2026-08-18.** Punkt 118 sin V0-prompt kjørt av bruker, eksport
|
||
lastet opp SAMME dag (samme `Temp-uploads/tee-cup-login-screen.zip`-
|
||
filnavn som leaderboardet i punkt 112 -- overskrevet, ikke et nytt
|
||
navn). Eksporten traff props-kontrakten
|
||
(`TournamentSettingsValues`/`TournamentSettingsCard`) helt eksakt --
|
||
rent innbytte. Fjernet ubrukt `Lock`-ikonimport og V0 sin egen
|
||
demo-`export default` (prosjektkonvensjon: ingen komponent i denne
|
||
kodebasen har en default-eksport).
|
||
|
||
Orkestrering lagt til i `individual-tournament-detail.tsx`
|
||
(`updateSettings`, kortet rendres øverst i Oppsett-fanen, over
|
||
Scoringsmetode) -- kortet kaster sin egen feil ved mislykket lagring
|
||
(viser den selv), i stedet for det vanlige "fang og vis global
|
||
feil"-mønstret de andre PATCH-hjelperne bruker. `ApiTournamentInfo`
|
||
utvidet med `name`/`start_date`/`end_date`/`visibility`/
|
||
`description`/`registration_*` -- feltene kom allerede med i API-
|
||
responsen, bare ikke typet på frontend før nå.
|
||
|
||
**Samme tidssone-klasse bug som ADR-088 funnet og fikset PROAKTIVT
|
||
denne gangen** (mønstergjenkjenning fra forrige runde, ikke en ny
|
||
scratch-oppdagelse): `<input type="datetime-local">` for
|
||
påmeldingsfrist ville fått nøyaktig samme UTC-mistolkning som
|
||
utslagsgruppenes klokkeslett hvis rå strenger ble sendt/vist direkte
|
||
-- `toDatetimeLocalValue`/`fromDatetimeLocalValue`-konvertering lagt
|
||
til FØR noe traff en nettleser, ikke funnet i etterkant.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Lettere
|
||
verifisering enn utslagsgruppene (ADR-088) -- ingen full scratch-
|
||
runde denne gangen: en midlertidig lokal `/settingspreviewtmp`-side
|
||
(kun i `next dev`, ALDRI committet) rendret kortet med realistiske
|
||
mock-verdier, bekreftet radiogruppe-semantikk, at "Når fullt"
|
||
faktisk deaktiveres (med forklaring) når maks-antall tømmes, at
|
||
"Lagre" korrekt aktiveres/deaktiveres med endringer, og at selve
|
||
lagre-runden fullfører og nullstiller "ulagret"-tilstanden. Vurdert
|
||
som lavere restrisiko enn utslagsgruppene siden komponentens egen
|
||
logikk er enklere (ett objekt, ett lagre-kall, ingen multi-entitet-
|
||
tilstandskoreografi) -- selve felt-til-felt-kartleggingen er
|
||
likevel IKKE browser-klikket-gjennom mot en ekte backend ennå, kun
|
||
lest/resonnert over kode + typesjekk.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. `docker compose build
|
||
teecup_api teecup_frontend && up -d` for begge, rene logger, 200
|
||
OK.
|
||
|
||
120. **Omspill (playoff) + direkte HCP-retting per deltaker -- 2026-08-18
|
||
(ADR-090).** To mindre oppfølgere: (A) "ved lik score for leder;
|
||
nytt valg: omspill med avgjørelse. Dette bør informeres om før
|
||
turneringen" -- ny `'playoff'`-verdi for `tiebreak_winner_method`
|
||
(migrasjon 086, kun gyldig for vinneren), fritekst `tiebreak_
|
||
playoff_rule`. Rangeringen behandler `'playoff'` identisk til
|
||
`'none'` (appen kan ikke selv avgjøre et omspill) -- forskjellen er
|
||
at regelen nå vises PROAKTIVT, både i Oppsett og som en gull-banner
|
||
øverst på leaderboardet, uansett om noen faktisk er tiet akkurat nå.
|
||
(B) "Hvordan justerer jeg hcp på spillere?" -- spillerpoolen
|
||
(ADR-085) rørte aldri en allerede frosset `tournament_participant.
|
||
handicap_index_snapshot`. Lagt til i `TournamentParticipantUpdate`
|
||
(allerede generisk endepunkt, ingen andre backend-endringer), ny
|
||
inline `HcpInput` i Deltakere-lista. Endrer bevisst KUN selve
|
||
snapshot-raden -- allerede opprettede runde-handicap forblir frosset.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, 3 nye
|
||
backend-tester -- 140/140 grønt (opp fra 137). Ingen egen scratch-
|
||
runde (små tilføyelser til allerede scratch-verifiserte flater).
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. Migrasjon 086 kjørt mot
|
||
ekte `teecup_db`, deretter `docker compose build teecup_api
|
||
teecup_frontend && up -d` for begge, rene logger, 200 OK.
|
||
|
||
121. **Forklarende hjelpetekst under Cut sitt "Antall som går videre" --
|
||
2026-08-18.** Bruker spurte hva feltet betyr -- feltet hadde ingen
|
||
egen hjelpetekst (kun kortets generelle "Topp N og delt plass"-
|
||
beskrivelse øverst, ikke gjentatt der blikket faktisk lander). Lagt
|
||
til en linje rett under feltet: "Plassen dette tallet havner på blir
|
||
grensen -- ikke et fast tak. Er noen uavgjort nøyaktig der, slipper
|
||
alle de gjennom." Ren tekst-tillegg, ingen logikkendring (oppførselen
|
||
var allerede riktig, kun uforklart). `tsc --noEmit` rent, `vitest
|
||
run` 55/55.
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. `docker compose build
|
||
teecup_frontend && up -d`, rene logger, 200 OK.
|
||
|
||
122. **Manuell deltakerstatus (DSQ/RTD/DNF/DNS) + spillerpool-kobling
|
||
fra turneringsside -- 2026-08-18 (ADR-091).** (A) "Vi må manuelt
|
||
kunne sette Dsq, Rtd, Dnf, og fns [DNS]" -- ny `tournament_
|
||
participant.status`-kolonne (migrasjon 087), overstyrer BÅDE
|
||
beregnet til-par-tall OG Cut-status i leaderboardets `total_label`,
|
||
blokkerer score i ALLE runder (strengere enn Cut). Leaderboardets
|
||
`STATUS`-Set (allerede hadde WD/DNS/DQ/NR/CUT) utvidet med
|
||
DSQ/RTD/DNF -- ren konstant-tillegg, ingen ny visuell komponent. Ny
|
||
status-nedtrekk i Deltakere-lista. (B) "hvordan løser jeg det om
|
||
det er andre ting enn hcp ... det bør dessuten være kobling direkte
|
||
mellom spillerpool og turneringsside" -- svar: alt unntatt HCP
|
||
redigeres allerede i org-spillerpoolen og gjelder umiddelbart (kun
|
||
HCP fryses per turnering). Ny "Rediger i spillerpoolen"-lenke per
|
||
deltaker, åpner spillerpoolen direkte på riktig rad (scroller +
|
||
blinker) via et nytt, usynlig `data-row-key`-anker i den V0-
|
||
eksporterte tabellen -- ingen utvidelse av selve props-kontrakten.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, 7 nye
|
||
backend-tester -- 147/147 grønt (opp fra 140). Ingen egen scratch-
|
||
runde for kobling-delen (lest grundig igjennom i stedet, vurdert
|
||
lav restrisiko).
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. Migrasjon 087 kjørt mot
|
||
ekte `teecup_db`, deretter `docker compose build teecup_api
|
||
teecup_frontend && up -d` for begge, rene logger, 200 OK.
|
||
|
||
123. **Turneringsoppsett som steg-wizard -- 2026-08-18 (ADR-092).**
|
||
Bruker viste GameBook-skjermdumper: "Jeg synes denne delen av
|
||
TeeCup er veldig lite brukervennlig ... Full wizard. Bring gjerne
|
||
V0 inn", med presisering underveis: "husk at vi ikke skal ende opp
|
||
med et plagiat av Golf GameBook." Oppsett-fanen (`SetupTab`) var én
|
||
lang stablet side -- omgjort til 6 klikkbare steg (Turnerings-
|
||
oppsett/Runder og baner/Klasser/Spillere/Grupper og startliste/
|
||
Resultat og opsjoner), fri navigasjon uten låsing eller fullført-
|
||
haker (GameBooks egne skjermdumper viste haker på alle steg selv
|
||
med tomt navnefelt -- ikke ekte validering). Kun to nye V0-
|
||
komponenter, GameBook brukt UTELUKKENDE som strukturelt forbilde
|
||
(steg-mønsteret), ikke visuelt -- begge bruker TeeCups egne
|
||
clubhouse-tokens fra `DESIGN_SYSTEM.md`: `tournament-setup-nav.tsx`
|
||
(steg-raden) og `round-groups-step-list.tsx` (ny topplassering for
|
||
"Utslagsgrupper", tidligere gjemt bak en knapp inni hver enkelt
|
||
runde -- den gamle knappen fjernet, ikke duplisert). Alle 6 stegs
|
||
faktiske innhold er allerede-eksisterende kort (TournamentSettings
|
||
Card/RoundsCard/ClassesCard/ParticipantsCard/CutCard/TiebreakCard),
|
||
kun flyttet til riktig steg -- ingen ny logikk, ingen backend-
|
||
endring, ingen migrasjon. Fant og fikset en bredde-regresjon
|
||
underveis: Oppsett delte en `max-w-4xl`-wrapper med Scorekort/
|
||
Presentasjon som gjorde at 6 steg-piller ikke fikk plass ved siden
|
||
av hverandre selv på en bred skjerm -- egen `max-w-6xl`-unntaksbredde
|
||
lagt til (samme mønster som leaderboardets `max-w-[110rem]`-unntak).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Lokal
|
||
scratch-visning (`next dev`, midlertidig previewside, ALDRI
|
||
committet, fjernet etter bruk): alle 6 steg klikkbare med riktig
|
||
innhold, Grupper-steget sender riktig runde-id videre (tre
|
||
varianter: dato+bane/kun bane/ingen av delene), lys+mørk, smal
|
||
skjerm (steg-raden scroller sidelengs i stedet for å wrappe/feile).
|
||
|
||
**Rullet ut 2026-08-18** -- bruker bekreftet. Ingen migrasjon (ren
|
||
frontend). `docker compose build teecup_frontend && up -d
|
||
teecup_frontend`, rene logger, 200 OK.
|
||
|
||
124. **Misvisende "Alle deltakere er i en gruppe"-tekst i Utslagsgrupper
|
||
-- 2026-08-19 (oppfølger til ADR-092).** Bruker rapporterte rett
|
||
etter wizard-utrullingen: "jeg ser ikke gruppene som er
|
||
opprettet" / "jeg ser rett og slett ingen steder å trykke". Skjerm-
|
||
dumper avslørte at panelet faktisk åpnet seg fint -- den ekte
|
||
årsaken var at Runde 1 hadde 0 deltakere TILORDNET RUNDEN (egen
|
||
tildeling under "Runder og baner", adskilt fra å være med i
|
||
turneringen, eksisterte fra før denne økten). `round-groups-
|
||
panel.tsx` sin "Ugrupperte (0)"-seksjon skrev da misvisende "Alle
|
||
deltakere er i en gruppe" -- samme `ungrouped.length===0`-sjekk
|
||
dekket både "alle er gruppert" og "ingen finnes i det hele tatt",
|
||
ingen måte å skille dem fra teksten alene. Lagt til en
|
||
`totalRoundParticipants`-beregning (`ungrouped.length` + summen av
|
||
alle gruppers deltakere) og et eget forklarende banner når den er 0,
|
||
pluss rettet selve "Ugrupperte"-teksten til å si "Ingen deltakere
|
||
lagt til i runden ennå" i stedet, med henvisning til "Runder og
|
||
baner". Ren tekst-/betingelse-endring i en allerede fungerende
|
||
komponent -- ingen ny logikk for selve gruppe-beregningen.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Ingen egen
|
||
scratch-runde -- et forsøk på å mocke `RoundGroupsPanel` isolert
|
||
strandet på at komponenten gjør ekte `fetch`-kall ved mount (ingen
|
||
enkel stub uten å bygge et helt mock-lag), vurdert som ikke verdt
|
||
kostnaden for en ren tekst-/boolsk-betingelse-endring; lest grundig
|
||
igjennom i stedet (samme lavere-tier-vurdering som Cut-hjelpe-
|
||
teksten, punkt 121).
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet, sammen med #125.
|
||
|
||
125. **Rundedeltakelse flyttet til "Spillere"-steget, som en bulk-tabell
|
||
-- 2026-08-19 (ADR-093).** Direkte oppfølger til #124: den EGENTLIGE
|
||
årsaken til "jeg ser ikke gruppene" var at å tilordne en deltaker
|
||
TIL en runde (med utslagssted) kun var mulig ett om gangen, gjemt
|
||
bak en kontroll inni hver enkelt rundekort. Bruker: "Det er ikke
|
||
logisk at spillere ligger under 'Runder og baner' ... I 'Spillere'-
|
||
fanen bør man kunne bulk legge til spillere til runder. Gjerne med
|
||
sjekkbokser ... kjønn og alder og utslagssted, slik at man enkelt
|
||
kan endre dette også." Ny bulk-tabell (`round-participation-
|
||
table.tsx`, V0-generert) i "Spillere"-steget, under `ParticipantsCard`:
|
||
rader = deltakere (navn, kjønn, fødselsdato/alder), kolonner =
|
||
runder (sjekkboks + utslagssted per celle, "Standardutslag" og
|
||
"Velg alle/ingen" per rundekolonne). Ett nytt backend-endepunkt --
|
||
`PATCH .../rounds/{id}/participants/{id}` for å ENDRE utslagssted
|
||
uten å slette+gjenopprette raden (ville stille fjernet spilleren fra
|
||
en allerede tildelt utslagsgruppe, se ADR-093 for full begrunnelse
|
||
om hvorfor). Ingen bulk-backend-endepunkt -- bulk-handlinger kjører
|
||
parallelle kall mot de eksisterende ett-om-gangen-endepunktene, med
|
||
refetch-fra-server etterpå og en feilsammendrag-banner for delvise
|
||
feil. Kjønn/fødselsdato redigeres via allerede eksisterende
|
||
spillerpool-PATCH, ingen backend-endring der. Ryddet opp den gamle
|
||
ett-om-gangen-kontrollen (`AssignRoundParticipantControl`) fullstendig,
|
||
samme "ikke duplisert inngang"-presedens som Grupper og startliste-
|
||
steget (ADR-092).
|
||
|
||
**Verifisert:** Ny backend-test (2 stk, bekrefter PATCH bevarer
|
||
gruppemedlemskap og avviser utslag fra feil bane) -- 149/149 grønt
|
||
(opp fra 147). `tsc --noEmit` rent, `vitest run` 55/55. Grundig
|
||
scratch-runde av selve komponenten (klikket faktisk gjennom av-/
|
||
påkrysning, bulk "velg alle", lys+mørk, smal skjerm) -- IKKE en full
|
||
innlogget klikk-gjennom mot ekte backend denne runden, vurdert
|
||
tilstrekkelig dekket av backend-pytestene + typesjekket props-
|
||
kontrakt.
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ingen migrasjon.
|
||
`docker compose build teecup_api teecup_frontend && up -d` for
|
||
begge, rene logger, 200 OK.
|
||
|
||
126. **Ekte oversettelsesinfrastruktur (next-intl) + hjelpeside/FAQ +
|
||
Spillere-tabellen slått enda mer sammen -- 2026-08-19 (ADR-094).**
|
||
Bruker ba om en veiledning ("alt skal være oversettbart"), fikk
|
||
beskjed om at appen hadde null i18n fra før (~2200 hardkodede
|
||
strenger, 479 backend-feilkoder), og valgte likevel den store
|
||
løsningen begge ganger spørsmålet ble stilt: ekte motor nå, norsk
|
||
OG engelsk fylt ut nå. `next-intl` installert, INGEN URL-prefiks
|
||
(norske slugs + delte/bokmerkede lenker gjorde det for kostbart) --
|
||
cookie-basert i stedet, med `preferred_locale` (fantes siden
|
||
ADR-015, aldri brukt av frontend) som standard for innloggede
|
||
brukere første gang. Fant og fikset en ekte modul-grense-bug
|
||
underveis (delte konstanter måtte ut i en egen `lib/locale.ts` --
|
||
en server-only-import i `i18n/request.ts` gjorde HELE filen
|
||
utilgjengelig for klientkomponenter, bekreftet med en reell 500-feil
|
||
i scratch). Fant og aksepterte en reell arkitekturbegrensning:
|
||
479 feilkoder er for grovkornet til å oversettes via kode alene
|
||
(samme kode dekker mange ulike meldinger) -- feilmeldinger forblir
|
||
norsk inntil videre, dokumentert som et bevisst hull. Ny `/hjelp`-
|
||
side (oversikt + søkbar FAQ, startet med de to konkrete
|
||
spørsmålene fra samtalen). `preferred_locale` gjort redigerbar
|
||
etter signup (var før låst). I SAMME melding: "Slå sammen de to
|
||
tabellene også" -- Deltakere (ADR-091) og Rundedeltakelse (ADR-093)
|
||
slått sammen til én `tournament-players-table.tsx` (V0), CSV-import
|
||
og "legg til deltaker" uendret, bare flyttet ut av den nå fjernede
|
||
`ParticipantsCard`.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, 3 nye
|
||
backend-tester -- 152/152 grønt (opp fra 149). Full klikk-gjennom
|
||
av hjelpesiden på begge språk i scratch (fant og fikset ett
|
||
gjenværende hardkodet strengpar underveis). Grundig scratch-runde
|
||
av selve den nye tabellen isolert. Ingen full innlogget klikk-
|
||
gjennom av den ferdig integrerte versjonen -- vurdert tilstrekkelig
|
||
dekket gitt komponentens egen grundige testing + typesjekket
|
||
sammenkobling.
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ingen migrasjon.
|
||
`docker compose build teecup_api teecup_frontend && up -d` for
|
||
begge, rene logger, 200 OK (inkl. ny `/hjelp`-rute).
|
||
|
||
127. **Spillertabellen gjort mer "regneark-aktig" + fyller full bredde --
|
||
2026-08-19 (tillegg til ADR-094).** Bruker, med skjermdump av den
|
||
ekte 51-spiller-tabellen: "skulle ønske tabellen var mer 'regneark-
|
||
aktig'" og "hvorfor må jeg scrolle horisontalt når jeg har så mye
|
||
ledig skjermplass?" -- gyldig, bekreftet i skjermdumpen. Bruker tok
|
||
dette direkte til V0 selv og lastet opp svaret. Ny versjon:
|
||
rutenett-linjer i stedet for kort-følelse, lavere radhøyde, mindre
|
||
tekst, og et `<colgroup>` som gir faste smale bredder til korte
|
||
kolonner mens rundekolonnene fordeler resten av bredden seg
|
||
imellom -- fyller skjermen i stedet for fast sidescroll. Props-
|
||
kontrakten uendret, ingen endring i `SetupTab` sin sammenkobling.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Ny scratch-
|
||
visning bekrefter bred skjerm fylt uten sidescroll, lys+mørk.
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger,
|
||
200 OK.
|
||
|
||
128. **Samme rutenett-stil utvidet til spillerpool-tabellen -- 2026-08-19
|
||
(tillegg til ADR-094).** Bruker: "Jeg ønsker at tabellen skal være
|
||
så bred at jeg slipper å scrolle sidelengs" -- presisering etter at
|
||
jeg først (feilaktig) hadde bedt V0 om å BEHOLDE sidescroll for
|
||
denne tabellen. `player-import-view.tsx` sin `EditTable` (opptil
|
||
13 felt + slett-knapp samtidig i spillerpool-modus) fikk samme
|
||
`table-fixed`+`<colgroup>`-teknikk som spillertabellen (#127), men
|
||
med individuelt tilpasset pikselbredde per felt (70-190px) i stedet
|
||
for få brede kolonner -- alle 13 feltene får nå plass uten
|
||
sidescroll på en bred skjerm. V0-eksporten manglet `"roster"`-
|
||
varianten av `Mode`-typen (eldre snapshot) -- flettet inn manuelt
|
||
for å bevare den grenen og selve typene uendret. Fjernet nå-død
|
||
`FIELD_MIN_W`-konstant.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Scratch-
|
||
visning (5 rader, alle 13 spillerpool-felt): alle kolonner synlige
|
||
uten sidescroll på 1900px, riktig hjelpetekst for spillerpool-modus
|
||
bevart, redigering + sticky navnekolonne fungerer, lys+mørk, smal
|
||
skjerm faller korrekt tilbake til sidescroll.
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger,
|
||
200 OK.
|
||
|
||
129. **HCP-komma, utdatert deltakertabell etter spillerpool-redigering,
|
||
zebra-striper -- 2026-08-19.** Tre bruker-rapporterte ting i samme
|
||
runde. (1) "Er det relevant om det registreres HCP med komma heller
|
||
enn punktum? Jeg vil at det skal være irrelevant." -- undersøkt
|
||
grundig: `tournament-players-table.tsx` og to felt i `round-
|
||
detail.tsx` ("guest-hcp"/"edit-hcp-{id}") brukte `type="number"`,
|
||
som i de fleste nettlesere BLOKKERER komma-tegnet før JS i det hele
|
||
tatt ser det -- selv om lagre-parsingen andre steder allerede gjorde
|
||
`.replace(",", ".")`, hjalp det ikke når kommaet aldri kom inn i
|
||
feltet. Byttet til `type="text"` + `inputMode="decimal"` (samme
|
||
mønster som account-settings.tsx allerede brukte korrekt). I
|
||
TILLEGG manglet selve `.replace(",", ".")`-konverteringen helt i
|
||
tre lagre-stier: `tournament-players-table.tsx` sin `commitHcp`,
|
||
og to steder i `org-player-roster-panel.tsx` samt ett i `player-
|
||
import-panel.tsx` (spillerpool-lagring/CSV-import). Alle rettet,
|
||
HCP-komma er nå konsistent håndtert app-bredt. (2) "Jeg kan ikke se
|
||
at deltagertabellen blir oppdatert når det er lagret endringer i
|
||
spillerpool-tabellen" -- rotårsak: "Rediger i spillerpoolen" er en
|
||
ekte `<a href>` (full sidenavigasjon), og nettleserens bfcache kan
|
||
gjenopprette turneringssiden i EKSAKT samme (utdaterte) tilstand
|
||
ved tilbake-navigasjon uten å kjøre noen React-effekter på nytt.
|
||
Løst med en stille bakgrunns-refresh (`visibilitychange` +
|
||
`pageshow` med `persisted`-sjekk) i BÅDE `IndividualTournamentDetail`
|
||
(deltakere/runder/baner/klasser) og `SetupTab` (spillerpool) --
|
||
hovedlasteren løftet ut av `useEffect` til en gjenbrukbar
|
||
`loadTournamentData()`, med en delt `mountedRef` i stedet for en
|
||
per-kall `cancelled`-variabel siden den nå kalles fra to steder.
|
||
(3) "Kan du kjøre annenhver rad-bakgrunn? Hvit og lys grå?" --
|
||
spillerpool-tabellen (`player-import-view.tsx`) fikk tilbake zebra-
|
||
striping (`bg-card`/`bg-muted/40`) oppå rutenett-stilen fra forrige
|
||
runde -- den sticky navnekolonnen får nå samme bakgrunn som resten
|
||
av raden i stedet for alltid `bg-card`, ellers ville den sett
|
||
frakoblet ut fra stripen bak seg ved sidescroll.
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Scratch
|
||
bekrefter: komma faktisk skrivbart og korrekt konvertert (testet
|
||
både i spillerpool-tabellen og den nye spillertabellen -- sistnevnte
|
||
viste "18,9" lagret som det reelle tallet 18.9, ikke reversert til
|
||
forrige verdi), zebra-striper synlige. Punkt (2) er IKKE scratch-
|
||
reprodusert (bfcache-oppførsel er vanskelig å simulere i en
|
||
hodeløs devtools-økt) -- vurdert tilstrekkelig dekket av at
|
||
rotårsaken er tydelig identifisert og fiksen er et etablert,
|
||
velprøvd mønster (visibilitychange/pageshow) for akkurat dette
|
||
problemet.
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger,
|
||
200 OK.
|
||
|
||
130. **Zebra-stripe rettet til nøytral grå + HCP-"frosset snapshot"
|
||
kommunisert tydeligere (ikke reversert) -- 2026-08-19.** To
|
||
oppfølgere. (1) Bruker: "Lyse GRÅ striper, ikke lyse grønne." --
|
||
`bg-muted` (punkt 129) er en grønntonet token i denne paletten
|
||
(klubbhus-fargene), ikke nøytral grå. Rettet med et translucent
|
||
svart/hvitt-overlegg (`bg-black/[0.03] dark:bg-white/[0.04]`) --
|
||
bærer ingen fargetone i seg selv, så det leses som ekte grå i
|
||
begge temaer uten å måtte velge to separate faste gråtoner.
|
||
(2) Bruker: "HCP oppdateres ikke i deltagertabellen. Det mangler,
|
||
selv om det er lagt til i spillertabellen." -- undersøkt grundig,
|
||
inkludert et FORHASTET forsøk på å regne rundedeltakeres
|
||
`playing_handicap` på nytt når turnering-HCP-en endres, som viste
|
||
seg å BRYTE en bevisst, allerede testet ADR-090-beslutning
|
||
("frosset rundehandicap", `test_updating_snapshot_does_not_
|
||
retroactively_change_already_frozen_round_handicap`) -- reversert
|
||
før commit, ingen kodeendring i `individual_tournaments.py` til
|
||
slutt. Presisert med bruker: dette var faktisk spillerpool→
|
||
turnering-HCP (ikke turnering→runde), som ER det bevisste,
|
||
allerede-eksisterende "frosset snapshot"-designet fra ADR-090 --
|
||
IKKE en bug. Bruker bekreftet eksplisitt: behold frosset, men
|
||
kommuniser problemstillingen bedre. Løst med (a) en kort forklarende
|
||
linje rett over spillertabellen i "Spillere"-steget ("HCP-en under
|
||
er en egen kopi for turneringen ... en endring i spillerpoolen
|
||
oppdaterer den IKKE automatisk"), og (b) et nytt FAQ-spørsmål i
|
||
`/hjelp` (nb+en) som forklarer HVORFOR (HCP kan påvirke resultatet,
|
||
skal aldri endre seg stille midt i en turnering).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55, backend
|
||
152/152 (uendret -- den forkastede backend-endringen ble aldri
|
||
committet). Scratch bekrefter grå (ikke grønn) stripe i begge
|
||
temaer, og at det nye FAQ-spørsmålet vises og utvides korrekt.
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet, sammen med #131.
|
||
|
||
131. **To reelle feil i rundedeltakelse-delen av den nye spillertabellen --
|
||
2026-08-19.** Bruker, mens hen faktisk brukte den nettopp utrullede
|
||
tabellen: "Dersom jeg velger at ALLE skal gå fra 55 så kan jeg ikke
|
||
individuelt endre enkeltspillere. Dersom jeg først velger ALLE og
|
||
55, så kan jeg ikke fjerne 'ALLE-haken' etterpå. Da går alle
|
||
utslagene tilbake til 32 igjen." Kodegjennomgang avdekket TO
|
||
separate, ekte bugs, ikke misforståelser: **(1)**
|
||
`changeRoundParticipantTee` (enkelt-rad utslagsendring) sendte
|
||
`tournament_participant`-IDen rett inn i PATCH-URL-en, men backend-
|
||
endepunktet forventer `tournament_round_participant`-IDen (en helt
|
||
annen rad) -- ethvert forsøk på å endre én spillers utslag
|
||
individuelt traff dermed en ikke-eksisterende rad (404), stille
|
||
ingen synlig effekt. `TournamentPlayersTable` sin egen cellemodell
|
||
kjenner aldri round-participant-IDen (kun tournament_participant-
|
||
IDen, selve nøkkelen i `cells[roundId][...]`), så fiksen slår opp
|
||
riktig ID FØRST -- samme oppslagsmønster `toggleRoundParticipant`
|
||
sin DELETE-gren allerede brukte riktig. **(2)** `bulkSetRound
|
||
Participation` sin "Velg alle" hoppet over ALLE deltakere som
|
||
allerede var tilordnet runden (uansett hvilket utslag de hadde) --
|
||
et valgt Standardutslag slo dermed KUN inn for nylig tilførte
|
||
deltakere, aldri for de som fra før var satt til et annet utslag
|
||
(f.eks. "32"). Av-kryssing fjernet deretter kun de nylig tilførte
|
||
-- de opprinnelige "32"-deltakerne ble aldri rørt i noen retning,
|
||
så resultatet så ut som en reversering til "32" selv om det egentlig
|
||
var at de aldri ble endret i utgangspunktet. Rettet til å PATCHe
|
||
utslaget på allerede tilordnede i stedet for å hoppe over dem --
|
||
"Velg alle" + et utslag betyr nå faktisk "alle spiller dette
|
||
utslaget".
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55. Ingen
|
||
scratch-simulering denne runden -- gitt at feilen er live og
|
||
brukeren aktivt tester akkurat nå, prioritert rask utrulling
|
||
fremfor en tidkrevende mock-backend-gjenskaping i scratch; begge
|
||
rettelser sporet presist til brukerens eksakte symptomer via
|
||
kodelesning (feil ID-type i URL-en, og hvilke deltakere som faktisk
|
||
var med i `Promise.all`-batchen).
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger,
|
||
200 OK.
|
||
|
||
132. **Utslagsvalg i den nye spillertabellen -- reelt UX-hull, funnet via
|
||
video -- 2026-08-19.** Bruker lastet opp en skjermopptak: "Den viser
|
||
hvor vanskelig det er å velge utslag." Bilde-for-bilde-gjennomgang
|
||
(video konvertert til enkeltbilder med `ffmpeg`, installert lokalt
|
||
for anledningen) viste at bruker klikket presist på avkrysnings-
|
||
boksen til én rad (ville trolig bare endre utslaget hennes fra "55"
|
||
til "50"), som avkrysset raden helt av -- og satt deretter fast:
|
||
når en rad ikke er tilordnet runden viste cellen KUN en død strek
|
||
(`<span>—</span>`), ingen interaktiv kontroll. Eneste vei tilbake
|
||
var den 16px avkrysningsboksen, tett inntil selecten, lett å bomme
|
||
på. Bekreftet med et oppklaringsspørsmål til bruker (var det
|
||
"massetildel 55, overstyr noen til 50"-flyten som var problemet?)
|
||
-- bruker bekreftet at DET allerede fungerte (endre en allerede
|
||
avkrysset rads select direkte er upåvirket av denne bug'en), det
|
||
var spesifikt fastlåsingen etter et (feilaktig eller bevisst)
|
||
klikk på boksen som var hovedproblemet.
|
||
|
||
**Fiks** (`components/tournament-players-table.tsx`, `PlayerRow`
|
||
sin runde-celle): utslagsvelgeren vises nå ALLTID, aldri en død
|
||
strek. På en ikke-tilordnet rad vises den stiplet/nedtonet med
|
||
rundens standardutslag som forhåndsvalg -- å velge ET HVILKET SOM
|
||
HELST utslag der kaller `onToggleRound(..., true, teeId)` og
|
||
tilordner raden i samme handling, ingen egen boks-runde nødvendig.
|
||
Avkrysningsboksen er nå viklet i en egen `<label>` med større
|
||
klikkflate, og adskilt fra selecten med en synlig skillelinje
|
||
(`gap-1` + en 1px `bg-border`-strek) i stedet for kun `gap-1.5` --
|
||
reduserer sjansen for feilklikk mellom de to kontrollene.
|
||
|
||
**Verifisert i scratch** (mock-data, lokal `next dev` på port 3100,
|
||
IKKE mot kjørende `teecup_frontend`/`teecup_api` -- en midlertidig
|
||
forhåndsvisningsrute under `frontend/app/tmp-preview-tee-select-
|
||
x7q9/`, fjernet igjen etter verifisering): brukt Chrome DevTools MCP
|
||
til faktisk å klikke gjennom scenarioet. Bekreftet (1) massetildeling
|
||
fortsatt fungerer identisk, (2) å endre en allerede tilordnet rads
|
||
utslag direkte i selecten fungerer uendret, (3) den faktiske bug'en
|
||
er løst -- avkryssing av en rad beholder nå en fungerende select
|
||
(viser siste kjente utslag, stiplet stil), og å velge et utslag der
|
||
tilordner raden på nytt i ett klikk. Ingen konsollfeil. `tsc --noEmit`
|
||
rent, `vitest run` 55/55.
|
||
|
||
**Rullet ut 2026-08-19** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
|
||
|
||
133. **Score-fanen (egen runde): skjult score-registrering + doble
|
||
"Score"-faner -- funnet via brukers skjermbilde, 2026-08-20.** Bruker
|
||
viste et ekte skjermbilde: på et hull med koordinatdata fylte
|
||
avstandsmåleren/hull-diagrammet HELE den synlige mobilskjermen, uten
|
||
noe som antydet at spillerkortene (selve score-registreringen) fantes
|
||
lenger ned -- en førstegangsbruker skjønte ikke at de måtte scrolle.
|
||
Samtidig lå det to visuelt identiske pille-fanerader rett oppå
|
||
hverandre: RoundHeader sin Score/Scorekort/Leaderboard, og en HELT
|
||
SEPARAT, egen "Score"/"Spillere og runde"-fanerad bygget inni
|
||
`round-detail.tsx` selv -- begge kalte én fane "Score", leste som
|
||
duplikate kontroller.
|
||
|
||
Løst via en Claude-skrevet V0-prompt (samme etablerte
|
||
"Claude skriver prompt, bruker kjører V0, Claude kobler inn"-mønster
|
||
som resten av appen) -- V0-eksporten (`round-score-top.tsx`) traff
|
||
begge problemene presist og ble brukt som fasit for restrukturering
|
||
av de EKTE komponentene (ikke V0s egen mock-`HoleDiagram`, men den
|
||
ekte `HoleTargetDistance`/`HoleDiagram`, som allerede -- upåaktet --
|
||
støttet en "compact"-modus, aldri faktisk brukt noe sted i kodebasen
|
||
før nå):
|
||
|
||
- `hole-target-distance.tsx`: ny valgfri `onAvailabilityChange`-prop
|
||
-- kalleren får vite om banen faktisk har koordinatdata for hullet,
|
||
uten å duplisere fetch-en selv.
|
||
- `round-detail.tsx` (`PlayerHoleCards`): avstandsmåleren vises nå
|
||
`size="compact"` som standard (kun de tre store avstandstallene),
|
||
med en "Vis banekart og hindringer"-knapp som utvider til det fulle
|
||
diagrammet -- knappen vises KUN når `onAvailabilityChange` faktisk
|
||
har meldt at det finnes data (ikke en død knapp på baner uten
|
||
koordinater). En alltid synlig "Registrer score ↓"-knapp scroller
|
||
rett til spillerkortene.
|
||
- `round-detail.tsx` (hovedkomponenten): den separate
|
||
"Score"/"Spillere og runde"-pille-fanen er fjernet, erstattet med
|
||
ÉN sekundær, tydelig ANNERLEDES knapp (ramme + ikon, ikke en pille)
|
||
som veksler `pageTab` mellom de to visningene, med tekst som endrer
|
||
seg etter tilstand ("Spillere og runde" / "Tilbake til score").
|
||
|
||
**Reelt funn underveis, ikke bare kosmetikk:** før denne fiksen
|
||
fantes det INGEN pålitelig vei tilbake fra "Spillere og runde" til
|
||
"Score" dersom brukeren kom dit via en ekstern lenke (f.eks.
|
||
RoundHeader sin Administrer-dialog, som lenker til `?tab=manage`) --
|
||
`pageTab`-tilstanden ble aldri tilbakestilt automatisk, og den gamle
|
||
fanen for å bytte tilbake fantes ikke i den konteksten. Den nye
|
||
veksle-knappen løser dette som et biprodukt, siden den er ren
|
||
klient-tilstand, ikke avhengig av URL.
|
||
|
||
**Verifisert i scratch:** en midlertidig forhåndsvisningsrute som
|
||
rendret den EKTE `RoundDetail`-komponenten (ikke en isolert mock) med
|
||
et fullstendig mocket `fetch` (runde, deltakere, hull, avstandspunkter
|
||
for hull 1 -- IKKE for hull 2, bevisst, for å teste begge
|
||
tilstander) og emulert GPS-posisjon (Chrome DevTools). Bekreftet:
|
||
spillerkort synlig uten scrolling på mobilviewport (402px), "Vis
|
||
banekart"-knappen vises for hull 1 og er HELT fraværende for hull 2,
|
||
fanebytte-knappen veksler korrekt begge veier med riktig
|
||
tekst/ikon, hele administrasjonspanelet (Flighter/Rediger runde/
|
||
Fullfør/Slett/full spillerliste) fortsatt fullt funksjonelt etter
|
||
fjerningen av den gamle faneraden. Ingen konsollfeil. `tsc --noEmit`
|
||
rent, `vitest run` 55/55.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
|
||
|
||
134. **Score-fanen: fire oppfølgingsfunn på spillerkortene rett etter
|
||
#133, 2026-08-20.** Bruker så det ferdige resultatet av #133 og
|
||
meldte fire konkrete ting samme runde:
|
||
|
||
- **"Deg"-badgen ved siden av eget navn i spillerkortet** oppfattet
|
||
som unødvendig ("knappen med 'deg' må bort") -- fjernet fra
|
||
`PlayerHoleCards` sin kortheader (`round-detail.tsx`). Ikke rørt
|
||
andre steder den samme badgen brukes (`PlayerList`,
|
||
`ScorecardGrid`) -- kun spilt-kortet på Score-fanen var i scope.
|
||
- **Historikk-panelet ("Din historikk på dette hullet...") vist på
|
||
ALLE spilleres kort**, ikke bare egen -- reelt feil siden teksten
|
||
allerede eksplisitt sier "DIN", uansett hvilken spillers data som
|
||
faktisk vises. Rettet: `HoleHistoryPanel` rendres nå kun når
|
||
`player.isSelf`.
|
||
- **Hindrings-ikonene i kompakt avstandsmåler-visning (fra #133)
|
||
viste generisk `TriangleAlert` for alle hindringstyper** i stedet
|
||
for de egentlige type-ikonene (bunker/vann/kratt/tre osv.) som
|
||
alltid har fungert i full-visningen -- ren regresjon, siden
|
||
compact-modusen (introdusert i #133) aldri hadde fått samme
|
||
ikon-logikk som full-modusen, og ingen tidligere kalte
|
||
`size="compact"` for at det skulle synes. `hole-diagram-view.tsx`:
|
||
kompakt-listen bruker nå samme `HazardRow`-komponent (ikon,
|
||
bane, forkant/bakkant) som full-visningen, i stedet for en egen,
|
||
forenklet rad -- bruker sammenlignet de to skjermbildene direkte
|
||
og påpekte at de tar omtrent like mye plass, så den forenklede
|
||
raden ga ingen reell besparelse for tapt informasjon.
|
||
- **Den nye "Spillere og runde"/"Tilbake til score"-veksleknappen
|
||
fra #133 er selv et duplikat** av RoundHeader sin egen
|
||
"Administrer"-knapp (øverst til høyre, lenker til samme
|
||
`?tab=manage`-visning) -- bruker pekte dette ut selv
|
||
("Den ligger jo i toppmenyen oppe til høyre?"), presiserte at kun
|
||
selve RADEN skulle bort, ikke funksjonen. Løst ved å fjerne
|
||
knapperaden helt og heller gjøre `pageTab`-synkroniseringen mot
|
||
`?tab=manage`-parameteren TOVEIS (`round-detail.tsx`) i stedet for
|
||
den opprinnelige ensrettede varianten fra #133 -- den ensrettede
|
||
varianten eksisterte kun for å skåne knappens EGEN lokale
|
||
tilstand fra å bli overstyrt av URL-en; siden knappen nå er
|
||
borte, er den beskyttelsen unødvendig, og toveis-synkronisering
|
||
gir samtidig RoundHeader sin "Score"-fane en reell funksjon som
|
||
"tilbake fra manage" (fungerte ikke før -- fanen linket til bare
|
||
URL-en, men ingenting nullstilte `pageTab` når parameteret
|
||
forsvant).
|
||
|
||
**Verifisert i scratch:** `tsc --noEmit` rent etter alle fire.
|
||
Badge-fjerning, historikk-restriksjon og fjerning av veksle-raden
|
||
bekreftet visuelt i en mocket `RoundDetail`-forhåndsvisning
|
||
(samme mock-fetch-teknikk som #133). Hindrings-ikon-fiksen verifisert
|
||
isolert ved å rendre `HoleDiagram` direkte med `size="compact"` og
|
||
alle hindringstyper -- sammenlignet før/etter-skjermbilde, alle
|
||
typer viser nå riktig ikon. Toveis-URL-synkroniseringen ble
|
||
bekreftet REAKTIVT for inn-veien (Administrer -> manage, samme
|
||
side, ingen remount) via faktisk klikk-gjennomgang; selve
|
||
tilbake-retningen (manage -> bar URL) kunne ikke skjermbilde-
|
||
bekreftes -- forhåndsvisnings-nettleseren hang gjentatte ganger på
|
||
akkurat den navigasjonen uavhengig av trigger (både `router.push`
|
||
og ekte `<Link>` testet), mens serveren i alle tilfeller svarte
|
||
riktig og raskt (bekreftet i dev-server-loggen) -- vurdert som et
|
||
miljø-/verktøyproblem i forhåndsvisnings-nettleseren, ikke en reell
|
||
kodefeil, siden logikken er en direkte, symmetrisk speiling av den
|
||
allerede beviste `initialTab`-beregningen ved førstegangslasting.
|
||
Flagget eksplisitt til bruker som ikke fullt skjermbilde-verifisert.
|
||
|
||
135. **Spillerkort på Score-fanen: "Mer"-knapp toner ned historikk +
|
||
Avslutt/Send scorekort, 2026-08-20.** Direkte oppfølging av #134 --
|
||
historikk-panelet og Avslutt/Send-radene lå fortsatt alltid synlig
|
||
på hvert kort, bare henholdsvis begrenset til eget kort og uendret.
|
||
Bruker foreslo selv en "Mer"-knapp som løsning; Claude skrev en
|
||
V0-prompt for mønsteret, bruker kjørte den og lastet opp eksporten
|
||
(`player-card-more.tsx`).
|
||
|
||
Ny lokal komponent `PlayerCardMore` i `round-detail.tsx` (rett etter
|
||
`ParticipantCompletionActions`) -- eier KUN vis/skjul, tar imot
|
||
`history`/`actions` som `ReactNode`-props. Interaksjonsmønsteret
|
||
(grid-rows 0fr->1fr myk høyde-animasjon, samme prinsipp som "Vis
|
||
banekart og hindringer" fra #133, tekst+chevron på knappen, innhold
|
||
tatt ut av a11y-tre/tab-rekkefølge mens skjult) hentet fra
|
||
V0-eksporten. De EKTE `HoleHistoryPanel` og `ParticipantCompletionActions`
|
||
sendes inn uendret som barn -- V0 sine egne plassholder-rader
|
||
(`HistoryRow`/`SecondaryActions`) ble IKKE tatt i bruk, kun designet
|
||
for dem. Kun `PlayerHoleCards` (Score-fanen) endret -- `PlayerList`
|
||
("Spillere og runde") beholder `ParticipantCompletionActions` synlig
|
||
uten veksling, siden den siden allerede er en ren
|
||
administrasjonsvisning der handlingene ikke er "i veien".
|
||
|
||
**Verifisert i scratch:** mocket `RoundDetail`-forhåndsvisning med tre
|
||
spillere (eier m/historikk, to medspillere u/historikk). Bekreftet:
|
||
alle tre kort viser kompakt "Mer" som standard, klikk utvider korrekt
|
||
(knapp blir "Skjul", chevron snur), egen historikk + Avslutt/Send
|
||
scorekort vises for eierens kort, KUN Avslutt/Send (ingen tomt/rart
|
||
mellomrom) for medspillerne, ingen dobbel padding/kant-artefakt der
|
||
de ekte komponentenes egen styling (border-t/px-4) møter wrapperen.
|
||
Sjekket lys og mørk modus. `tsc --noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
|
||
|
||
136. **Score-fanen: mindre luft under RoundHeader, 2026-08-20.** Bruker
|
||
viste et skjermbilde fra live: unødvendig stort tomrom mellom
|
||
fanerad-headeren og "Hull 1"-boksen når ingen varsel-banner
|
||
(offline/pending-sync) var synlig. Årsak: `<main>` sin egen
|
||
`py-6 sm:py-8`-toppadding la seg OVENPÅ hull-seksjonens allerede
|
||
eksisterende `mt-5` lenger ned i stedet for å erstatte den -- disse
|
||
stablet seg til 44-52px kombinert, mens resten av siden bruker en
|
||
tettere rytme (16-20px). Første rettelse: `<main>` byttet til
|
||
`pt-3 sm:pt-4` i stedet for `py-6 sm:py-8`.
|
||
|
||
**Oppfølging samme dag, etter et nytt skjermbilde fra live:** bruker
|
||
kunne fortsatt ikke se noen forskjell. Regnet etter: 12px kuttet fra en
|
||
kombinert avstand på 44-52px er en for liten, lett-å-overse endring på
|
||
en full mobilskjerm -- riktig rettet, men ikke egentlig LØST. Kuttet
|
||
i stedet HELE `<main>` sin toppadding (`pt-0`) -- den første synlige
|
||
seksjonen i Score-fanen (hull-navigatoren, evt. et varsel-banner eller
|
||
FormatResultPanel foran den) har allerede sin egen `mt-5`/`mb-5`, som nå
|
||
er den ENESTE luften mot headeren, samme rytme som resten av siden.
|
||
Bunnpadding fortsatt uendret. Verifisert i scratch (mocket
|
||
`RoundDetail`, mobilviewport 402px) -- klart synlig, tettere avstand
|
||
sammenlignet før/etter.
|
||
|
||
137. **Hindringer bak spilleren skal ikke lenger vises, 2026-08-20.**
|
||
Bruker påpekte (uavhengig av skjermbildet over): avstander til
|
||
hindringer skal kun vises når hindringen faktisk ligger MELLOM
|
||
spilleren og greenen -- en hindring spilleren allerede har gått forbi
|
||
er unødvendig informasjon.
|
||
|
||
`hole-target-distance.tsx`:
|
||
- `groupDiagramHazards()` (brukt av det vanlige diagram-sporet, når
|
||
banen har tee-koordinater) tar nå imot spillerens `alongPercent`
|
||
(posisjon langs tee-green-aksen) og filtrerer bort enhver hindring
|
||
der YTTERSTE kant (bakkanten for et parret forkant+bakkant-hinder,
|
||
selve punktet for et enkeltstående) ligger BAK spilleren. Et parret
|
||
hinder regnes altså som passert først når spilleren er forbi BEGGE
|
||
kantene, ikke bare forkanten. En liten toleranse
|
||
(`HAZARD_PASSED_TOLERANCE_PCT = 1,5`) hindrer at en hindring rett
|
||
ved spillerens fot blinker inn/ut mellom to GPS-oppdateringer.
|
||
- Den sjeldnere fallback-listen (bane uten tee-koordinater, ingen
|
||
akse å projisere langs) filtrerer med en enklere proxy: hindringen
|
||
må ligge nærmere green-midten enn spilleren selv gjør.
|
||
|
||
**Verifisert i scratch:** en frittstående forhåndsvisning av
|
||
`HoleTargetDistance` (compact + full) med stubbet `navigator.
|
||
geolocation.watchPosition` (synkron, deterministisk testposisjon --
|
||
ikke Chrome DevTools sin posisjons-emulering, som var upålitelig
|
||
tidligere i denne økten) og to hindringer på rett linje langs hullet,
|
||
én bak og én foran en fast spillerposisjon. Bekreftet: kun hindringen
|
||
foran spilleren ("Vannhinder") vises, i BÅDE kompakt liste og fullt
|
||
diagram -- hindringen bak ("Fjellknaus") er helt fraværende i begge.
|
||
`tsc --noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
|
||
|
||
138. **"Mer" flyttet inn i navnereden, "Mål et slag" flyttet inn i "Mer"
|
||
-- KUN scorekortførers eget kort, 2026-08-20.** Direkte oppfølging av
|
||
#135/#136 samme dag. Bruker viste skjermbilde med to nye punkter:
|
||
(1) "Mer" skulle vekk fra sin egen full-bredde-rad og inn i selve
|
||
navnereden, ved siden av score-knappen. (2) "Mål et slag" skulle vekk
|
||
fra sin egen rad og inn i "Mer"-panelet. Bruker presiserte i tillegg,
|
||
uavhengig av skjermbildet: BÅDE hull-historikk OG "Mål et slag" skal
|
||
være forbeholdt scorekortføreren selv (`player.isSelf`) -- "Mål et
|
||
slag" var tidligere synlig for ALLE spilleres kort (kun klubbutvalget
|
||
var allerede begrenset til eget bag via `ownBagClubs`), nå er hele
|
||
knappen borte for medspillere.
|
||
|
||
Claude skrev en V0-prompt (samme mønster som før), bruker kjørte den
|
||
og lastet opp eksporten (`player-card-more.tsx`, `PlayerCardBody`).
|
||
Løste DOM-utfordringen riktig: navnereden var tidligere ÉN stor
|
||
`<button>` (hele raden var trykkflaten som åpner score-registrering)
|
||
-- HTML tillater ikke en knapp inni en knapp, så eksporten strukturerte
|
||
om til en YTRE, ikke-knapp rad-wrapper med to SØSKEN: score-knappen og
|
||
en kompakt "Mer"-pille (44x44px, tekst+chevron).
|
||
|
||
`round-detail.tsx`:
|
||
- `PlayerCardMore` (fra runde 1, se #135) utvidet med en ny
|
||
`scoreButton`-prop (den eksisterende, rike navnerad-knappen --
|
||
playerSide-prikk/badges/HCP/dimmed/won/ScorecardCell/StrokeDots,
|
||
alt UENDRET innhold, kun flyttet til å være denne propen) og en ny
|
||
`measureShot`-prop. Selve "Mer"-vekslen flyttet fra en egen
|
||
full-bredde rad til en kompakt pille ved siden av `scoreButton` i en
|
||
ny, ikke-knapp navnerad-wrapper.
|
||
- `PlayerHoleCards`: `ShotMeasurementEntry` er ikke lenger en egen rad
|
||
-- sendes nå inn som `measureShot`, gated `player.isSelf && !readOnly`
|
||
(før: kun `!readOnly`, altså synlig for alle). `ownBagClubs`-
|
||
betingelsen forenklet til alltid `ownBagClubs` siden hele knappen nå
|
||
uansett kun rendres for isSelf.
|
||
|
||
**Verifisert i scratch:** mocket `RoundDetail`-forhåndsvisning, tre
|
||
spillere. Bekreftet: navnerad + kompakt "Mer"-pille ligger side ved
|
||
side (ingen nøstet knapp, `tsc --noEmit` bekrefter gyldig JSX/DOM),
|
||
eget kort sitt panel viser Mål et slag -> historikk -> Avslutt/Send i
|
||
riktig rekkefølge, medspillers panel viser KUN Avslutt/Send (verken
|
||
Mål et slag eller historikk). Sjekket lys og mørk modus.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet. Ren frontend.
|
||
`docker compose build teecup_frontend && up -d`, rene logger, 200 OK.
|
||
|
||
139. **"Registrer score"-hoppet landet bak sticky headeren, 2026-08-20.**
|
||
Bruker viste to skjermbilder: (1) banekartet utvidet dytter
|
||
"Registrer score" langt ned -- foreslo en flytende knapp i bunnen som
|
||
løsning (se V0-prompt, ikke bygget denne runden -- venter på
|
||
V0-eksport). (2) et reelt, uavhengig funn: å trykke "Registrer score"
|
||
scroller til spillerlisten, men landet med FØRSTE spillerkort delvis
|
||
skjult bak den sticky RoundHeader-en -- `scrollIntoView` kompenserer
|
||
ikke selv for en `position: sticky`-header.
|
||
|
||
Årsak funnet raskt: `holePanelRef` (brukt for hull-til-hull-navigasjon,
|
||
samme sticky-header-utfordring) bruker allerede riktig
|
||
`scroll-mt-28` -- men `playerListRef` (brukt for nettopp dette
|
||
"Registrer score"-hoppet) hadde kun `scroll-mt-4`, langt fra nok til å
|
||
dekke headerens faktiske høyde. Rettet: samme `scroll-mt-28`-verdi som
|
||
det allerede fungerende mønsteret.
|
||
|
||
**Verifisert i scratch:** mocket `RoundDetail` med ekte
|
||
tee/green-koordinater (banekart faktisk utvidbart) + stubbet GPS.
|
||
Utvidet banekartet, trykket "Registrer score" -- første spillerkort
|
||
(Erol Haagenrud) landet nå fullt synlig rett under headeren, ikke
|
||
lenger delvis skjult. `tsc --noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #140. Ren
|
||
frontend. `docker compose build teecup_frontend && up -d`, rene
|
||
logger, 200 OK.
|
||
|
||
140. **Flytende "Registrer score" når banekartet er utvidet, 2026-08-20.**
|
||
Direkte oppfølging av #139 sitt punkt 1 -- V0-eksporten kom inn samme
|
||
dag. Claude skrev prompten, bruker kjørte den og lastet opp
|
||
(`floating-register-score.tsx`).
|
||
|
||
`round-detail.tsx`: ny liten `RegisterScoreButton`-komponent (samme
|
||
visuelle knapp, nå gjenbrukt to steder). I `PlayerHoleCards`:
|
||
- Banekart kollapset (standard): knappen ligger i normal side-flyt,
|
||
uendret oppførsel fra før.
|
||
- Banekart utvidet: knappen er i stedet `fixed inset-x-0 bottom-0`,
|
||
festet til viewportets bunn uansett scroll-posisjon i det høye
|
||
diagrammet. Gradient-scrim (`bg-gradient-to-t from-background`) +
|
||
skygge løfter den visuelt over innholdet under. `aria-hidden` +
|
||
`pointer-events-none` når skjult/i overgang -- blokkerer aldri klikk
|
||
på innhold bak. `env(safe-area-inset-bottom)` i bunnpaddingen for
|
||
gest-navigasjon. Samme klikk-handling (`scrollIntoView` til
|
||
spillerlisten) begge steder -- kun plasseringen er ny.
|
||
- Liten spacer (`h-20`) lagt til nederst når kartet er utvidet, slik
|
||
at siste innhold (kommentarfeltet) kan scrolles fritt for den
|
||
flytende knappen i stedet for å bli permanent skjult bak den.
|
||
|
||
**Verifisert i scratch:** mocket `RoundDetail` med ekte
|
||
tee/green-koordinater + stubbet GPS (samme oppsett som #139).
|
||
Bekreftet: kollapset ser uendret ut, utvidet gjør knappen flytende og
|
||
forblir synlig ved scroll, kun ÉN "Registrer score"-knapp i a11y-treet
|
||
om gangen (aldri to samtidig), klikk hopper fortsatt korrekt til
|
||
spillerlisten (med riktig `scroll-mt-28` fra #139), ingen blokkering
|
||
av klikk på innhold bak den flytende sonen. Sjekket lys og mørk modus.
|
||
`tsc --noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #139. Ren
|
||
frontend. `docker compose build teecup_frontend && up -d`, rene
|
||
logger, 200 OK.
|
||
|
||
141. **Flytende "Registrer score" hang igjen synlig etter at spillerlisten
|
||
var nådd, 2026-08-20.** Direkte oppfølging av #140 -- bruker viste
|
||
skjermbilde: selv etter å ha scrollet forbi spillerkortene (helt ned
|
||
til kommentarfeltet), sto den flytende "Registrer score ↓"-knappen
|
||
fortsatt og fløt over bunnen av skjermen, uten mening siden
|
||
spillerlisten allerede var passert (pilen peker "ned til spillerne",
|
||
men spillerne var nå OVER, ikke under). Bruker spurte om dette burde
|
||
V0-promptes -- Claude vurderte det til å være ren VIS/SKJUL-LOGIKK
|
||
rundt et allerede ferdig designet element (fra #140), ikke en ny
|
||
visuell flate, og løste det direkte.
|
||
|
||
`round-detail.tsx`: ny `playerListReached`-tilstand, sant når
|
||
spillerlistens toppkant har nådd/passert headerens høyde (112px,
|
||
samme referanse som `scroll-mt-28`). Knappen (og spaceren under den,
|
||
fra #140) vises nå kun når `mapExpanded && !playerListReached`.
|
||
|
||
**Feilslått første forsøk, funnet i scratch:** brukte først
|
||
`IntersectionObserver` med en krympet `rootMargin` for å fange
|
||
112px-krysningen. Viste seg feil -- med standard `threshold: [0]`
|
||
fyrer observatøren KUN ved 0%->0%-kryssinger (elementet helt inn/ut av
|
||
roten), ikke ved at `boundingClientRect.top` passerer en vilkårlig
|
||
linje MENS elementet allerede overlapper roten. Spillerlisten ble
|
||
dermed hengende fast på "ikke nådd" gjennom resten av scrollingen,
|
||
identisk med bugen selv om koden så riktig ut. Erstattet med en vanlig
|
||
`scroll`/`resize`-lytter (passive) som leser
|
||
`getBoundingClientRect().top` direkte -- korrekt, kontinuerlig
|
||
oppdatering.
|
||
|
||
**Verifisert i scratch** (samme mock-oppsett som #139/#140): utvidet
|
||
banekartet, trykket flytende "Registrer score" -- knappen forsvant nå
|
||
korrekt idet spillerkort 1 landet synlig, ingen tomt mellomrom stod
|
||
igjen. Scrollet helt ned til kommentarfeltet -- knappen forble skjult
|
||
(ikke lenger den opprinnelige bugen). Scrollet tilbake opp forbi
|
||
spillerlisten -- knappen dukket korrekt opp igjen. `tsc --noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #142. Ren
|
||
frontend. `docker compose build teecup_frontend && up -d`, rene
|
||
logger.
|
||
|
||
142. **Nei, ikke helt -- ekte målebasert oppfølging av #141,
|
||
2026-08-20.** Bruker spurte direkte: scroller det nå faktisk langt
|
||
nok ned til å vise HELE spillerkort 1? Presis scratch-måling (ikke
|
||
skjermbilde-eyeballing) avdekket at svaret fortsatt var nei: det
|
||
hardkodede `112px` (lånt fra `scroll-mt-28`, brukt både i CSS-en for
|
||
scroll-hoppet og i #141 sin `playerListReached`-terskel) stemte ikke
|
||
-- headerens EKTE høyde målte 141,5px for denne runden (bane + utslag
|
||
+ dato + klokkeslett samtidig), ~30px mer enn antatt. Toppen av
|
||
spillerkort 1 lå dermed fortsatt delvis bak headeren.
|
||
|
||
Rot-årsak: `RoundHeader` sin høyde er reelt DYNAMISK (rundenavn/
|
||
utslag/dato/klokkeslett kan pakkes til flere linjer avhengig av
|
||
runden) -- en NY hardkodet konstant (f.eks. 160px) ville bare flyttet
|
||
samme sårbarhet til en annen runde med enda lenger innhold, ikke løst
|
||
den. Derfor målt LIVE i stedet:
|
||
|
||
- Ny `getHeaderHeight()`-hjelper: `document.querySelector("header")
|
||
?.getBoundingClientRect().height` (112 kun som aller siste
|
||
nødfallback).
|
||
- `scrollToPlayerList()` erstatter `scrollIntoView` + statisk
|
||
`scroll-mt-28` -- regner selv ut nøyaktig mål-scrollposisjon med
|
||
`getHeaderHeight()` + en delt `SCROLL_GAP_PX = 16`-konstant.
|
||
- `playerListReached`-sjekken (#141) oppdatert til å bruke SAMME
|
||
`getHeaderHeight() + SCROLL_GAP_PX`-terskel -- måtte rettes en gang
|
||
til underveis i denne rundens scratch-verifisering: brukte først
|
||
kun `getHeaderHeight()` uten gap-konstanten, som fikk knappen til å
|
||
henge synlig selv etter et korrekt landet hopp (16px avvik mellom
|
||
hvor scrollet faktisk landet og hvor "reached" ble regnet ut).
|
||
|
||
**Verifisert i scratch, presist (ikke bare skjermbilde):** målte
|
||
`getBoundingClientRect()` for spillerkort 1 og headeren direkte via
|
||
`evaluate_script` rett etter scroll-hoppet. Bekreftet numerisk:
|
||
`cardTop: 157.5`, `headerBottom: 141.5`, `gapBelowHeader: 16`,
|
||
`fullyVisible: true` (både over header OG innenfor viewport-bunnen).
|
||
Bekreftet at den flytende knappen samtidig er `aria-hidden="true"`
|
||
(korrekt skjult) i samme øyeblikk. `tsc --noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #141. Ren
|
||
frontend. `docker compose build teecup_frontend && up -d`, rene
|
||
logger.
|
||
|
||
143. **Automatisk hull-fremgang landet ikke øverst på nytt hull,
|
||
2026-08-20.** Bruker viste skjermbilde: etter at ALLE spilleres
|
||
score/statistikk er ført for et hull, hopper appen automatisk videre
|
||
til neste hull (`advanceWizardPlayer()` -> `goNext()`) -- men
|
||
visningen scrollet ikke til toppen av det nye hullet, landet et sted
|
||
midt på siden i stedet.
|
||
|
||
Rot-årsak: `scrollToHolePanel()` (kalt fra BÅDE `goNext()`/`goPrev()`
|
||
for manuell Forrige/Neste-navigasjon OG fra `advanceWizardPlayer()`
|
||
sin automatiske fremgang) brukte samme flawed mønster som allerede
|
||
rettet for `scrollToPlayerList()` i #142 samme dag: `scrollIntoView`
|
||
+ en statisk `scroll-mt-28`-klasse (112px), mens headerens EKTE høyde
|
||
er dynamisk (141,5px målt for en runde med langt nok innhold).
|
||
Rettet med samme mønster: `getHeaderHeight()` (målt live) +
|
||
delt `SCROLL_GAP_PX`-konstant, manuell `window.scrollTo()`.
|
||
|
||
**Verifiseringen lyktes IKKE fullt ut denne runden, notert ærlig:**
|
||
forsøk på å klikke gjennom "Neste hull" i scratch traff gjentatte,
|
||
uforklarte HARDE sideinnlastinger ved ethvert `router.replace`-kall
|
||
(samme "hard reload i stedet for myk SPA-overgang"-egenhet som er
|
||
observert flere ganger tidligere i denne økten for ferske
|
||
`/tmp-preview-*`-ruter -- denne gangen traff det OGSÅ den ekte,
|
||
stabile `/my-rounds/[id]`-ruta, testet via en `initScript`-injisert
|
||
fetch-mock for å unngå akkurat den ferske-rute-mistanken). Ingen
|
||
bruker har noensinne rapportert at hull-bytte laster hele siden på
|
||
nytt i ukevis med reell bruk, så dette vurderes som en egenhet ved
|
||
DENNE scratch-nettleserøkten, ikke reell produksjonsatferd -- men det
|
||
betyr at selve scroll-beregningen IKKE kunne klikkes gjennom og måles
|
||
presist, i motsetning til søsken-fiksen i #142
|
||
(`scrollToPlayerList`, som aldri kalles sammen med en
|
||
router-navigasjon og DERFOR kunne verifiseres presist). Samme formel
|
||
gjenbrukt med vilje for konsistens, men flagget som et gap i
|
||
verifiseringen. `tsc --noEmit` rent.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet, uttrykkelig akseptert
|
||
den svakere verifiseringen ("Vi får heller reversere eller justere").
|
||
Ren frontend. `docker compose build teecup_frontend && up -d`, rene
|
||
logger. Ekstra oppmerksomhet på akkurat denne (automatisk
|
||
hull-fremgang etter siste spiller) anbefalt i faktisk bruk.
|
||
|
||
144. **Cut-overlevere kaskaderes automatisk til alle senere runder,
|
||
2026-08-20 (ADR-095).** Bruker: "I eksempelturneringen vår opererer
|
||
vi med cut etter runde 1. På et vis burde de som har klart cut'en
|
||
autoselekteres på runde 2?" Undersøkt (read-only, Explore-agent) før
|
||
bygging: `tournament_round_participant`-rader opprettes kun på
|
||
forespørsel, aldri på forhånd for alle runder -- ingen eksisterende
|
||
mekanisme for dette.
|
||
|
||
To designbeslutninger bekreftet av bruker etter at jeg foreslo en
|
||
snevrere variant: (1) kaskade til ALLE runder etter cut, ikke bare
|
||
den neste -- "Har du klart cut'en spiller du alle påfølgende
|
||
runder." (2) fjern kuttede spilleres rundedeltakelse i senere runder
|
||
(ikke bare la stå blokkert) -- "Jo, fjern de. Dette får vi heller
|
||
reversere om det viser seg å være en dårlig beslutning," etter at
|
||
jeg opplyste at sletting kaskaderer til `tournament_round_hole`
|
||
(migrasjon 040, `ON DELETE CASCADE`).
|
||
|
||
Ny `_sync_post_cut_round_participation`-funksjon
|
||
(`app/routers/individual_tournaments.py`), kalt fra `apply_cut` i
|
||
samme transaksjon: for hver runde etter cut -- overlevere som
|
||
mangler en rad får én auto-INSERT-et (tee kopiert fra cut-runden,
|
||
handicap regnet ut med samme funksjon `add_round_participant`
|
||
bruker); kuttede spilleres eksisterende rad SLETTES, MEN kun hvis
|
||
den ikke allerede har registrert score (beskytter ekte data mot
|
||
kaskade-sletting). Idempotent, som resten av `apply_cut`.
|
||
`CutResult` fikk nytt felt `added_to_later_rounds`, vist i "Anvend
|
||
cut"-bekreftelsen. `SetupTab` nullstiller nå hele
|
||
`roundParticipantsByRound` etter "Anvend cut" (flere runder kan
|
||
endres samtidig) i stedet for å refreshe kun én.
|
||
|
||
**Testet, ikke bare gjennomgått:** 5 nye tester lagt til
|
||
`tests/test_cut.py`, kjørt mot en isolert scratch-database
|
||
(`./scripts/run_backend_tests.sh`) -- auto-tillegg med riktig
|
||
kopiert tee, fjerning av kuttede spilleres rad, IKKE-fjerning når
|
||
scorer finnes (+ bekreftet at 403-sperren fortsatt fungerer der),
|
||
kaskade til runde 3 uten runde 2 i mellom. Én eksisterende test
|
||
fikk sin forventning riktig oppdatert fra 403 til 404 (raden
|
||
slettes nå, blokkeres ikke lenger). Alle 156 backend-tester grønne.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet ("Suprt! Da kjører vi
|
||
på!"), etter en presiseringsrunde om hva "fjernes" faktisk betyr
|
||
(deselekteres fra runden, IKKE fjernet fra deltakerlisten eller
|
||
spillerpoolen). `docker compose build teecup_api teecup_frontend &&
|
||
up -d`, begge containere friske, rene logger. Ingen migrasjon
|
||
nødvendig.
|
||
|
||
145. **Sorterbare kolonner i Spillere-tabellen (turneringsoppsett),
|
||
2026-08-20.** V0-prompt (`v0-prompt-players-table-sort.md`, sortering
|
||
ALENE -- en tidligere prompt som også inkluderte en bulk-
|
||
utslagssted-flyt ble uttrykkelig trukket av bruker: "Jeg glemmer at
|
||
sjekkboksene er for HVEM SOM SKAL SPILLE RUNDEN. Ikke gjør hva jeg
|
||
ba deg om (med unntak av sorteringsmuligheten)"). V0-mønsteret
|
||
(klikk header -> stigende -> synkende -> opprinnelig rekkefølge,
|
||
`aria-sort`, pil-indikator kun på aktiv kolonne, ekte fokuserbar
|
||
knapp) integrert i den ekte `tournament-players-table.tsx` for de syv
|
||
ikke-runde-kolonnene (Spiller/Hcp/Klasse/Statistikknivå/Status/
|
||
Kjønn/Alder) -- runde-kolonnene og handlingskolonnen forblir
|
||
usorterbare, urørt.
|
||
|
||
Klasse-sortering slår opp klassenavn via `classId` (`classNameById`,
|
||
utledet fra `classes`-proppen); statistikknivå/status sorteres etter
|
||
en meningsfull rangering (mengde sporing / alvorlighet), ikke
|
||
alfabetisk. Null-verdier sorteres alltid sist, uansett retning.
|
||
|
||
**Verifisert i scratch mot den EKTE komponenten** (ikke V0-mocken):
|
||
egen `tmp-preview`-rute som monterer `TournamentPlayersTable` direkte
|
||
med realistisk mock-data, `evaluate_script`-sjekker av faktisk
|
||
radrekkefølge OG `aria-sort`-verdi gjennom alle tre klikk-sykluser
|
||
(stigende/synkende/opprinnelig) for både numerisk (Hcp) og
|
||
oppslags-basert (Klasse) sortering -- begge korrekte, inkludert
|
||
null-sist-oppførsel. `tsc --noEmit` rent. Scratch-ruten slettet
|
||
etterpå, bekreftet med `git status --short`.
|
||
|
||
**Rullet ut 2026-08-20** -- samlet med #144. Ren frontend, samme
|
||
`docker compose build teecup_frontend && up -d`, rene logger.
|
||
|
||
146. **Flytende "Registrer score" forsvant helt ved mange hindringer,
|
||
2026-08-20.** Bruker viste skjermbilde: knappen manglet når hullet
|
||
hadde mange registrerte hindringer -- selv om banekartet ALDRI ble
|
||
utvidet.
|
||
|
||
Rot-årsak: den flytende knappens synlighet var styrt av `mapExpanded`
|
||
alene (#140-#142). Feil premiss -- ADR-083 gjorde at
|
||
"compact"-visningen (kartet IKKE utvidet) OGSÅ viser hele
|
||
hindringslisten, ikke bare nærmeste. På et hull med mange hindringer
|
||
(7 i brukerens tilfelle) blir denne listen alene høy nok til å dytte
|
||
den vanlige side-flyt-knappen utenfor synlig område -- akkurat den
|
||
situasjonen den flytende knappen skulle dekke, men trigget aldri
|
||
fordi `mapExpanded` fortsatt var `false`.
|
||
|
||
Rettet ved å bytte ut det indirekte signalet (`mapExpanded`) med et
|
||
direkte: er selve side-flyt-knappen faktisk synlig akkurat nå. Ny
|
||
`inFlowButtonRef` + `inFlowButtonVisible`-tilstand, målt med samme
|
||
`getBoundingClientRect()` + scroll/resize-lytter-mønster som
|
||
`playerListReached` (ikke IntersectionObserver, se begrunnelse i
|
||
kodekommentaren fra #141). Side-flyt-knappen fjernet fra sin
|
||
`{!mapExpanded && ...}`-betingelse -- ligger nå ALLTID i normal flyt,
|
||
uansett kart-tilstand. Den flytende knappen vises når
|
||
`!inFlowButtonVisible && !playerListReached` (før: `mapExpanded &&
|
||
!playerListReached`).
|
||
|
||
**Verifisert i scratch, ikke bare gjennomgått:** egen
|
||
`tmp-preview`-rute som monterer den EKTE `RoundDetail` (kun
|
||
`roundId`-prop, ingen egen mock-versjon), med `window.fetch`-mock for
|
||
runde/deltakere/hull-data og et hindringssett som gjenskaper
|
||
brukerens skjermdump nøyaktig (7 hindringer, samme typer/avstander).
|
||
`evaluate_script`-målt gjennom fire scenarioer: (1) mange hindringer,
|
||
kart ALDRI utvidet -- flytende knapp korrekt synlig
|
||
(`aria-hidden="false"`) siden side-flyt-knappen satt bak headeren;
|
||
(2) scroll til side-flyt-knappen faktisk er fri av headeren -- flytende
|
||
knapp korrekt skjult; (3) hull UTEN hindringer -- flytende knapp
|
||
forblir skjult hele veien, ingen regresjon; (4) kart utvidet (det
|
||
opprinnelige brukstilfellet) -- samme korrekte oppførsel som før,
|
||
pluss `playerListReached`-skjuling ved scroll til spillerlisten
|
||
fortsatt intakt. `tsc --noEmit` rent. Scratch-ruten slettet etterpå,
|
||
bekreftet med `git status --short`.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet ("Bygg og deploy").
|
||
`docker compose build teecup_frontend && up -d`, container frisk,
|
||
rene logger. Ingen migrasjon, ren frontend.
|
||
|
||
147. **Tre ting i "Ny runde"-veiviseren, 2026-08-20 (bruker, skjermdump av
|
||
Spillere-steget).**
|
||
|
||
1. **"Administrer runde"-overleggsmenyen lukket seg ikke selv etter
|
||
"Spillere og runde" ble trykket** -- måtte lukkes manuelt for å se
|
||
siden under. Rot-årsak: `<Link href={manageHref}>` i
|
||
`ManageRoundDialog` (round-header.tsx) navigerer ofte KUN til en
|
||
søkeparameter-endring (`?tab=manage`) på samme rute -- ingen
|
||
remount, så dialogens lokale `manageOpen`-state (eid av
|
||
`RoundHeader`) forble uendret. Rettet med `onClick={onClose}` på
|
||
selve lenken, i tillegg til navigasjonen.
|
||
|
||
2. **Kunne ikke overstyre HCP for en nylig lagt til, kontokoblet
|
||
medspiller i selve veiviseren** -- feltet viste kun en read-only
|
||
"X · hentes fra profilen"-tekst. Fungerte allerede via "Runde og
|
||
runde" (PATCH .../participants/{id}) senere i runden, men ikke fra
|
||
start. To lag:
|
||
- **Backend** (`add_participant`, app/routers/rounds.py): POST-
|
||
endepunktet ignorerte `body.handicap_index` HELT for en
|
||
`user_id`-koblet deltaker (brukte alltid profilens verdi
|
||
direkte) -- ulikt PATCH-endepunktet (`update_participant`), som
|
||
allerede æret en eksplisitt overstyring for enhver deltaker,
|
||
ikke bare gjester (bekreftet i kode: `guest_only_fields`-sperren
|
||
ekskluderer bevisst `handicap_index`). Rettet til `body.
|
||
handicap_index if body.handicap_index is not None else target
|
||
["handicap_index"]`, i tråd med ADR-038 sin allerede dokumenterte
|
||
"MANUELT satt ... med mindre eksplisitt overstyrt"-modell.
|
||
- **Frontend** (`step3-players.tsx`): HCP-feltet for en
|
||
kontokoblet, ikke-eier-spiller gjort redigerbart (samme `Field`/
|
||
`TextInput`-mønster som allerede brukt for gjester i samme fil),
|
||
forhåndsutfylt med profilverdien, urørt betyr fortsatt "bruk
|
||
profilen". `wizard-context.tsx` sin `submit()` sender nå
|
||
`handicap_index: p.hcp ?? null` også for kontokoblede
|
||
deltakere (uendret verdi er en trygg no-op, siden backend
|
||
faller tilbake til profilen når feltet er urørt).
|
||
- Ny backend-test `test_add_participant_hcp_override.py` (2 tester
|
||
-- overstyring æres, OG regresjonsvern: uendret oppførsel når
|
||
ingen overstyring gis), kjørt mot scratch-database
|
||
(`./scripts/run_backend_tests.sh`), alle 158 tester grønne.
|
||
|
||
3. **Standard statistikknivå for en NY medspiller var "Alt", skal
|
||
være "Kun slag"** -- `defaultStat`-proppen til `AddPlayerPanel`
|
||
speilet feilaktig `state.statLevel` (scoreførerens EGET valgte
|
||
nivå fra "Spilleform"-steget), ikke et fast "Kun slag"-utgangs-
|
||
punkt for ANDRE spillere. Bekreftet ved kodegjennomgang: en
|
||
tilstøtende kommentar i wizard-context.tsx (fra en tidligere,
|
||
urelatert rettelse 2026-08-15) dokumenterer eksplisitt at
|
||
`defaultStat` alltid var MENT som "default for NYE medspillere",
|
||
atskilt fra eierens eget felt -- speilingen var en glipp, ikke et
|
||
bevisst valg. Rettet til en fast `"score"` i `step3-players.tsx`.
|
||
Organisatoren kan fortsatt sette et høyere nivå manuelt per
|
||
medspiller.
|
||
|
||
**Verifisert i scratch, ikke bare gjennomgått, for alle tre:**
|
||
(1) egen `tmp-preview`-rute som monterer den EKTE `RoundHeader`,
|
||
bekreftet via `evaluate_script` at dialogen (`[role="dialog"]`) er
|
||
borte fra DOM-en umiddelbart etter klikk+navigasjon, ingen manuell
|
||
lukking nødvendig. (2)+(3) egen `tmp-preview`-rute som monterer den
|
||
EKTE `WizardProvider` + `Step3Players` (med `window.fetch`-mock for
|
||
`/auth/me` og `/people/search`), drevet gjennom hele den ekte søk-og-
|
||
legg-til-flyten (søkte "Morten", la til kontoen, ekspanderte kortet)
|
||
-- bekreftet HCP-feltet er en ekte, redigerbar `<input>` forhånds-
|
||
utfylt med 11.1, aksepterer et desimaltall (satt direkte via
|
||
`dispatchEvent` for å utelukke en `fill`-verktøy-særegenhet som først
|
||
strippet punktumet), OG at "Kun slag" er forhåndsvalgt for den nylig
|
||
lagte-til spilleren. `tsc --noEmit` rent gjennom hele. Begge
|
||
scratch-rutene slettet etterpå, bekreftet med `git status --short`.
|
||
|
||
**Rullet ut 2026-08-20** -- bruker bekreftet ("Ja takk").
|
||
`docker compose build teecup_api teecup_frontend && up -d`, begge
|
||
containere friske, rene logger. Ingen migrasjon.
|
||
|
||
148. **Hindringslisten sortert feil vei + kartretning ved slagmåling nå
|
||
styrt av ekte utslag→green-geometri, 2026-08-21.** Bruker, med
|
||
skjermdump: holdes telefonen loddrett for avstandsmåling, bør det
|
||
som er lengst unna (greenen) stå øverst i lista og det nærmeste
|
||
(utslaget) nederst, med hindringene sortert i SAMME rekkefølge
|
||
imellom -- listen viste tidligere motsatt (nærmeste hindring øverst,
|
||
rett attmed greenen). Presisert: der en hindring har både forkant og
|
||
bakkant vist, er det BAKKANTEN (fjerneste kant) som skal styre
|
||
sorteringen.
|
||
|
||
To sorteringssteder i `hole-target-distance.tsx` rettet fra stigende
|
||
til synkende (lengst unna FØRST): `groupDiagramHazards()` sin
|
||
`entities.sort(...)` (den parrede forkant/bakkant-visningen, brukt av
|
||
`HoleDiagram` -- nøkkel endret fra alltid `below` til `above ?? below`,
|
||
altså bakkanten når en finnes), og den enklere flate listen (`hazards
|
||
.sort(...)`, brukt når banen mangler tee-koordinater -- ingen
|
||
forkant/bakkant-parring der, kun ett tall per punkt).
|
||
|
||
**Samme runde, andre delen av brukerønsket:** på baner med BÅDE
|
||
utslags- OG green-koordinater kjent, skal selve satellittkartet i
|
||
slagmåleren ("Mål et slag") vises med greenen opp/utslaget ned, ikke
|
||
nord opp. `initialBearing`-mekanismen fantes allerede (bygget
|
||
2026-08-10, roterte kartet etter spillerens EGEN forrige slag-
|
||
retning på hullet, `bearingDegrees(forrige slag start, slutt)`) --
|
||
denne er nå supplert med en NY, mer pålitelig kilde: `ShotMeasurement
|
||
Entry` (fellet-komponenten brukt fra alle tre stedene i filen
|
||
slagmåling kan åpnes fra: kompakt spillerkort, delt-ball-sidebadge,
|
||
og full-skjerm-veiviserens "Retning"-steg) henter nå selv hullets
|
||
`target-points`, regner ut `bearingDegrees(midtpunkt(utslag front/
|
||
bakkant), green senter)`, og lar DENNE vinne over forrige-slag-
|
||
gjetningen når den finnes -- ekte banegeometri er mer pålitelig enn
|
||
en tilfeldig sleng/hook på forrige slag. Egen, liten fetch per
|
||
spillerkort (i stedet for å prop-drille et tall gjennom RoundDetail/
|
||
PlayerHoleCards/ScoringWizard for å dele HoleTargetDistance sin
|
||
allerede-eksisterende henting) -- en akseptert kostnad for at ALLE
|
||
tre inngangene til kartet får riktig retning samtidig, ikke bare én.
|
||
|
||
**Verifisert i scratch, presist, ikke bare gjennomgått:** egen
|
||
`tmp-preview`-rute som monterer den EKTE `RoundDetail`, med
|
||
`window.fetch`-mock for `target-points` (utslag+green forskjøvet til
|
||
en bevisst IKKE-kardinal (ikke nord/øst/sør/vest) retning, for å
|
||
fange opp en feil som ved et uhell falt tilbake til "ingen
|
||
rotasjon"). Ekte `NEXT_PUBLIC_MAPBOX_TOKEN` lest fra `.env` inn i
|
||
scratch-serverens miljø (aldri skrevet i klartekst noe sted).
|
||
`mapboxgl.Map`-konstruktøren instrumentert (kun i scratch, aldri i
|
||
kildekoden) til å fange opp `bearing`-verdien den faktisk mottok.
|
||
Gikk gjennom hele den ekte flyten (ekspander spillerkort -> "Mål et
|
||
slag" -> "Velg punkt på kart") og bekreftet numerisk: forventet
|
||
`19.02°` (regnet uavhengig, samme formel, i selve scratch-siden) mot
|
||
faktisk mottatt `19.016306...°` -- match. Selve korttavlerenderingen
|
||
feilet med "Kunne ikke laste kartet" pga. manglende ekte nettverks-
|
||
tilgang til Mapbox sine tile-servere i dette sandkasse-miljøet
|
||
(urelatert til retting -- `bearing`-argumentet appliseres synkront
|
||
ved konstruksjon, uavhengig av om selve kart-flisene faktisk laster).
|
||
`tsc --noEmit` rent. Scratch-ruten slettet etterpå, bekreftet med
|
||
`git status --short`.
|
||
|
||
**Rullet ut 2026-08-21** -- bruker bekreftet ("Bygg og deploy, men
|
||
git commit først"). Committet i fem logiske steg (cut-kaskade,
|
||
sorterbare kolonner, Ny runde-fiksene, Score-fane-rettelsene,
|
||
CHANGELOG), deretter `docker compose build teecup_api
|
||
teecup_frontend && up -d`, begge containere friske, rene logger.
|
||
Ingen migrasjon.
|
||
|
||
149. **Shotgun-start bygget, 2026-08-21 (ADR-096, migrasjon 088).**
|
||
Reverserer/utvider migrasjon 084 sitt bevisste "IKKE shotgun"-valg
|
||
fra 2026-08-18 -- oppdaget som et behov mens utskriftsklare
|
||
startlister ble planlagt: "Det må komme frem om det er Shotgun
|
||
eller løpende start, med hvilket hull man starter på. Sorteringen
|
||
av startlisten gjøres etter utslagsform." Bruker valgte ekte
|
||
støtte fremfor kun utskrift-deko.
|
||
|
||
Ny `start_mode` ('consecutive'/'shotgun') på BÅDE `tournament_round`
|
||
(individuell) og `session` (Cup) -- samme arkitektoniske hull fantes
|
||
uavhengig begge steder. Ny valgfri `start_hole` på
|
||
`tournament_round_group`/`match`, kun meningsfull i shotgun.
|
||
Shotgun: alle grupper/matcher går ut på ETT felles tidspunkt
|
||
(intervallet meningsløst i denne modusen), sortert etter STARTHULL i
|
||
stedet for klokkeslett. 'consecutive' er uendret oppførsel, ingen
|
||
regresjon.
|
||
|
||
**Reell bug funnet OG rettet under scratch-verifisering:** begge
|
||
berørte frontend-skjermene (`round-groups-panel.tsx`,
|
||
`session-blind-draw.tsx`) hadde en hardkodet
|
||
`.sort((a,b) => a.sequence - b.sequence)` som stille overstyrte
|
||
backendens shotgun-sortering, uansett hva serveren faktisk sendte --
|
||
ville vist grupper/matcher i FEIL rekkefølge for enhver shotgun-runde
|
||
hvis den ikke var fanget opp. Rettet til å sortere etter starthull
|
||
når shotgun (inkludert LOKALE, ulagrede endringer -- en nettopp
|
||
justert starthull flytter gruppen i visningen umiddelbart).
|
||
|
||
Frontend: `round-groups-panel.tsx` (individuell, hånd-kodet fra
|
||
før) fikk en Løpende/Shotgun-veksler + per-gruppe starthull-felt.
|
||
`tournament-program.tsx` sin `EditSessionForm` (Cup) fikk samme
|
||
veksler + en tydelig modus-merkelapp i øktlisten.
|
||
`session-blind-draw.tsx` fikk et starthull-felt ved "Legg til
|
||
match" i shotgun-modus, vist på matchkortet både før og etter
|
||
avsløring.
|
||
|
||
**Testet, ikke bare gjennomgått:** 5 nye backend-tester (felles
|
||
tidspunkt uansett sequence, riktig starthull-sortering, override
|
||
vinner fortsatt, regresjonsvern for uendret 'consecutive'-
|
||
oppførsel) -- alle 163 backend-tester grønne mot scratch-database.
|
||
Frontend verifisert i en `tmp-preview`-rute som monterer den EKTE
|
||
`RoundGroupsPanel` med en STATEFUL `window.fetch`-mock (speiler
|
||
faktisk server-oppførsel) -- nettopp DENNE testingen fanget opp
|
||
sorterings-bug-en over. `tsc --noEmit` rent. Scratch-ruten slettet
|
||
etterpå, bekreftet med `git status --short`.
|
||
|
||
**Rullet ut 2026-08-21** -- bruker bekreftet migrasjon mot ekte
|
||
`teecup_db` ("ja"). Migrasjon 088 kjørt (4× `ALTER TABLE`, verifisert
|
||
med `information_schema`-spørring FØR og ETTER), deretter
|
||
`docker compose build teecup_api teecup_frontend && up -d`, begge
|
||
containere friske, rene logger.
|
||
|
||
150. **Utskriftsklare turneringsdokumenter koblet til ekte data, 2026-08-21
|
||
(ADR-097).** Oppfølging av #149 (shotgun-start ble bygget nettopp for
|
||
dette behovet). Bruker lastet opp V0-eksporten for de fire
|
||
utskriftsprompt-flatene (scorekort/startliste/cart-tags/
|
||
resultatliste, planlagt tidligere samme dag -- se FEATURE_BACKLOG.md
|
||
"utskrift av turneringsdokumenter"-planlegging). Eksporten var rene
|
||
visuelle mockups (mock-data, "Last ned PDF" var en blindvei) --
|
||
denne runden kobler alle fire til ekte API-data for begge
|
||
turneringsformer og gjør PDF-nedlastingen ekte.
|
||
|
||
Bruker spurte eksplisitt om en anbefaling for PDF-mekanisme ut fra
|
||
sluttresultat, ikke byggekostnad: valgte server-generert PDF via
|
||
headless Chromium (Playwright), som rendrer de EKTE Next.js-
|
||
utskriftssidene (samme React-komponent som forhåndsvisningen) i
|
||
stedet for nettleserens egen print-dialog eller en duplisert
|
||
server-side malmotor -- se ADR-097 for full begrunnelse. Ny
|
||
`POST /orgs/{organization_id}/print/pdf` (`app/routers/print_pdf.py`),
|
||
Playwright + Chromium lagt til `Dockerfile`/`requirements.txt`.
|
||
|
||
Fire nye sider (`app/tournaments/[id]/print/{scorecard,startlist,
|
||
cart-tags,resultlist}/page.tsx`) + fire komponenter, alle bygget mot
|
||
ekte join-mønstre kartlagt av en Explore-agent FØR koding (ingen
|
||
endepunkt gir en komplett startliste/scorekort alene -- 3-4 kall
|
||
joines klient-side). Delt `lib/print-data.ts` (typer + fetchere,
|
||
ekte gjenbruk på tvers av alle fire). Scorekortet generalisert fra
|
||
V0-mockupens ETT eksempelkort til én sheet PER UTSLAGSGRUPPE
|
||
(individuell) / PER MATCH (Cup) -- hele feltet i én utskrift, ikke
|
||
ett kort om gangen.
|
||
|
||
Inngangspunkter: `PrintMenu`-komponent i `round-groups-panel.tsx` sin
|
||
header (individuell), tre nye "Skriv ut ..."-oppføringer i
|
||
`tournament-program.tsx` sin økt-handlingsmeny (Cup), og en
|
||
"Skriv ut resultatliste"-lenke i begge leaderboard-visningene.
|
||
|
||
**Verifisert så langt:** `tsc --noEmit` rent på hele frontend-
|
||
prosjektet, hele `vitest`-suiten grønn (55 tester, ingen regresjon).
|
||
`python3 -m py_compile` rent på de nye backend-filene.
|
||
|
||
**Rullet ut 2026-08-21** -- bruker bekreftet, kjørt sammen med flere
|
||
andre endringer samme dag (se punkt 151-153). `docker compose build
|
||
teecup_api teecup_frontend && up -d`, begge containere friske, rene
|
||
logger, bekreftet via helsesjekk mot ekte `teecup.golf`.
|
||
**⚠️ ÉN ting fortsatt IKKE gjort:** ekte klikk-gjennom av selve
|
||
utskriftsknappene (alle fire sidene, bekreft faktisk PDF-nedlasting
|
||
fungerer) -- kun kodenivå-verifisering + containernes helsesjekk er
|
||
gjort, ikke en reell bruker-flyt-test av selve PDF-genereringen.
|
||
Bør gjøres FØR noen faktisk stoler på "Last ned PDF"-knappen.
|
||
denne runden.
|
||
|
||
151. **Rotårsak funnet for "banen er allerede importert"-forvirringen +
|
||
ærlig feilmelding lagt til, 2026-08-21.** Bruker rapporterte at
|
||
scorekortet for "Tjøme Invitational 2026" viste feil par på hull
|
||
2/5/11/14 (par 6/3/6/3 i stedet for 5/4/5/4). Sporet via en Explore-
|
||
agent + direkte spørring mot ekte `teecup_db` (bekreftet, ikke
|
||
gjettet): organisasjonen "Erol Haagenruds turneringer" har tre
|
||
manuelt håndlagde `custom`-baner ("Tjøme"/"Tjøme G&CC"/"Tjøme
|
||
Golfklubb – Bane", opprettet 2026-08-18) -- INGEN ekte offisiell
|
||
import -- og alle 4 berørte runder (begge "Klubbmesterskap 2026" og
|
||
begge "Tjøme Invitational 2026") pekte på den håndlagde varianten
|
||
med 4 feiltastede par-verdier.
|
||
|
||
**Den ekte rotårsaken, ikke bare symptomet:** teeoff sin egen
|
||
kildedata for "Tjøme Golfklubb – Hovedbanen" hadde en reell datafeil
|
||
-- hcp-indeks 13 var oppført på to hull, indeks 14 manglet helt.
|
||
Reprodusert direkte (kalte teeoff sitt eget `/api/facilities/
|
||
tjome-golfklubb`-endepunkt fra API-containeren) og bekreftet med en
|
||
tilbakerullet test-transaksjon mot ekte `teecup_db` (BEGIN → forsøkt
|
||
ekte innsetting med reelle data → ROLLBACK, ingen varig endring) at
|
||
selve databaseskjemaet var uskyldig -- innsettingen gikk knirkefritt
|
||
med KORREKT data. `import_official_course` sin validering sjekket at
|
||
hvert hull HADDE en hcp-indeks, men aldri at SETTET av 18 verdier
|
||
faktisk var 1-18 uten duplikater -- den ugyldige teeoff-dataen kom
|
||
derfor gjennom valideringen og feilet først på `hole`-tabellens
|
||
UNIQUE-constraint ved selve lagringen. Denne databasefeilen ble
|
||
fanget av den generiske `translate_db_errors()`-oversetteren og vist
|
||
til brukeren som "allerede importert til organisasjonen" -- NØYAKTIG
|
||
samme melding som et ekte duplikat-forsøk, en totalt misvisende
|
||
feilmelding som skjulte den ekte årsaken i en måned. Mest sannsynlige
|
||
hendelsesforløp: samme teeoff-datafeil blokkerte trolig importen
|
||
allerede 2026-08-18, brukeren fikk samme forvirrende melding, ga opp
|
||
og tastet banen inn for hånd i stedet -- og introduserte da sine
|
||
egne 4 skrivefeil underveis.
|
||
|
||
Bruker rettet selv kildedataen direkte hos teeoff (hull 16s indeks
|
||
korrigert 13→14). Bekreftet live etterpå (samme direkte kall mot
|
||
teeoff sitt API) at rettelsen faktisk slo gjennom.
|
||
|
||
**Kodefiks (uavhengig av teeoffs egen rettelse, forhindrer samme
|
||
misvisende feil for ENHVER fremtidig kilde-datafeil):** ny eksplisitt
|
||
validering i `import_official_course` OG `import_international_course`
|
||
(`app/routers/courses.py`) -- sjekker at hull-nummer- og hcp-indeks-
|
||
settene faktisk er 1..18 uten duplikater/hull FØR noe forsøkes
|
||
lagret, med en ny, ærlig feilkode (`EXTERNAL_DATA_INVALID`) og
|
||
melding som navngir det faktiske problemet i stedet for å late som
|
||
det er et duplikat. Frontend-feilhåndtering oppdatert på alle fire
|
||
stedene som kaller disse import-endepunktene: `individual-tournament-
|
||
detail.tsx` (individuell turnering), `tournament-program.tsx` (Cup-
|
||
format, både teeoff- og GolfAPI-import), og `course-template-
|
||
editor.tsx` sin "TeeOff-bane som mal"-flyt.
|
||
|
||
**Verifisert:** rå teeoff-data hentet på nytt etter brukerens
|
||
rettelse -- hcp-indeksene er nå korrekt 1-18. `tsc --noEmit` rent,
|
||
`python3 -m py_compile` rent på courses.py.
|
||
|
||
**Oppdatering samme dag -- full opprydning gjennomført og verifisert
|
||
mot ekte `teecup_db`.** Bruker rettet selv teeoff-dataen (hcp-indeks
|
||
16→14), men da banen ble hentet på nytt viste det seg at HELE
|
||
indeks-fordelingen var endret (10 av 18 hull), ikke bare det ene
|
||
hullet -- bruker bekreftet dette var bevisst, en fullstendig
|
||
oppdatert kortversjon, ikke en ny feil. Full backup av `teecup_db`
|
||
tatt først (`pg_dump` -- måtte bruke `teeoff_admin`-superbrukeren,
|
||
ikke `teecup_app`, siden RLS blokkerer en vanlig dump på tvers av
|
||
organisasjoner selv for eierens egen app-rolle). Under selve
|
||
rettingen viste det seg at en ekte offisiell import ALLEREDE hadde
|
||
lyktes i mellomtiden (kl. 13:44, trolig via appens egen "Hent bane
|
||
fra teeoff" da teeoff-dataen ble frisk) -- og at Klubbmesterskap
|
||
2026 sine to runder alt var gjenopprettet med nye ID-er, allerede
|
||
pekende korrekt på den nye banen, uten manuell inngripen. Kun de to
|
||
"Tjøme Invitational 2026"-rundene og 51 allerede innsjekkede
|
||
spilleres utslagstildelinger trengte faktisk manuell SQL-retting.
|
||
Bekreftet etterpå: alle 4 runder peker på riktig bane, alle 51
|
||
spilleres utslag stemmer med banen de faktisk spiller på, de tre
|
||
feilaktige duplikat-banene slettet, og par-tallene rettet i den ene
|
||
av de to lekkede `personal_course`-kopiene som faktisk hadde feilen
|
||
(den andre viste seg allerede å ha riktige tall -- sjekket FØR
|
||
retting i stedet for å anta begge trengte den samme fiksen).
|
||
Sidespørsmål avklart underveis: en tredje, urelatert organisasjon
|
||
("Tjøme Gents") har en egen import av samme bane med den GAMLE
|
||
indeks-fordelingen, brukt av en aktiv turnering der -- bruker
|
||
bekreftet den kun er en testturnering, rørt ikke.
|
||
|
||
**Gjenstående, ikke en feil, bare vanlig oppsett:** de tre nå tomme
|
||
rundene (Klubbmesterskap Runde 1+2, Tjøme Invitational Runde 2)
|
||
trenger spillere lagt til på nytt via "Spillere"/"Grupper og
|
||
startliste" -- de gamle deltaker-radene forsvant da Klubbmesterskap
|
||
sine runder ble gjenopprettet med nye ID-er.
|
||
|
||
152. **To reelle bugs funnet mens bruker testet offentlig deling av
|
||
Klubbmesterskap 2026, 2026-08-21.** Bruker satte turneringen til
|
||
"Offentlig" og delte URL-en rett fra egen adresselinje
|
||
(`/tournaments/{id}?org=...`) -- en ikke-innlogget mottaker fikk et
|
||
ødelagt Cup/lag-format-administrasjonsskjermbilde ("Lag og spillere",
|
||
"Klarte ikke å laste lag og spillere"), selv om turneringen er
|
||
individuell.
|
||
|
||
**Fiks A -- riktig lenke nå kopierbar.** Den ekte offentlige lenken
|
||
(`/t/{id}`) fantes fra før, men kun som ren, ikke-kopierbar tekst i
|
||
"Presentasjon"-fanen -- lett å bomme på til fordel for adresselinjens
|
||
egen (feil) URL. `tournament-presentation.tsx` fikk en ekte
|
||
kopier-knapp (samme mønster som `JoinCodeChip`).
|
||
|
||
**Fiks B -- ekte bug, uavhengig av feil lenke.**
|
||
`tournament-router.tsx` hentet turneringsformat med et innlogget-only
|
||
kall, og falt ved ENHVER feil (inkl. 401/ikke-innlogget) ubetinget
|
||
tilbake til Cup-format-adminskjermen -- uansett turneringens faktiske
|
||
format. Skiller nå eksplisitt 401 fra andre feil: en ikke-innlogget
|
||
besøkende på en organisator-URL sendes videre til den ekte offentlige
|
||
siden (`/t/{id}`) i stedet for å se en ødelagt adminskjerm.
|
||
|
||
Underveis oppdaget bruker et TREDJE, større hull: **org-individuelle
|
||
turneringer har ingen fungerende offentlig "Følg live"-side i det
|
||
hele tatt** -- allerede notert som bevisst utsatt i FEATURE_BACKLOG.md
|
||
("Flaggturnering Del B", 2026-08-14: "egen, større oppgave hvis/når
|
||
etterspurt"). Bruker: "gjør det ordentlig på første forsøk", V0 til
|
||
det visuelle. Grundig todelt analyse gjennomført (Cup-sidens fulle
|
||
funksjonssett + sanntidsmekanisme, ADR-027; den autentiserte
|
||
individuell-leaderboard-beregningens kostnad/offentlig-trygghet;
|
||
eksisterende offentlig runde-leaderboard som designreferanse) FØR
|
||
bygging, se ARCHITECTURE_DECISIONS.md for ADR (kommer når hele
|
||
funksjonen er ferdig).
|
||
|
||
**Backend bygget og importverifisert (`app.main` boot-testet i
|
||
kjørende container), rullet ut sammen med punkt 153 -- se der:**
|
||
- `PublicTournamentInfo` (`registration.py`) += `format_type` --
|
||
manglet før, landingssiden kunne ikke skille format offentlig.
|
||
- Ny `GET /public/tournaments/{id}/rounds` -- rundeliste med utledet
|
||
status (ikke startet/pågår/fullført; ingen statuskolonne finnes på
|
||
`tournament_round` selv, utledes fra `tournament_round_score`, samme
|
||
prinsipp som den eksisterende interne "dagens runde"-logikken).
|
||
- Ny `GET /public/tournaments/{id}/individual-leaderboard` --
|
||
gjenbruker `_compute_individual_standings` UENDRET (samme billige
|
||
4-spørrings-beregning som det autentiserte leaderboardet), fjerner
|
||
kun `player_id`. HCP-indeks lagt til bevisst (bruker valgte "ja,
|
||
vis HCP" da spurt eksplisitt -- samme presedens som frittstående
|
||
runders offentlige leaderboard allerede har).
|
||
- Sanntid: `broadcast_live_update(tournament_id)` koblet inn i
|
||
`_recompute_round_score` (individual_tournaments.py) -- kjøres etter
|
||
HVER hull-innsending uansett scoringsmetode, samme "delt funksjon
|
||
kringkaster selv"-mønster som Cup-formatets
|
||
`recompute_and_cache_match_state` (ADR-027). Gjenbruker SAMME
|
||
WebSocket-kanal (`/ws/public/tournaments/{id}/live}`) Cup-siden
|
||
allerede åpner -- ingen ny WS-infrastruktur.
|
||
- `public-tournament.tsx` sin "Program"-seksjon (viste før alltid en
|
||
tom Cup-økt-liste for individuelle turneringer) viser nå en
|
||
rundeliste med "Pågår nå" (samme fylte Radio-ikon-merke som
|
||
`watch-round.tsx`)/"Fullført"-merker.
|
||
|
||
**Gjenstår:** selve "Følg live"-siden sitt UI for individuelle
|
||
turneringer -- V0-prompt skrevet og sendt (rundevelger + leaderboard
|
||
med golfscore-formspråk fra DESIGN_SYSTEM.md, HCP synlig, mobil
|
||
først), venter på eksport. `/t/[id]/live` sin format-forgrening
|
||
(Cup uendret, individuell til den nye siden) kobles inn når
|
||
komponenten finnes.
|
||
|
||
`tsc --noEmit` rent, 55/55 vitest, ingen migrasjon (ingen nye
|
||
kolonner -- status utledes, lagres ikke).
|
||
|
||
153. **Offentlig "Følg live" for individuelle turneringer FERDIG BYGGET,
|
||
2026-08-21 (ADR-098) -- direkte fortsettelse av #152.** V0-eksporten
|
||
kom tilbake (`Temp-uploads/`, samme filnavnmønster som tidligere
|
||
zip-er denne uken -- innhold sjekket mot faktisk prompt, ikke
|
||
filnavnet). Høy troskap mot prompten, men mock-en modellerte hver
|
||
runde som sitt eget separate leaderboard -- matchet ikke den ekte,
|
||
KUMULATIVE beregningen (`_compute_individual_standings`). Rettet ved
|
||
integrering: rundevelgeren endrer ikke lenger leaderboard-radene,
|
||
kun hvilken rundes hull-for-hull som lastes ved utvidelse (se
|
||
ADR-098 Beslutning B for full begrunnelse).
|
||
|
||
Ny `GET .../rounds/{round_id}/participants/{tournament_participant_id}
|
||
/holes` (offentlig, nøkler på tournament_participant_id -- IKKE det
|
||
runde-interne tournament_round_participant_id) lagt til underveis,
|
||
egen ny rute (ikke gjenbruk av frittstående-runders tilsvarende --
|
||
ulik datamodell, ADR-098 Beslutning E). `PublicLeaderboardEntry` fikk
|
||
`rounds`-feltet lagt til (per-runde-celler, til fremtidig bruk).
|
||
|
||
`app/t/[id]/live/page.tsx` skrevet om til å avgjøre format SERVER-side
|
||
(enkelt `fetch()` mot `/public/tournaments/{id}`, samme mønster som
|
||
`app/logg-inn/page.tsx`) FØR noe rendres -- unngår
|
||
`tournament-router.tsx` sin klient-side-feilklasse fra #152 helt (en
|
||
individuell turnering kan aldri vise Cup-UI-et, ikke engang kortvarig).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent på hele frontend, 55/55 vitest,
|
||
backend importverifisert (`app.main` boot-testet med de nye rutene i
|
||
en kjørende container FØR selve bygget, kun for importsjekk). Ingen
|
||
unused imports (sjekket for hånd, samme rutine som utskriftsfunksjonen).
|
||
Ingen migrasjon.
|
||
|
||
**Ekte scratch-verifisering gjennomført FØR utrulling** (isolerte
|
||
containere på egne porter -- `teecup_api_scratch`/`_scratch2` mot ekte
|
||
`teecup_db` skrivebeskyttet, egen `next dev`-instans, IKKE de faktiske
|
||
produksjonscontainerne, som fortsatt betjente ekte trafikk hele tiden):
|
||
tomtilstand mot ekte data (alle 3 Klubbmesterskap-deltakere, korrekt
|
||
par-rekkefølge i hull-for-hull-utvidelsen -- bekreftet at rettelsen fra
|
||
punkt 151/152 faktisk holder), pluss en mocket "under spilling"-
|
||
tilstand (leder/delt plassering/cut-linje/DSQ/"Pågår nå"-puls) siden
|
||
ingen ekte runder er i gang ennå. Fant og rettet én reell sak
|
||
underveis: spillernavn ble avkortet med "..." på 360px-skjermer (under
|
||
lesbarhetsbarren, CLAUDE.md) -- rettet til linjebryting i stedet, re-
|
||
verifisert med et bevisst langt testnavn. Mørk modus og 390px+
|
||
(vanlig telefonbredde) OK uten endring.
|
||
|
||
**Rullet ut 2026-08-21** -- bruker bekreftet. `docker compose build
|
||
teecup_api teecup_frontend && up -d`, begge containere friske, rene
|
||
logger. Bekreftet direkte mot ekte `teecup.golf`: `/health` 200,
|
||
`/public/tournaments/{id}` returnerer `format_type: "individual"`,
|
||
`/t/{id}/live` svarer 200. Scratch-ressursene (containere, dev-
|
||
server) ryddet opp og bekreftet stoppet etterpå.
|
||
|
||
154. **Rett Thru-kolonnens vertikale justering i offentlig leaderboard,
|
||
2026-08-21 -- ekte bruk-funnet bug etter #153 sin utrulling.** Bruker
|
||
rapporterte fra faktisk mobilbruk (teecup.golf, live): THRU-etiketten
|
||
sto lavere enn I DAG/TOTAL. Årsak: Thru-verdien manglet samme
|
||
`min-h-10` som `ScoreShape` (brukt av I dag/Total) gir de andre
|
||
kolonnene -- hele Thru-kolonnen ble dermed lavere, senterte etiketten
|
||
ut av linje med søsknene sine. Rettet i
|
||
`public-individual-live.tsx`. Rullet ut sammen med punkt 155.
|
||
|
||
155. **Skjul HCP på offentlig leaderboard for rene bruttoslagspill-
|
||
turneringer, 2026-08-21.** Bruker: "når det er brutto som er
|
||
hovedkonkurransen trenger vi ikke se hcp i leaderboarden" -- HCP er
|
||
kun meningsfullt der den faktisk brukes til slagfordeling (netto/
|
||
stableford/Københavner/BBB), ikke ren brutto/eclectic-gross.
|
||
`PublicTournamentInfo` += `scoring_method` (manglet før på den
|
||
offentlige modellen), ny `shouldShowHcp(scoringMethod)`-hjelper i
|
||
`public-individual-live.tsx` (`GROSS_ONLY_METHODS = {"stroke_gross",
|
||
"eclectic_gross"}`). Bruker stilte oppfølgingsspørsmål om dette var en
|
||
systemendring (ja) -- eskalerte til den bredere parity-diskusjonen som
|
||
punkt 156 svarer på. Rullet ut sammen med punkt 154 (`docker compose
|
||
build teecup_api teecup_frontend && up -d`).
|
||
|
||
156. **Flight/gruppe-scoping i ScoreTab (ADR-099), 2026-08-21 -- steg 1
|
||
av en avtalt 8-punkts parity-plan.** Bruker: "Jeg skal i utgangspunktet
|
||
ha en identisk opplevelse når jeg fører score for meg (og eventuelt de
|
||
andre i flighten min) i en org-turnering som det jeg har når jeg fører
|
||
en individuell runde." Grundig Explore-undersøkelse kartla hele gapet
|
||
mellom org-turneringers scoring og frittstående runders scoring
|
||
(rapportert til bruker, som ba om rekkefølgen). Denne runden bygger
|
||
punkt 1: `ScoreTab` viste tidligere HELE turneringsfeltet i stedet for
|
||
brukerens egen flight -- `tournament_round_group` ble kun lest av
|
||
admin-parings-skjermen. Se ADR-099 for de fire beslutningene (visning
|
||
ikke autorisasjon; fallback til hele feltet uten gruppe; `is_self`
|
||
server-side, aldri rå `user_id`; et reelt RLS-hull på
|
||
`tournament_round_group` funnet og lukket underveis, migrasjon 089).
|
||
|
||
**Backend:** `RoundParticipantOut` += `tournament_round_group_id`,
|
||
`is_self` (tre spørringer i `individual_tournaments.py` utvidet:
|
||
`add_round_participant`, `list_round_participants`,
|
||
`update_round_participant_tee`). Ny migrasjon
|
||
`089_tournament_round_group_rls.sql`.
|
||
|
||
**Frontend:** `ScoreTab` (`individual-tournament-detail.tsx`)
|
||
defaulter pill-listen og `participantId`-valget til egen gruppe når
|
||
en finnes, med "Vis hele feltet"/"Vis kun min flight"-veksling og en
|
||
inline-merknad når runden mangler gruppeinndeling. Ingen ny V0-runde
|
||
-- filter+lenke lagt til en allerede eksisterende, V0-bygget skjerm.
|
||
|
||
**Verifisert:** 164/164 pytest (inkl. ny
|
||
`test_round_participant_flight_scoping.py` -- fanget en reell NULL-
|
||
håndteringsbug: `p.user_id::text = $2` ga SQL NULL, ikke `false`, for
|
||
spillere uten konto, rettet med `COALESCE`), `tsc --noEmit` rent.
|
||
RLS-policyen verifisert direkte (engangs scratch-db: `teecup_app`-
|
||
rollen med feil `app.current_org` satt fikk 0 rader, riktig org 1
|
||
rad). Full-stack scratch-verifisering (egen scratch-db, egen
|
||
API-container, egen isolert MinIO, ekte innlogging via
|
||
`/auth/login-password`, INGEN fetch-mocking -- et tidligere forsøk med
|
||
`window.fetch`-mocking i chrome-devtools-MCP-miljøet brøt selve
|
||
sidenavigasjonen på en måte som ikke var reproduserbar med ekte
|
||
nettverkstrafikk, byttet derfor til en ekte scratch-backend i stedet
|
||
for å grave videre i et miljøspesifikt verktøyproblem) bekreftet:
|
||
gruppert runde defaulter riktig til 3 av 6 (egen flight), veksling
|
||
fungerer begge veier, ugruppert runde faller korrekt tilbake med
|
||
riktig merknad, mørk modus og 390px mobilskjerm uendret lesbare.
|
||
Alle scratch-ressurser (API-/MinIO-container, db, rolle) ryddet opp
|
||
etter verifisering.
|
||
|
||
**Rullet ut 2026-08-21** -- bruker bekreftet. Migrasjon 089 kjørt
|
||
mot ekte `teecup_db` først (kun RLS-policy på
|
||
`tournament_round_group`, ingen dataendring -- lest av og verifisert
|
||
tomt før/etter, 1 rad uendret), deretter `docker compose build
|
||
teecup_api teecup_frontend && up -d`. Begge containere friske, rene
|
||
logger. `https://teecup.golf/health` 200 etter utrulling.
|
||
|
||
157. **Kjedet flerspiller-scoring i ScoreTab (ADR-100), 2026-08-22 --
|
||
steg 2 av parity-planen, direkte fortsettelse av #156.** V0-
|
||
eksporten (`Temp-uploads/tee-cup-login-screen.zip` -- filnavnet
|
||
stemte ikke med innholdet, sjekket nøye før bruk) kom tilbake, høy
|
||
troskap mot prompten. Integrert ved å beholde V0s NYE lag (hull-
|
||
navigator + kortliste + kontekst-rad + kjede-footer) men fylle
|
||
steg-INNHOLDET med de allerede eksisterende, mer fullverdige
|
||
komponentene fra `HoleStatsSheet` (omdøpt `TournamentScoringWizard`)
|
||
i stedet for V0s egne forenklede skjemakontroller -- samme prinsipp
|
||
som print-funksjonen og "Følg live" tidligere denne uken.
|
||
|
||
**Ingen backend-endring.** Gjenbruker eksisterende endepunkter
|
||
uendret (parallell henting per flight-medlem + samme PATCH som før).
|
||
|
||
**Tre reelle bugs funnet og rettet i scratch før utrulling** (se
|
||
ADR-100 Beslutning D for full detalj): (1) standardhull-valget
|
||
kunne låse seg til hull 1 pga. en race mot ufullstendig lastet data,
|
||
(2) auto-hopp mellom steg uteble for spiller 2+ i kjeden pga. en
|
||
stale-closure-lesning av forrige spillers utkast, (3) en
|
||
diskvalifisert spiller blokkerte "hull ferdig"-sjekken permanent
|
||
siden den daværende sjekken krevde registrering fra HELE flighten
|
||
i stedet for kun den faktisk score-bare kjeden.
|
||
|
||
**Verifisert:** 164/164 pytest (uendret), `tsc --noEmit` rent.
|
||
Full-stack scratch (egen db/API-container/MinIO, ekte innlogging,
|
||
ingen fetch-mocking) med en flight på 4 (blandet stat_level, én
|
||
DSQ) -- kjeding gjennom alle steg og alle spillere bekreftet riktig
|
||
ETTER de tre rettelsene, DSQ hoppet korrekt over i kjeden men
|
||
fortsatt synlig som kort, "Vis hele scorekortet" (steg 1) uendret,
|
||
mørk modus OK. Alle scratch-ressurser ryddet opp.
|
||
|
||
**Rullet ut 2026-08-22** -- bruker bekreftet. Ingen migrasjon (ingen
|
||
backend-endring). `docker compose build teecup_api teecup_frontend
|
||
&& up -d`, begge containere friske, rene logger.
|
||
`https://teecup.golf/health` 200 etter utrulling.
|
||
|
||
158. **Hull-for-hull-utvidelse i den autentiserte leaderboard-fanen
|
||
(ADR-101), 2026-08-22.** Bruker, mens forrige runde pågikk: klikk
|
||
på en spiller skal ekspandere raden og vise scoren på hvert hull,
|
||
pluss sum per runde og for ut/inn. `LeaderboardTab` hadde utvidelse
|
||
kun for eclectic -- slagspill/netto/Stableford (StrokePlayLeaderboard,
|
||
den vanligste gruppen) hadde ingen. Mønsteret fantes allerede,
|
||
ferdig bygget, for den OFFENTLIGE siden (`public-individual-
|
||
live.tsx` sin LeaderboardRow/HoleNine) -- portert derfra (ingen ny
|
||
V0-runde), ikke bygget fra bunnen.
|
||
|
||
**Backend, to små tillegg, ingen migrasjon:** `RoundCellOut` +=
|
||
`tournament_round_id`. Nytt endepunkt
|
||
`.../individual-leaderboard/{tournament_participant_id}/rounds/
|
||
{round_id}/holes`, speiler registration.py sitt offentlige
|
||
endepunkt (eget stiprefiks -- unngår kollisjon med det eksisterende
|
||
round_participant_id-nøkkede).
|
||
|
||
**Frontend:** `StrokePlayLeaderboard` sin `LeaderboardRow`-type
|
||
("eksakt kontrakt") UENDRET -- nytt valgfritt prop-sett på selve
|
||
komponenten i stedet (`expandedId`/`onToggleExpand`/
|
||
`renderExpanded`), forelderen eier state/henting. Kun felt-rader
|
||
(ikke ledere) er utvidbare -- unngår risiko i den sticky
|
||
leder-geometrien. Nye `LeaderboardHoleNine`/`LeaderboardRoundHoles`
|
||
bruker filens egne netto-par-bevisste `classify`/`scoreMarkClasses`
|
||
(samme som HoleGrid), ikke den offentlige sidens enklere variant.
|
||
|
||
**Verifisert:** 165/165 pytest (+1 ny), `tsc --noEmit` rent.
|
||
Full-stack scratch (egen db/API-container/MinIO, ekte innlogging,
|
||
ingen fetch-mocking) med en flerrunde-turnering (2 runder, én
|
||
spiller begge, én kun runde 1) -- riktig Ut/Inn-seksjon(er), riktig
|
||
Par-/Slag-sum, riktig golfscore-form, mørk og lys modus, smal
|
||
mobilskjerm. Alle scratch-ressurser ryddet opp.
|
||
|
||
**Rullet ut 2026-08-22** -- bruker bekreftet. Ingen migrasjon.
|
||
`docker compose build teecup_api teecup_frontend && up -d`, begge
|
||
containere friske, rene logger. `https://teecup.golf/health` 200
|
||
etter utrulling.
|
||
|
||
159. **Gap-analyse skrevet ned: scoring-paritet frittstående runde vs.
|
||
org-turnering, 2026-08-22 -- ren dokumentasjon, ingen kode.** Bruker
|
||
ba om at den tidligere "avtalte 8-punkts parity-rekkefølgen" (nevnt
|
||
kun muntlig, referert i ADR-099/100) ble gjort grundig på nytt og
|
||
denne gangen faktisk skrevet ned, siden listen aldri ble committet
|
||
til noen fil -- et hull i egen dokumentasjonsdisiplin (CLAUDE.md-
|
||
regelen om å holde disse filene oppdatert er noe JEG må huske å
|
||
gjøre når arbeid fullføres, ikke en automatisk mekanisme). Grundig
|
||
kodesøk (Explore-agent) sammenlignet `round-detail.tsx`/`rounds.py`
|
||
mot `individual-tournament-detail.tsx`/`individual_tournaments.py`
|
||
på tvers av åtte konkrete kandidat-funksjoner (offline-kø, live
|
||
push, stats-dashboard, full flerspiller-scorekort-grid, "plukket
|
||
opp"-knapp, GPS-skuddmåling, kommentartråd, tilskuer-metrikkveksling)
|
||
pluss én i motsatt retning (print/eksport). Bekreftet at ADR-099/
|
||
100/101 samt rangefinder (ADR-064), Flagg-GPS (ADR-066) og kombinert
|
||
hull-historikk (ADR-071) allerede er reell paritet og ikke skal
|
||
fanges opp på nytt. Full liste, med filnavn/antatt størrelse per
|
||
punkt, skrevet inn i FEATURE_BACKLOG.md ("Scoring-paritet:
|
||
frittstående runde vs. org-turnering") -- erstatter den tidligere
|
||
muntlige listen. Ingen prioritering/bygging startet -- venter på at
|
||
bruker velger rekkefølge blant punktene.
|
||
|
||
160. **Rundespesifikk kommentartråd for org-turneringer (ADR-102),
|
||
2026-08-22 -- punkt 7 i gap-listen, valgt før punkt 6 (GPS-
|
||
slagmåling, som avhenger av et delingsmål).** Bruker bekreftet
|
||
(AskUserQuestion) at dette skal være en EKTE parallell til
|
||
frittstående runders `RoundMessages` (ADR-044) -- ikke en utvidelse
|
||
av den eksisterende, hele-turnering-brede Banter Board (ADR-025) --
|
||
og at tråden skal være ÉN per turneringsrunde (hele feltet deler
|
||
tråd, ikke per flight, til tross for at en turneringsrunde kan ha
|
||
langt flere deltakere enn en frittstående runde).
|
||
|
||
**Ny migrasjon `090_tournament_round_message.sql`:**
|
||
`tournament_round_message`/`_reaction`/`_comment`, speiler
|
||
`round_message`/`round_message_reaction`/`round_message_comment`
|
||
(058/059) strukturelt, men MED `organization_id` + full RLS
|
||
(org-tenant-invariant) i stedet for frittstående sidens RLS-frie
|
||
mønster.
|
||
|
||
**Backend:** ny fil `app/routers/tournament_round_messages.py`
|
||
(speiler `round_messages.py` endepunkt-for-endepunkt, org-scopet).
|
||
Lese-/skriverett = org-medlemskap (`get_authorized_org`, samme
|
||
presedens som runde-/deltaker-OPPSETT ellers i
|
||
`individual_tournaments.py`). Moderering: forfatter ELLER org-admin
|
||
(`is_org_admin`), speiler Banter Board. Bilde-opplasting gjenbruker
|
||
`storage.py` sin MinIO+AVIF-pipeline uendret.
|
||
|
||
**Bevisst utenfor omfang i v1** (se ADR-102 Beslutning E): ingen
|
||
@-tagging (samme presedens som `team-chat.tsx`/`public-
|
||
tournament.tsx`s bruk av `PostEngagement`), ingen WebSocket-/
|
||
sanntid-piggyback (den autentiserte turneringsvisningen har ingen
|
||
WS-tilkobling ennå -- eget, uprioritert gap). Reaksjoner + trådede
|
||
kommentarer ER med.
|
||
|
||
**Frontend:** ny `frontend/components/tournament-round-messages.tsx`
|
||
(adaptert `round-messages.tsx`), montert som en kollapsbar
|
||
"Kommentarer"-seksjon i `ScoreTab`, samme mønster som "Vis hele
|
||
scorekortet" (ADR-100). `frontend/lib/offline-queue.ts` gjenbrukt
|
||
UENDRET (bekreftet 100% generisk).
|
||
|
||
**Verifisert:** 177/177 pytest (12 nye -- hele autorisasjonsmatrisen,
|
||
RLS, idempotens), `tsc --noEmit` rent. Full-stack scratch (egen
|
||
db/API-/MinIO-container, ekte innlogging -- inkludert et ekte
|
||
TOTP-2FA-oppsett for å teste admin-moderering i nettleseren, ikke
|
||
bare i pytest) bekreftet: tekst+bilde postet (AVIF-konvertering
|
||
verifisert direkte mot scratch-MinIO), reaksjon, trådet svar,
|
||
forfatter sletter egen, org-admin sletter en annens, offline-kø
|
||
ende-til-ende (frakoblet -> post -> "venter på synk" -> tilkoblet ->
|
||
automatisk synk -> riktig serverstate), mørk modus + smal
|
||
mobilskjerm. Alle scratch-ressurser ryddet opp.
|
||
|
||
**Rullet ut 2026-08-22** -- bruker bekreftet. Migrasjon 090 kjørt
|
||
mot ekte `teecup_db` (kun tre nye tabeller, ingen endring av
|
||
eksisterende data -- bekreftet tom rett etter kjøring). `docker
|
||
compose build teecup_api teecup_frontend && up -d`, begge
|
||
containere friske, rene logger. `https://teecup.golf/health` 200
|
||
etter utrulling.
|
||
|
||
161. **Slag-for-slag GPS-avstandsmåling for org-turneringer (ADR-103),
|
||
2026-08-22 -- punkt 6 i gap-listen, bygget etter punkt 7 (ADR-102)
|
||
fordi delingsfunksjonen forutsatte kommentartråden.** Bruker
|
||
bekreftet før planen ble godkjent at avstand-til-green/hindringer
|
||
(rangefinderen) allerede er dekket (ADR-064/083, delt komponent,
|
||
live siden 2026-08-17) -- denne runden dekker den atskilte, frie
|
||
slag-for-slag-målingen (utslag→ball, ADR-048), ikke faste
|
||
banepunkter.
|
||
|
||
**Ny migrasjon `091_tournament_round_shot.sql`:** speiler
|
||
`round_shot` (060/061) strukturelt, org-scopet (RLS), FK-et direkte
|
||
til `tournament_round_participant` + `hole_number` (IKKE
|
||
`tournament_round_hole_id` -- den raden finnes kanskje ikke ennå,
|
||
siden `tournament_round_hole` først opprettes ved første
|
||
score-innsending, ulikt frittstående `round_hole`).
|
||
|
||
**Backend:** ny fil `app/routers/tournament_round_shots.py`.
|
||
Autorisasjon speiler `update_hole` (self ELLER org-admin) for
|
||
skriving -- strengere enn ADR-102s "org-medlem"-nivå, siden dette
|
||
er samme sensitivitetsklasse som selve scoringen. Deling reuser den
|
||
NYE rundespesifikke kommentartråden (ADR-102), ikke Banter Board --
|
||
lokal kopi av `rounds.py::share_shot` sitt Mapbox-snippet-mønster
|
||
(samme "ingen kryssimport mellom subsystemene"-presedens).
|
||
|
||
**Frontend:** `ShotMeasurementSheet`/`MapPointPicker` (Mapbox GL
|
||
JS + GPS) var allerede 100 % generiske -- gjenbrukt UENDRET.
|
||
`ShotMeasurementEntry` generalisert fra en lokal `round-detail.tsx`-
|
||
funksjon til ny delt `frontend/components/shot/shot-measurement-
|
||
entry.tsx` (eksplisitte URL-props i stedet for `roundId`+`owner`) --
|
||
de tre eksisterende frittstående-kallstedene fikk kun en
|
||
signaturomskrivning, atferd uendret. Ny bruk i
|
||
`TournamentScoringWizard`s "direction"-steg (samme sted som
|
||
round-detail.tsx sin plassering) -- dekker BÅDE kjedet flerspiller-
|
||
scoring OG `HoleGrid` sitt enkelt-spiller-kall siden begge deler
|
||
samme wizard-komponent.
|
||
|
||
**Verifisert:** 188/188 pytest (11 nye), `tsc --noEmit` rent.
|
||
Full-stack scratch MED ekte Mapbox-tokens (ekte innlogging, ingen
|
||
fetch-mocking): GPS-flyten testet via injisert
|
||
`navigator.geolocation`-overstyring (samme arbeidsomgåelse som
|
||
ADR-083 for headless Chrome), kart-velg-punkt testet med et EKTE
|
||
Mapbox-kart. Slag målt (begge metoder), kølle+avstand riktig,
|
||
deling opprettet en ekte `tournament_round_message` med et ekte
|
||
satellittbilde (14 444 byte AVIF, verifisert direkte mot
|
||
scratch-MinIO), "Delt"-merkelapp korrekt, sletting av et slag lot
|
||
den allerede delte kommentaren stå urørt, mørk modus + smal
|
||
mobilskjerm. Alle scratch-ressurser ryddet opp.
|
||
|
||
**Rullet ut 2026-08-22** -- bruker bekreftet. Migrasjon 091 kjørt
|
||
mot ekte `teecup_db` (kun én ny tabell, `tournament_round_shot`,
|
||
ingen endring av eksisterende data -- bekreftet tom rett etter
|
||
kjøring). `docker compose build teecup_api teecup_frontend && up
|
||
-d`, begge containere friske, rene logger.
|
||
`https://teecup.golf/health` 200 etter utrulling.
|
||
|
||
162. **Fullt flerspiller-scorekort-grid + offline-kø for score-føring i
|
||
ScoreTab (ADR-104), 2026-08-22 -- punkt 4 og 1 i gap-listen, bedt om
|
||
samtidig siden punkt 1 er en liten oppgave.** Ren frontend-runde,
|
||
ingen backend-endring.
|
||
|
||
**Punkt 4:** "Vis hele scorekortet" viste tidligere kun en navne-
|
||
pille-rad + ett enkelt-spiller-`HoleGrid` (bytt HVEM som vises). Ny
|
||
`TournamentScorecardGrid` speiler `round-detail.tsx` sin
|
||
`ScorecardGrid` strukturelt (sticky spillerkolonne, Ut/Inn/Sum), men
|
||
bruker denne filens EGNE `classify`/`scoreMarkClasses` for visuell
|
||
konsistens med flight-kortlisten. `HoleGrid` (kun ett kallsted)
|
||
slettet -- cellene tapper direkte inn i den eksisterende
|
||
`TournamentScoringWizard`-mounten (setter `activeHole` +
|
||
`wizardPlayerId`), ingen ny wizard-instans. Hente-effekten for
|
||
`holesByParticipant` utvidet til å dekke hele det synlige feltet,
|
||
men kun når seksjonen faktisk er åpen.
|
||
|
||
**Punkt 1:** `updateHoleFull` hadde ingen offline-kø-kobling --
|
||
speiler nå `round-detail.tsx` sitt `queueParticipantHoleWrite`/
|
||
`updateWizardStat`-mønster (ADR-028/057/060) nøyaktig, samme delte
|
||
`frontend/lib/offline-queue.ts` uendret. Samme banner-språk, samme
|
||
tre synk-utløsere (online-event, mount-sjekk, manuell knapp). 409-
|
||
håndtering lagt til for første gang i denne filen (manglet helt før).
|
||
|
||
**Verifisert:** `tsc --noEmit` rent, 188/188 pytest uendret (ingen
|
||
backend rørt). Full-stack scratch (egen db/API-/MinIO-container,
|
||
ekte innlogging, ingen fetch-mocking) med en flight på 3 spillere:
|
||
gridet viser alle samtidig med riktig Ut/Inn/Sum/golfscore-form,
|
||
tapp på annen spiller enn aktiv åpner riktig wizard for nøyaktig den
|
||
spilleren+hullet, et reelt 403 (selv-only-avvisning) bekreftet
|
||
uendret riktig. Offline-kø: frakoblet → registrert → banner +
|
||
optimistisk oppdatering uten feil → tilkoblet → automatisk synk →
|
||
bekreftet direkte mot databasen at BÅDE det online og det kølagte
|
||
hullet ble lagret riktig. Mørk modus + smal mobilskjerm. Alle
|
||
scratch-ressurser ryddet opp.
|
||
|
||
**Rullet ut 2026-08-22** -- bruker bekreftet. Ingen migrasjon --
|
||
kun `docker compose build teecup_frontend && up -d`, container
|
||
frisk, rene logger. `https://teecup.golf/health` 200 etter
|
||
utrulling.
|
||
|
||
163. **Personlig statistikk-dashboard blander inn org-turnering-runder
|
||
(ADR-105), 2026-08-22 -- punkt 3 i gap-listen, bedt om rett etter
|
||
ADR-104, med punkt 2 (live push) varslet som neste.** `GET
|
||
/rounds/stats/summary` (`/my-rounds/stats`) aggregerte tidligere KUN
|
||
frittstående runder -- en spillers GIR%/putting/fairway-trender fra
|
||
org-turneringer vistes aldri der.
|
||
|
||
**Design-avklaring (AskUserQuestion, bekreftet):** bland tallene inn
|
||
i de eksisterende trend-/snitt-tallene (ingen visuell markering),
|
||
men vis kilde-antall separat -- speiler `hole_history.py` (ADR-072)
|
||
sin `times_personal`/`times_tournament`. Ny `RoundStatsSummary`-felt
|
||
`rounds_completed_personal`/`rounds_completed_tournament`, vist som
|
||
"(X frittstående + Y turnering)" i Oversikt-ingressen.
|
||
|
||
**Backend:** ny `_fetch_tournament_stats_rows()` i `rounds.py`,
|
||
speiler `hole_history.py` sin N+1-per-org-henting
|
||
(`player_organizations_for_user` → `org_connection` per org), uten
|
||
course-key-filtreringen (ikke nødvendig her). Resultatet slås inn i
|
||
de SAMME `by_round`/`stat_level_by_round`/`played_at_by_round`-
|
||
dict-ene som personlige rader (namespacet nøkkel `f"t:{trp_id}"`) --
|
||
`_resolve_stats_window`/`_build_round_series`/`_summarize_rounds`
|
||
trengte INGEN endring i selve regnelogikken (kun ett nytt
|
||
`source_by_round`-parameter til `_summarize_rounds` for de to nye
|
||
kilde-antall-feltene). `tournament_round` har ingen `completed_at`
|
||
-- "fullført" avgjøres ved radtelling mot
|
||
`played_hole_numbers(hole_config)`, samme prinsipp
|
||
`individual_tournaments.py` bruker for flaggturnering-resultatet.
|
||
Rader uten `tr.scheduled_at` ekskluderes helt (kan ikke plasseres i
|
||
et tidsvindu uten dato). `ordered_rounds`-sorteringen måtte gjøres
|
||
eksplisitt (kunne før stole på SQL-ens `ORDER BY` som
|
||
innsettingsrekkefølge -- stemmer ikke lenger etter sammenslåing av
|
||
to kilder).
|
||
|
||
**Frontend:** `ApiStatsSummary`-typen fikk de to nye feltene,
|
||
`rounds-stats-summary.tsx` sin "Aggregert over N fullførte
|
||
runder"-setning utvidet med kilde-parentesen når
|
||
`rounds_completed_tournament > 0`. Ingen ny komponent.
|
||
|
||
**Fant og rettet underveis i full-stack scratch-verifiseringen:**
|
||
en første one-off scratch-frontend-container ble bygget UTEN riktig
|
||
`--build-arg TEECUP_API_ORIGIN` -- `next.config.mjs` sin
|
||
`rewrites()` leses ved BUILD-tid for standalone-output (dokumentert
|
||
i `frontend/Dockerfile`s egen kommentar), IKKE ved
|
||
container-oppstart som antatt. Containeren proxyet derfor tre
|
||
innloggingsforsøk til den EKTE, kjørende `teecup_api` (2×422 ugyldig
|
||
e-postdomene, 1×401 ukjent testbruker) før feilen ble oppdaget.
|
||
Ingen skriving til ekte `teecup_db` skjedde (begge feilkodene
|
||
inntreffer før/uten DB-skriving; rate-limiteren er en ren
|
||
in-memory-teller) -- containeren ble stanset umiddelbart og
|
||
brukeren varslet direkte i økten. Rettet ved å bygge
|
||
scratch-frontend-imaget på nytt med riktig
|
||
`--build-arg TEECUP_API_ORIGIN` pekt mot scratch-API-containeren.
|
||
|
||
**Verifisert:** 5 nye pytest-tester (kildeblanding,
|
||
strokes_only-turneringsrunde ekskludert fra putt-snittet, ufullført
|
||
turneringsrunde ekskludert, runde uten `scheduled_at` ekskludert,
|
||
RLS -- runde i en org brukeren ikke er spiller i ekskludert) + full
|
||
regresjonssuite, 193/193 grønt. `tsc --noEmit` rent. Full-stack
|
||
scratch (egen db + to one-off-containere for
|
||
teecup_api/teecup_frontend, ekte innlogging) med en bruker som har
|
||
runder i TO ulike organisasjoner + én frittstående runde: dashbordet
|
||
viste riktig "3 fullførte runder (1 frittstående + 2 turnering)",
|
||
riktig blandet snitt/GIR/putt, ingen konsollfeil. Mørk modus + smal
|
||
mobilskjerm (375px) bekreftet lesbart. Alle scratch-ressurser (db,
|
||
rolle, containere, images) ryddet opp.
|
||
|
||
**Rullet ut 2026-08-22** -- bruker bekreftet. Ingen migrasjon --
|
||
`docker compose build teecup_api teecup_frontend && up -d`, begge
|
||
containere friske, rene logger. `https://teecup.golf/health` 200
|
||
etter utrulling.
|
||
|
||
164. **Flytende "Registrer score" sluttet aldri å vises igjen etter
|
||
spillerlisten -- dekket over "Kommentarer"-seksjonen, 2026-08-23.**
|
||
Bruker viste to skjermdumper (desktop + mobil): scrollet helt ned
|
||
til kommentartråden, den flytende knappen hang fortsatt igjen nederst
|
||
og dekket delvis over "Kommentarer (1)"-overskriften.
|
||
|
||
Rot-årsak: en stale vakt igjen fra FØR #146 (se punkt over/CHANGELOG
|
||
#146) frikoblet knappens synlighets-TRIGGER fra `mapExpanded` (byttet
|
||
til `inFlowButtonVisible`) -- men selve `playerListReached`-
|
||
MÅLE-effekten (`round-detail.tsx`) beholdt sin egen, aldri fjernede
|
||
`if (!mapExpanded) return`-vakt (linje 6448) fra FØR den omleggingen.
|
||
Siden banekartet er kollapset som standard (`mapExpanded = false`,
|
||
"kompakt som standard"-designet), kjørte scroll/resize-lytteren for
|
||
`playerListReached` ALDRI for de aller fleste brukere -- tilstanden
|
||
forble fastlåst på sin initielle `false` for hele økten. Synlighets-
|
||
betingelsen `inFlowButtonVisible || playerListReached` degraderte
|
||
dermed reelt til bare `inFlowButtonVisible`: så snart brukeren
|
||
scrollet forbi selve side-flyt-knappen tidlig på siden, dukket den
|
||
flytende knappen opp -- og forsvant ALDRI igjen resten av siden
|
||
(spillerliste, alle hullkort, og til slutt "Kommentarer" lengst
|
||
ned), fordi mekanismen som skulle skjule den igjen ("nådd
|
||
spillerlisten") aldri fikk kjøre i utgangspunktet.
|
||
|
||
Rettet ved å fjerne `if (!mapExpanded) return`-vakten helt (samme
|
||
linje) og endre effektens avhengighetsliste fra `[mapExpanded]` til
|
||
`[]` -- måler nå alltid, uansett kart-tilstand, akkurat som
|
||
kode-kommentaren over effekten allerede beskrev at oppførselen SKULLE
|
||
være ("forblir sant BÅDE mens listen vises OG etter at brukeren har
|
||
scrollet helt forbi den").
|
||
|
||
**Verifisert i full-stack scratch** (egen db + to one-off-containere,
|
||
ekte innlogging, en frittstående runde med 3 spillere + 6
|
||
kommentarer for å gi siden nok høyde til å reprodusere scenarioet
|
||
nøyaktig): `evaluate_script`-målt `aria-hidden` på selve den flytende
|
||
knappens DOM-element gjennom hele scrollet -- korrekt skjult mens
|
||
side-flyt-knappen er synlig, korrekt skjult idet spillerlisten nås,
|
||
og BEKREFTET FORTSATT skjult helt ned til og forbi
|
||
"Kommentarer"-seksjonen (der bildet av bruker viste den låst synlig
|
||
før fiksen) -- reprodusert med skjermbilder identisk til brukerens
|
||
egne. Scroll tilbake opp bekreftet at knappen dukker opp igjen som
|
||
forventet når spillerlisten forlates oppover. `tsc --noEmit` rent,
|
||
ingen backend-endring. Alle scratch-ressurser ryddet opp.
|
||
|
||
**Ikke rullet ut ennå** -- venter på brukerens bekreftelse.
|