Del C (ADR-068, migrasjon 072, siste del av den tredelte utvidelsen som startet med Flaggturnering GPS/kart, se ADR-066/067): nytt format eclectic_gross/eclectic_net/eclectic_stableford -- beste resultat per hull på tvers av en turnerings egne runder, krever samme bane (avvist tydelig ved rundeopprettelse ellers). Regnes ut ved lesing, ingen nye tabeller. Bevisst avvik fra opprinnelig plan: integrert som en ny gren i eksisterende individual-leaderboard-endepunkt fremfor et nytt eget endepunkt -- se ADR-068 for begrunnelsen. Tre ikke-relaterte, brukerrapporterte UI-rettelser tatt med i samme runde: avstandsindikatoren brukte "grønn"/"Midt" i stedet for riktige golf-uttrykk "green"/"senter", og "Oppdateres live"-badgen fjernet. "Antall hull"-bryteren i Ny runde-veiviseren fikk samme grønne aksent-valgt-stil som resten av samme skjerm (delt Segmented-primitiv). Se ARCHITECTURE_DECISIONS.md (ADR-068) og CHANGELOG.md (punkt 84) for full begrunnelse og verifiseringslogg. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4530 lines
275 KiB
Markdown
4530 lines
275 KiB
Markdown
# TeeCup — Funksjons-backlog
|
||
|
||
> Formål: fange ALT som er ønsket for TeeCup på ett sted, slik at intet krav går
|
||
> tapt mellom økter, modeller eller samtaler. Kilder: de to opprinnelige
|
||
> Gemini-samtalene (funksjonalitet + Docker) og arbeidet gjort med Claude.
|
||
>
|
||
> Status-koder: ✅ ferdig · 🔨 pågår · 📋 planlagt/fanget · ❓ trenger beslutning
|
||
> · 🔀 endret fra opprinnelig råd · 💤 utsatt (bevisst)
|
||
>
|
||
> Sist oppdatert: 2026-08-14 (stale-opprydding — seks seksjoner rettet
|
||
> til å reflektere faktisk levert status, se CHANGELOG.md samme dato)
|
||
|
||
---
|
||
|
||
## Fundament (bygget)
|
||
|
||
| Funksjon | Status | Notat |
|
||
|---|---|---|
|
||
| Handicap-/matchmotor (testet) | ✅ | 24 tester, R&A-verifisert. Erstatter Geminis løse JS-funksjon. |
|
||
| Formater: singles, fourball, foursome, greensome, scramble | ✅ | I motoren, allowance som konfig. |
|
||
| Brutto/netto + prosent-allowance (75 %, 90 %, 3/4) | ✅ | ADR-005. Prosentene bekreftet mot Golf GameBook (ADR-014). |
|
||
| 9-hulls (front/back) slagfordeling | ✅ | ADR-008, egen funksjon + test. |
|
||
| Miksede formater per turnering (økter) | ✅ | ADR-007. Ryder Cup-strukturen. |
|
||
| Spiller-pool m/ reserver, delvis deltakelse | ✅ | ADR-007. |
|
||
| Multi-tenant skjema + RLS | ✅ | ADR-001/003. Isolasjon bevist med oppførselstest. |
|
||
| Dedikert app-rolle (ingen superuser/BYPASSRLS) | ✅ | migrasjon 002. |
|
||
| API: oppsett (spillere/lag/roster/økter/matcher/blind draw) | ✅ | Verifisert for ekte mot scratch-db + engangscontainer. |
|
||
| API: scoring (hole_score/match_hole_result, matchstatus, handicap-beregning) | ✅ | ADR-012/014. Verifisert for ekte, inkl. fourball better-ball og race-sikker recompute. |
|
||
| Banedata fra teeoff via API | ✅ | ADR-004 → **ADR-019**, LIVE 2026-07-18. Import (kopi) ved eksplisitt organisator-valg, ikke live oppslag ved hver bruk. Se egen seksjon under. |
|
||
| Konfigurerbar handicap-pipeline (4 brytere) | ✅ | ADR-014. Bygget i `app/handicap.py`, brukt av scoring-runden. |
|
||
| Ekte autentisering (magic-link + JWT-sesjon) | ✅ | ADR-009. `app/routers/auth.py` + migrasjon `004_auth.sql`. `X-Debug-User-Id`-stubben er helt fjernet. |
|
||
| RLS-tomstreng-fiks (`app_current_org()`) | ✅ | Migrasjon `005_rls_null_guard.sql`. Se detaljer under. |
|
||
| Organisasjon-bootstrap (opprette ny org via API) | ✅ | `POST /orgs`, `app/routers/organizations.py`. Se detaljer under. |
|
||
| Ekte SMTP-utsending av magic-link | ✅ | `app/email.py`. Se detaljer under. |
|
||
| Containerisert, LIVE på `teecup.teeoff.no` | ✅ | `Dockerfile` + `docker-compose.yml`. Se egen seksjon under — to reelle driftshendelser funnet og rettet. |
|
||
| Dato/klokkeslett på turnering/økt/match | ✅ | ADR-015. `session.scheduled_at` + `tee_interval_minutes` → utledet `match.tee_time`. Se egen seksjon under. |
|
||
| Maskinlesbar feilkode-kontrakt (`{"code",...}`) | ✅ | ADR-015. Full retrofit, alle 39 tidligere steder. Se egen seksjon under. |
|
||
| i18n-forberedelse (nb/en) | ✅ | ADR-015. `preferred_locale` + ekte engelsk e-postmal. Se egen seksjon under. |
|
||
| Frontend: innlogging + verifisering, LIVE | ✅ | ADR-016. Next.js på `teecup.teeoff.no`, ekte magic-link-flyt bevist med reell e-post. Se egen seksjon under. |
|
||
| Frontend: dashboard (org-bytter/-opprettelse + turneringsliste), LIVE | ✅ | `/dashboard`. Kablet mot `/auth/me`, `/orgs`, `/orgs/{id}/tournaments`. Skrive-flyt bekreftet med ekte data (org "Tjøme Gents" + turnering opprettet av bruker). |
|
||
| Frontend: lag/roster-skjerm, LIVE | ✅ | `/tournaments/[id]`. To nye backend-endepunkter bygget samtidig (`PATCH`/`DELETE` roster). Skrive-flyt ikke testet med ekte data ennå. |
|
||
| Selvregistrering + utvidet spillerprofil (API) | ✅ | ADR-017. `app/routers/registration.py` (offentlig, uautentisert), utvidet `players.py`, e-post-basert `player.user_id`-kobling ved innlogging. Backend komplett og live. Frontend-påmeldingsskjema/landingssider gjenstår (egen ADR-runde). |
|
||
| Roster: endre kaptein / fjern spiller (`PATCH`/`DELETE`) | ✅ | `app/routers/tournaments.py`. Bevisst ingen "kun én kaptein"-håndhevelse ennå — se «Brukerroller»-punktet under. |
|
||
| Egendefinerte baner (`course`) via API | ✅ | Nytt `app/routers/courses.py` (`GET`/`POST /orgs/{id}/courses`). Kun `source='custom'` — ADR-004s teeoff-integrasjon (`source='official'`) fortsatt kun vedtatt, ikke bygget. Se egen seksjon under. |
|
||
| Frontend: program-skjerm (økter/tidsplan), LIVE | ✅ | `/tournaments/[id]/program`. Se egen seksjon under. |
|
||
|
||
---
|
||
|
||
### Frontend: innlogging + verifisering — ✅ BYGGET OG VERIFISERT LIVE 2026-07-17
|
||
- Første frontend-skjerm i prosjektet. Designet i V0 (login-skjerm, merkevare
|
||
form/farge hentet fra Teeoff-logoen — IKKE navn/logo, se ADR-016s
|
||
begrunnelse og tidligere samtale), hentet inn som `frontend/` (Next.js +
|
||
Tailwind + shadcn/ui).
|
||
- **Kvalitetsrunde på V0-output før bruk:** fargetokens i `globals.css` var
|
||
OKLCH-TILNÆRMINGER av de forespurte hex-fargene (`#8bc24a`/`#ff5722`), ikke
|
||
eksakte — regnet ut presise OKLCH-ekvivalenter og rettet alle 6
|
||
forekomster (lys/mørk/system-mørk). Fjernet `@vercel/analytics` helt
|
||
(ga null verdi på egen-hostet infrastruktur, kun en unødvendig
|
||
tredjeparts nettverkskall). Fjernet `typescript: { ignoreBuildErrors:
|
||
true }` (bygget kjører nå ekte typesjekk). Fjernet dødt
|
||
`pnpm.overrides`-felt. `frontend/.gitignore` manglet `.pnpm-store/` —
|
||
årsaken til at VSCode viste 10 000 "ukjente" filer ved første
|
||
commit-forsøk; rettet.
|
||
- **Kablet mot ekte API** (`app/routers/auth.py` sine `/auth/request-link`
|
||
og `/auth/verify-link`): `frontend/next.config.mjs` sin `rewrites()`
|
||
proxyer `/auth/*`/`/orgs/*`/`/health` server-side til API-et — se
|
||
ADR-016 for hele begrunnelsen (same-origin, ingen CORS, cookie uendret).
|
||
`components/login-form.tsx` sender ekte `POST /auth/request-link`;
|
||
ny `app/verify/page.tsx` + `components/verify-form.tsx` mottar
|
||
`?token=...` fra e-postlenken (automatisk verifisering) ELLER viser et
|
||
manuelt "lim inn koden"-felt (nødvendig fallback, ikke overflødig — se
|
||
under).
|
||
- **Backend-endring i samme runde:** `app/email.py`/`app/config.py` fikk en
|
||
ny `PUBLIC_BASE_URL`-innstilling (default `https://teecup.teeoff.no`,
|
||
valgfri) — e-postmalen sender nå en EKTE klikkbar lenke
|
||
(`{base}/verify?token=...`) i tillegg til den rå koden som fallback
|
||
(samme mal-mekanisme som i18n-runden, ADR-015).
|
||
- **Containerisert og rullet ut LIVE** (ny `frontend/Dockerfile`,
|
||
multi-stage, Next.js `output: "standalone"`; ny `teecup_frontend`-tjeneste
|
||
i `docker-compose.yml`). Caddy (`teecup.teeoff.no`, i det SEPARATE
|
||
`teeoff`-repoet) peker nå på `teecup_frontend` i stedet for `teecup_api`
|
||
direkte — se ADR-016 for hvorfor, og CHANGELOG.md for
|
||
driftsdetaljene (samme stale-Caddy-inode-hendelse som
|
||
containeriseringsrunden, løst likt).
|
||
- **Verifisert med FAKTISK e-postlevering, ikke bare curl:** ekte
|
||
`POST /auth/request-link` sendt til brukerens egen adresse over
|
||
`https://teecup.teeoff.no`, ekte e-post mottatt med en ekte klikkbar
|
||
lenke, lenken åpnet i nettleser, landet på en fungerende `/verify`-side,
|
||
sesjon opprettet — brukeren bekreftet "jeg er tilsynelatende innlogget."
|
||
Første gang en hel bruker-vendt flyt (ikke bare API-et isolert) er bevist
|
||
ende-til-ende i produksjon.
|
||
|
||
---
|
||
|
||
### Dato/klokkeslett + feilkode-kontrakt + i18n-forberedelse — ✅ BYGGET OG VERIFISERT 2026-07-17
|
||
- Reist av brukeren rett før frontend-arbeidet: kan turnering/økt/match ha
|
||
dato/klokkeslett, og må appen forberedes for flerspråklighet? Begge er
|
||
API-kontraktspørsmål som er langt billigere å løse FØR frontend bygges enn
|
||
å ettermontere — se ADR-015 for alle tre beslutningene i sin helhet.
|
||
- **Migrasjon `006_scheduling_and_locale.sql`:** alt additivt (nullable/
|
||
default) — `tournament.end_date` (+ `CHECK` mot `start_date`),
|
||
`session.scheduled_at`/`tee_interval_minutes`/`start_hole`,
|
||
`match.tee_time_override`, `app_user.preferred_locale`,
|
||
`magic_link_token.locale` (begge sistnevnte `CHECK IN ('nb','en')`). Kjørt
|
||
permanent mot den ekte `teecup_db` (se under).
|
||
- **Feilkoder:** ny `app_error(status_code, code, message)`-factory i
|
||
`app/errors.py`, alle 39 tidligere norsk-hardkodede `HTTPException(...,
|
||
detail="...")`-steder på tvers av 6 filer (`errors.py`, `auth.py`,
|
||
`routers/auth.py`, `routers/tournaments.py`, `routers/matches.py`,
|
||
`routers/scoring.py`) retrofittet til en delt ~15-kode-taksonomi. Endelig
|
||
`grep -rn 'detail="' app/` ga NULL treff.
|
||
- **Dato/tid i API-et:** `tournament.start_date` (fantes i skjemaet siden
|
||
001, men var ALDRI koblet til API-modellene før nå — funnet og fikset som
|
||
en naturlig bivirkning av å legge til `end_date`) + `end_date` eksponert;
|
||
`session` sine tre nye felt eksponert i `SessionCreate`/`SessionOut`;
|
||
`match.tee_time` utledet i Python i både `create_match` og `list_matches`
|
||
(ingen ekstra spørring — økten hentes allerede for andre formål der).
|
||
- **i18n:** `MagicLinkRequest.locale` (`Literal["nb","en"]`, default `nb`)
|
||
sendes av klienten, følger med i token-raden, brukes til å velge
|
||
e-postmal OG (kun ved førstegangsopprettelse) `app_user.preferred_locale`.
|
||
Ekte engelsk e-postmal lagt inn i `app/email.py` (ikke bare rørlegging) —
|
||
konkret bevis på at flerspråklighet fungerer ende-til-ende, ikke bare at
|
||
et felt eksisterer.
|
||
- **Verifisert grundig mot en fersk `teecup_scratch`** (migrasjoner
|
||
001→006, `test_isolation.sql` 12/12 uendret, engangs-API-container):
|
||
feilkode-form bekreftet på tvers av 5 filer (`NOT_FOUND`/`LIMIT_REACHED`
|
||
fra tournaments.py, `DUPLICATE` fra roster, `NOT_AUTHENTICATED` uten
|
||
cookie, `NOT_ROSTERED_ON_TEAM` fra matches.py sin `add_participant`);
|
||
`tee_time` bekreftet å øke riktig per `sequence` (10 min intervall →
|
||
08:00/08:10/08:20), `tee_time_override` bekreftet å vinne over utledet
|
||
verdi, økt uten `scheduled_at` bekreftet å gi `tee_time: null` (ikke
|
||
feil); i18n bekreftet full runde — ny bruker med `locale:"en"` fikk
|
||
`preferred_locale:"en"`, en ETTERFØLGENDE `request-link` for samme bruker
|
||
med `locale:"nb"` skiftet e-postmalen men IKKE den lagrede preferansen
|
||
(bekreftet uendret via `/auth/me`).
|
||
- **Migrasjon 006 kjørt mot ekte `teecup_db` 2026-07-17**, bruker bekreftet
|
||
eksplisitt i samme økt — kjørte rent, `test_isolation.sql` fortsatt 12/12.
|
||
- **Mindre driftslærdom, funnet OG rettet samme runde:** et innledende
|
||
`grep -v -i 'pass|secret|key'`-filter jeg brukte for å lese ikke-sensitive
|
||
`.env`-nøkler fanget IKKE opp `TEECUP_DATABASE_URL`, som bar
|
||
`teecup_app`-passordet innebygd i selve URL-en (i tillegg duplisert av det
|
||
samme passordet som allerede lå rent i `TEECUP_APP_PASSWORD`) — passordet
|
||
endte dermed synlig i et verktøyresultat. Flagget til brukeren umiddelbart
|
||
(samme mønster som SMTP-passord-hendelsen under containeriseringsrunden).
|
||
**Rettet:** `.env` bygget om til fem separate, rent navngitte felt
|
||
(`TEECUP_DB_HOST/PORT/NAME/USER/PASS`), `app/config.py`+`app/db.py` bygger
|
||
nå `asyncpg`-poolen fra disse i stedet for én DSN-streng,
|
||
`docker-compose.yml` oppdatert tilsvarende. Se CHANGELOG.md for full
|
||
driftsdetalj, inkl. at dette krevde et fullt
|
||
`docker compose up -d --build --force-recreate` (ikke bare
|
||
`--force-recreate` — imaget bygger inn `app/` ved build-tid). Verifisert
|
||
ende-til-ende mot den live stacken (`Application startup complete`,
|
||
`/auth/me` ga rent `401` over ekte https, `teeoff.no` upåvirket).
|
||
|
||
---
|
||
|
||
### Containerisering og go-live — ✅ FERDIG 2026-07-16
|
||
- `POST /orgs` osv. var siste kodebit; dette var første gang noe rørte EKTE,
|
||
PERMANENT infrastruktur (ekte `teecup_db`, ekte langtlevende container, den
|
||
DELTE Caddy-instansen som også ruter live `teeoff.no`).
|
||
- **Bygget:** ekte `teecup_db` opprettet, migrasjoner 001→005 kjørt permanent
|
||
(samme filer, ingen endringer), `test_isolation.sql` bestått (ruller
|
||
alltid tilbake, trygt å kjøre mot en database som skal bli stående).
|
||
`Dockerfile` (speiler scratch-rundenes bevist-riktige volumoppsett:
|
||
`app/` + `handicap_engine.py` på samme relative plassering) +
|
||
`docker-compose.yml` (tjeneste `teecup_api`, joiner det eksisterende,
|
||
eksterne `teeoff_default`-nettverket). Ny Caddy-blokk for
|
||
`teecup.teeoff.no` i `/opt/teeoff/deploy/Caddyfile`.
|
||
- **To reelle driftshendelser, begge funnet og rettet i sanntid:**
|
||
1. **Stale bind-mount-inode:** `teeoff_caddy` sin `Caddyfile`-mount er en
|
||
ENKELTFIL-bind-mount, låst til inoden som fantes da containeren sist
|
||
startet (13 dager tidligere). Fil-redigering via atomisk rename laget
|
||
en ny inode på samme sti — containeren fortsatte å lese den GAMLE filen
|
||
uansett hvor mange ganger `caddy validate`/`reload`/admin-API `/load`
|
||
ble kjørt (alle opererte på den uendrede gamle filen, derav ingen
|
||
synlig feil). Løsning: full `docker restart teeoff_caddy` (brukeren
|
||
bekreftet eksplisitt — avvek fra planens "kun graceful reload"-løfte,
|
||
ga noen sekunders nedetid for `teeoff.no`).
|
||
2. **Alvorlig nettverksalias-kollisjon** (oppdaget rett etter omstarten, da
|
||
EKTE teeoff-trafikk — `/api/facilities?...` med ekte klubb-slugs som
|
||
`borregaard-golfklubb` — dukket opp i `teecup_api` sin logg):
|
||
`docker-compose.yml` sin service-nøkkel var `api:`, identisk med
|
||
teeoffs eget `api`-servicenavn. Docker Compose registrerer
|
||
nettverksalias etter service-NAVNET (ikke bare `container_name`) på
|
||
delte nettverk, så begge containerne fikk alias `api` på
|
||
`teeoff_default` — Caddys `reverse_proxy api:8000` i teeoffs egen
|
||
config kunne da tilfeldig treffe enten ekte `teeoff_api` eller
|
||
`teecup_api`, dvs. ekte brukertrafikk til teeoff.no kunne bli besvart
|
||
av TeeCup-koden. **Rettet umiddelbart:** stoppet `teecup_api` først
|
||
(hindre videre feilruting), ga service-nøkkelen navnet `teecup_api`,
|
||
gjenopprettet, bekreftet med `docker network inspect` at alias `api` nå
|
||
KUN peker på ekte `teeoff_api`.
|
||
**Lærdom for fremtidige tjenester på delt nettverk:** ALLTID gi
|
||
docker-compose sin service-nøkkel (ikke bare `container_name`) et
|
||
prosjekt-unikt navn når flere uavhengige compose-prosjekter deler samme
|
||
eksterne nettverk — service-navnet blir også et DNS-alias.
|
||
- **Mindre driftslærdom (samlet):** (a) `.env` leses IKKE på nytt av en
|
||
allerede kjørende container — `docker compose up -d --force-recreate`
|
||
kreves etter enhver `.env`-endring; (b) et `#`-tegn i et upassordet
|
||
`.env`-passord kuttes som kommentar av Compose sin parser, anførselstegn
|
||
(helst enkle) løser det; (c) jeg eksponerte ved et uhell to secret-verdier
|
||
i eget debug-output mens jeg feilsøkte en `.env`-korrupsjon (manglende
|
||
linjeskift) — brukeren roterte passordet som forsiktighetsregel.
|
||
- **Verifisert ende-til-ende mot den ekte, live stacken:** `teeoff.no`
|
||
upåvirket gjennom hele prosessen; `https://teecup.teeoff.no/health` → 200
|
||
med automatisk utstedt TLS; full magic-link-innlogging (ekte e-post
|
||
mottatt, `verify-link` ga `Secure`-flagget cookie siden vi nå er over ekte
|
||
https, `/auth/me` fungerte med sesjonen).
|
||
|
||
---
|
||
|
||
### Ekte SMTP-utsending — ✅ BYGGET OG VERIFISERT 2026-07-16
|
||
- Brukeren la inn egne, uavhengige SMTP-credentials i `.env`
|
||
(`TEECUP_SMTP_SERVER/PORT/USER/PASS`, `TEECUP_FROM_EMAIL` — ADR-009, ikke
|
||
delt med teeoff). Kun nøkkelnavn ble lest for å bekrefte de fantes, ALDRI
|
||
verdiene (CLAUDE.md sin sikkerhetsregel).
|
||
- **Løsning:** ny `app/email.py` (`send_magic_link_email`, `smtplib` kjørt via
|
||
`asyncio.to_thread` siden det er et synkront bibliotek — samme mønster som
|
||
teeoffs egen fungerende utsending). Håndterer BEGGE vanlige
|
||
SMTP-tilkoblingsmåter dynamisk (implisitt TLS på port 465 vs. STARTTLS på
|
||
andre porter) siden porten bevisst ikke ble lest under planlegging.
|
||
`app/config.py` fikk nye, valgfrie innstillinger (`SMTP_CONFIGURED` avledet
|
||
fra at alle fem er satt) — IKKE `_required`, så scratch-/dev-testing
|
||
fungerer fortsatt uten SMTP satt opp, via `TEECUP_DEV_LOG_MAGIC_LINKS`.
|
||
- **Bevisst designvalg:** en driftsfeil i selve utsendingen (feil passord,
|
||
SMTP nede, eller ingen leveringsmåte konfigurert i det hele tatt) logges
|
||
kun server-side og endrer ALDRI klientens respons — alt annet ville brutt
|
||
anti-enumereringsgarantien i `request-link` (klienten skal ikke kunne
|
||
skille "e-posten finnes ikke" fra "e-posten finnes men utsendingen
|
||
feilet").
|
||
- **Verifisert i to trinn:** (1) dev-log-flyten uendret uten SMTP satt
|
||
(regresjonstest av eksisterende scratch-løype), (2) én ekte test-e-post
|
||
sendt til en adresse brukeren oppga, med de ekte credentials videreført fra
|
||
`.env` til scratch-containeren uten at jeg noensinne leste verdiene selv —
|
||
**brukeren bekreftet mottak** av en e-post med innloggingskode. Dette er
|
||
første gang noe i dette prosjektet er verifisert ved faktisk levering til
|
||
en ekte, ekstern mottaker, ikke bare via curl/scratch-container.
|
||
|
||
---
|
||
|
||
### Organisasjon-bootstrap — ✅ BYGGET OG VERIFISERT 2026-07-16
|
||
- Gjennomgang av alle routere hadde avdekket at INGEN endepunkt opprettet en
|
||
`organization`-rad — i alle testrunder denne økten var organisasjoner satt
|
||
inn direkte med superbruker-SQL. En ekte førstegangsbruker hadde ingen vei
|
||
til å opprette klubben/bedriften sin og bli owner. Reelt blokkerende, ikke
|
||
en utsettbar produktbeslutning.
|
||
- **Løsning:** nytt `POST /orgs {"name": ...}`, autorisert med
|
||
`get_current_user` (ikke `get_authorized_org` — sirkulært før org-en
|
||
finnes). Ingen ny migrasjon nødvendig.
|
||
- **Selvrefererende RLS-bootstrap bekreftet å fungere** (kommentaren i 002 om
|
||
en "privilegert sti" var ALDRI bygget og viste seg unødvendig): generer
|
||
org-ens uuid i Python FØR innsetting, sett `app.current_org` til nøyaktig
|
||
den verdien via den eksisterende `org_connection()`, sett så inn
|
||
`organization`-raden med samme id. `org_self`-policyens implisitte
|
||
`WITH CHECK` (id = `app_current_org()`) blir da trivielt sann.
|
||
`teecup_app` (NOSUPERUSER/NOBYPASSRLS) trenger altså INGEN egen privilegert
|
||
tilkobling for å bootstrappe sin egen første organisasjonsrad.
|
||
- **Verifisert med 5 tester**, inkludert en negativ kontroll som beviser
|
||
mekanismen er presis, ikke et RLS-hull: forsøkte å sette inn en
|
||
organisasjon med en MISMATCHENDE id (annen enn `app.current_org`) — avvist
|
||
med `insufficient_privilege`, som forventet. Også bekreftet: ny org fungerer
|
||
normalt med eksisterende endepunkter (GET/POST tournaments), dukker opp
|
||
riktig i `/auth/me`, og full kryss-org-isolasjon holder mellom to
|
||
uavhengig opprettede organisasjoner.
|
||
|
||
---
|
||
|
||
### RLS-tomstreng-bug — ✅ FIKSET 2026-07-16
|
||
- Alle RLS-policyer i 001/003 (`org_isolation` på 14 tabeller + `org_self` på
|
||
`organization`) brukte `current_setting('app.current_org', true)::uuid`.
|
||
Denne håndterte NULL trygt (ga ingen rader, som tiltenkt), men IKKE
|
||
tomstreng — en gjenbrukt asyncpg-pool-tilkobling der en TIDLIGERE
|
||
forespørsel satte GUC-en via `SET LOCAL` kunne lese den tilbake som `''`
|
||
etter at transaksjonen var ferdig, og casten kastet da en 500 i stedet for
|
||
skjemaets lovede "trygg standard: se ingenting".
|
||
- **Fiks:** samlet i én `STABLE` SQL-funksjon `app_current_org()` (migrasjon
|
||
`005_rls_null_guard.sql`) som gjør `NULLIF(current_setting(...), '')::uuid`
|
||
— konverterer tomstreng til NULL FØR cast. Alle 15 policyer alteret
|
||
(`ALTER POLICY`) til å bruke funksjonen i stedet for det rå uttrykket.
|
||
Verifisert med 3 nye regresjonstester i `test_isolation.sql` (Test 10-12:
|
||
tomstreng-lesing gir 0 rader ikke krasj, tomstreng-skriving avvises av RLS
|
||
ikke krasj, org-bootstrap-innsetting fungerer rett etter tomstreng-
|
||
tilstand) OG ved å faktisk gjenskape original-buggen mot en ekte container
|
||
(pool-størrelse 1, varm opp med `org_connection()`, deretter kall
|
||
`/auth/me` på samme gjenbrukte tilkobling — gikk fra 500 til 200).
|
||
- **Viktig presisering oppdaget underveis:** den opprinnelige planen antok at
|
||
`/auth/me` kunne joine `organization` direkte igjen når tomstreng-buggen
|
||
var fikset. Det var FEIL — fiksen gjør bare at tomstreng oppfører seg som
|
||
NULL (trygt: se ingenting), den endrer IKKE at `org_self`-policyen krever
|
||
en MATCHENDE `app.current_org` for å vise en rad i det hele tatt (riktig
|
||
RLS-oppførsel, ikke en bug). 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 spørringer, N = antall org-er brukeren tilhører,
|
||
typisk 1-3) — verifisert at dette faktisk returnerer navnet korrekt.
|
||
|
||
---
|
||
|
||
## Ønsket, men IKKE fanget før nå (fra Gemini-samtalene)
|
||
|
||
### Brukerroller (utover org-medlemskap) — ADR-023 + ADR-026
|
||
- **Status:** ✅ HELT FERDIG 2026-07-19 — kaptein/deltaker-delen (ADR-023)
|
||
OG tilskuer-delen (ADR-026, se eget punkt lenger ned).
|
||
- De opprinnelige samtalene beskriver: turneringsadmin, **lagkaptein**, spiller,
|
||
**tilskuer** (les-only, følger live uten skriverettigheter).
|
||
- Vi har i dag org-roller (owner/admin/member) + `is_captain` på roster.
|
||
- **2026-07-19, ✅ BYGGET (ADR-023):** Kaptein er nå en REELL autorisasjonsrolle,
|
||
ikke bare et merke. `app/team_authz.py` sin nye `user_is_team_captain`
|
||
(erstatter `user_may_act_for_team`) krever `is_captain=true` (eller
|
||
org-eier/admin) for å legge til/fjerne deltakere og låse et lag
|
||
(`matches.py`). Bevisst unntak — funnet ved å faktisk sjekke ekte
|
||
produksjonsdata FØR utrulling: har laget INGEN utpekt kaptein ennå, godtas
|
||
enhver rostret spiller i stedet (ellers ville «De Unge»-laget i den ekte
|
||
«De Gamle er Eldst»-turneringen vært låst ute umiddelbart — 0 av 2
|
||
roster-rader er i dag merket kaptein der).
|
||
- **2026-07-19, ✅ BYGGET (ADR-023):** «Kun én kaptein per lag» håndheves nå —
|
||
`PATCH`/`POST .../roster` (`tournaments.py`) fjerner automatisk
|
||
kapteinmerket fra andre rader på samme lag når en ny kaptein settes.
|
||
Nødvendig konsekvens av at kaptein nå er en autorisasjonsrolle. Ingen
|
||
eksisterende lag hadde flere kapteiner (sjekket mot ekte data), så ingen
|
||
opprydning av data var nødvendig.
|
||
- **2026-07-19, ✅ BYGGET (ADR-023):** Score-føring/-korrigering begrenset til
|
||
matchens FAKTISKE deltakere (`app/team_authz.py` sin nye
|
||
`user_is_match_participant`, brukt av `scoring.py`) — ikke lenger «noen på
|
||
laget», og uavhengig av kapteinmerket. Erstatter «Scoring-autorisasjon»-
|
||
punktet under, spørsmål (a) er dermed besvart.
|
||
- **Tilskuer — ✅ HELT FERDIG, LIVE 2026-07-19 (ADR-026),** rett etter
|
||
Kommunikasjon (ADR-025) som gjorde feed-synligheten (som denne skulle
|
||
defineres sammen med) klar. Ingen ny rolle/tabell — «tilskuer» er ganske
|
||
enkelt enhver som kan SE turneringen (`tournament.visibility`), nå også
|
||
for LEADERBOARD, MATCH-LISTE og FULLT SCOREKORT (hull-for-hull), ikke bare
|
||
info-siden/programtider som før (`GET /public/tournaments/{id}/
|
||
leaderboard`, `.../sessions/{id}/matches`, `.../matches/{id}/scorecard`,
|
||
alle i `registration.py`, gjenbruker eksisterende
|
||
`fetch_leaderboard`/`fetch_matches`/`fetch_scorecard`). Match-listen arver
|
||
blind draw-skjuling (ADR-013) automatisk via `own_team_ids()` gjort
|
||
null-sikker for en anonym leser (tom mengde = kun avslørte matcher).
|
||
Scorekortet har en eksplisitt reveal-sjekk i tillegg. Ny `/t/[id]/live`-
|
||
side (leaderboard-bar, øktliste, matcher, hull-for-hull-scorekort),
|
||
lenket fra hovedsiden. Bruker valgte å bygge fullt scorekort med i denne
|
||
runden (utover opprinnelig anbefaling om kun leaderboard+matchliste).
|
||
- Se ARCHITECTURE_DECISIONS.md ADR-023 (kaptein/deltaker) og ADR-026
|
||
(tilskuer) for alle delbeslutningene, og CHANGELOG.md for
|
||
scratch-verifiseringen (15 + 16 automatiserte sjekker).
|
||
|
||
### Scoring-autorisasjon: hvem fører, hvem korrigerer, hvem lukker (rejst 2026-07-16)
|
||
- **Status:** ❓ delvis avgjort — direkte oppfølger av «Brukerroller» over.
|
||
- **Hvem fører score i dag (2026-07-19, ADR-023):** kun matchens FAKTISKE
|
||
deltakere, ELLER org-eier/admin — `app/team_authz.py` sin
|
||
`user_is_match_participant`. Byttet fra «noen på laget» samme dag. Se
|
||
«Brukerroller» over for full begrunnelse.
|
||
- **Hvem kan korrigere en ført score i dag:** akkurat de samme — skriving er en
|
||
upsert (`ON CONFLICT ... DO UPDATE`), så en korrigering er ikke skilt fra en
|
||
førstegangs-innføring. Ingen godkjenning fra motstanderen, ingen audit-trail
|
||
(forrige verdi overskrives sporløst).
|
||
- **Match-lås ved avgjørelse: ✅ FIKSET 2026-07-16.** `submit_hole_score` og
|
||
`submit_hole_result` (`app/routers/scoring.py`) avviser nå med 409 («Matchen
|
||
er avgjort og kan ikke lenger endres.») FØR upserten kjøres, hvis
|
||
`match.points_side_a IS NOT NULL` — dette er allerede et pålitelig,
|
||
entydig signal siden kolonnen kun settes når `compute_match_state` sier
|
||
matchen er avgjort. Gjelder BÅDE nye hull og korrigering av allerede talte
|
||
hull. Verifisert: hull etter avgjørelse avvist, korrigering av et
|
||
talt hull avvist, GET scorecard fortsatt leselig, ikke-avgjorte matcher
|
||
upåvirket. **Bevisst IKKE bygget:** ingen manuell «avslutt match før den
|
||
er avgjort»-handling (det grenser mot walkover under, fortsatt utsatt).
|
||
Turnering-status kan nå settes via API — se eget punkt lenger ned.
|
||
- **Walkover/konsesjon — ✅ BYGGET OG LIVE 2026-07-19 (ADR-024).** Løste det
|
||
opprinnelige hullet: en side som aldri stiller nok spillere kan nå
|
||
avsluttes ved at den TAPENDE siden (kaptein, eller org-admin) erklærer
|
||
walkover — ensidig, ingen bekreftelse fra motparten. `POST /orgs/{id}/
|
||
matches/{id}/concede` (én match) og `POST /orgs/{id}/tournaments/{id}/
|
||
concede` (gi opp ALLE ikke-avgjorte matcher laget har, i én operasjon —
|
||
v1 er låst til nøyaktig to lag, så det finnes bare én motstander). Kan
|
||
erklæres uansett hvor mange hull som allerede er registrert (poengmessig
|
||
teller kun seier/tap/delt, ikke marginen) — allerede registrerte hull
|
||
forblir urørt i scorekortet. Se ARCHITECTURE_DECISIONS.md ADR-024.
|
||
Frontend: `session-scorecard.tsx` (gi opp én match) og
|
||
`tournament-detail.tsx` (gi opp resten av turneringen, per lag).
|
||
- **Turnering-status via API — ✅ BYGGET OG LIVE 2026-07-19.** `status` lagt
|
||
til i `TournamentUpdate` (`app/routers/tournaments.py`), settes via det
|
||
eksisterende `PATCH /orgs/{id}/tournaments/{id}` (ekte PATCH-semantikk
|
||
uendret — et utelatt `status`-felt lar verdien stå urørt). `status` er,
|
||
ulikt de andre PATCH-bare feltene, en EKTE Postgres ENUM-type
|
||
(`tournament_status`, migrasjon 001) — den generiske SQL-byggeren i
|
||
`update_tournament` fikk et eksplisitt `::tournament_status`-cast for
|
||
akkurat dette feltet, funnet og løst FØR noe ble antatt riktig (verifisert
|
||
i scratch at castet faktisk trengs). Ingen egen tilstandsmaskin/
|
||
overgangsregler — enhver org-medlem med skrivetilgang kan sette hvilken
|
||
som helst av de fire verdiene, samme tillitsnivå som resten av appen.
|
||
Frontend: `tournament-status-badge.tsx` fikk en ny redigerbar
|
||
`TournamentStatusPicker` (dropdown, optimistisk oppdatering med
|
||
rollback ved feil), koblet inn i `tournament-detail.tsx` sin header.
|
||
- **Avgjort 2026-07-16:**
|
||
- **Match-lås ved avgjørelse: ✅ bygget** — se eget punkt over. Automatisk
|
||
(ikke en handling noen utfører), så «hvem får låse» ble aldri et
|
||
spørsmål som trengte avklaring.
|
||
- **Fortsatt åpent:** (a) ~~skal score-føring begrenses til faktiske
|
||
matchdeltakere~~ ✅ avgjort/bygget 2026-07-19 (ADR-023, se over). (b) skal
|
||
korrigering kreve motpartens godkjenning, eller er upsert-modellen god nok
|
||
for v1? (c) ~~skal turnering-status kunne settes via API~~ ✅ avgjort/
|
||
bygget 2026-07-19 (se over).
|
||
|
||
### Blind draw (skjult lagoppstilling)
|
||
- **Status:** ✅ skjema (migrasjon 003, `lineup_lock`) + API bygget og verifisert
|
||
(`app/routers/matches.py`: synlighetsfilter i SQL, ikke Python-filter — se ADR-013).
|
||
**Frontend LIVE 2026-07-18:** `/tournaments/[id]/sessions/[sessionId]`,
|
||
`components/session-blind-draw.tsx`. Ny `DELETE .../matches/{id}/
|
||
participants/{id}` (kunne ikke angre et valg før låsing uten den). Fant
|
||
og fikset et "skriv blindt"-hull: org-admin kunne legge til deltakere på
|
||
et lag de ikke er rostret på (forrige rundes utvidelse), men
|
||
`own_team_ids()` i `app/blind_draw.py` viste dem aldri tilbake før
|
||
reveal — utvidet til samme owner/admin-regel som skrive-siden. Se
|
||
CHANGELOG.md for full runde, inkl. en tredje, urelatert 500-bug
|
||
(manglende handicap-indeks) funnet og fikset samtidig.
|
||
- Kapteinene låser oppstillingen skjult; matchene avsløres samtidig når begge er
|
||
ferdige.
|
||
- **2026-07-19 (ADR-023):** hvem som FÅR låse et lag er nå kaptein-spesifikt
|
||
(eller org-eier/admin) — se «Brukerroller» over. Med fallback for lag uten
|
||
utpekt kaptein ennå.
|
||
- **Nytt 2026-07-18, ✅ BYGGET (fant og fikset rett før frontend-skjermen):**
|
||
`match_participant.tee_id` er påkrevd, men det fantes INGEN vei til å
|
||
liste EN banes tee-er (selv offisielt importerte), og egendefinerte
|
||
baner har ALDRI hatt noen vei til å FÅ tee-er — et hull som fantes fra
|
||
før ADR-019, ikke noe den innførte. Uten dette kunne en turnering på en
|
||
manuelt navngitt bane aldri få en ekte deltaker på noen match. Ny
|
||
`GET/POST /orgs/{id}/courses/{id}/tees` (kun full_18-rating, offisielle
|
||
baner avviser manuell tee-opprettelse). **Presisert eksplisitt: dette er
|
||
IKKE en teeoff.no-kodeendring** — egendefinerte baner er per definisjon
|
||
utenfor teeoffs katalog, så dette er en ren teecup-intern funksjon.
|
||
**Bevisst utenfor omfang:** hull-/stroke-index-data for egendefinerte
|
||
baner (trengs i SCORING-fasen, ikke blind draw) — egen, senere sak når
|
||
scorekort-skjermen bygges.
|
||
|
||
### Forenklet scoreføring (uten slagtall)
|
||
- **Status:** ✅ skjema (migrasjon 003) + API bygget og verifisert
|
||
(`app/routers/scoring.py`: `hole-scores` for `stroke`-modus,
|
||
`hole-results` for `hole_result`-modus, begge mater samme `compute_match_state`).
|
||
- **Nytt 2026-07-18, ✅ BYGGET (forarbeid før scorekort-skjermen):**
|
||
`GET/POST /orgs/{id}/courses/{id}/holes` løser hull-/stroke-index-hullet
|
||
fra tee-runden (kun `stroke`-modus trengte det). `GET scorecard` utvidet
|
||
med `stroke_entries`/`hole_result_entries` (rå registrerte tall, ikke
|
||
bare utledet vinn/tap/delt) — uten dette kunne ikke en gjenlastet
|
||
scorekort-side vise gjeldende tilstand. Verifisert med en full
|
||
`stroke`-runde med ekte handicap-justering på en egendefinert bane.
|
||
- **Frontend LIVE 2026-07-18:** `/tournaments/[id]/sessions/[sessionId]/
|
||
matches/[matchId]`, `components/session-scorecard.tsx`. Ett hull om
|
||
gangen med store steppere/knapper (banebruk i sollys, ikke et regneark
|
||
— matcher UX-visjonen lenger ned i denne fila). Lenket fra blind draw
|
||
sin avslørte visning. Ingen nye backend-hull denne runden.
|
||
|
||
### Bøtekasse (Kangaroo Court)
|
||
- **Status:** 📋 planlagt
|
||
- Meld inn overtramp med bøter («kastet kølla på hull 4»).
|
||
- Henger sammen med Kommunikasjon: bøter deles i feeden. Sannsynligvis en egen
|
||
post-type i meldingsmodellen.
|
||
|
||
### Flerårig statistikk / historikk / MVP
|
||
- **Status:** 📋 planlagt (datamodell støtter det allerede)
|
||
- Seiersprosent i singelmatcher, historisk MVP, statistikk over år.
|
||
- Datamodellen tillater dette fordi spillere lever på org-nivå og gjenbrukes på
|
||
tvers av turneringer, og handicap fryses per turnering (reproduserbart).
|
||
|
||
### Leaderboard (turnering-total)
|
||
- **Status:** ✅ HELT FERDIG 2026-07-18 — backend + frontend LIVE
|
||
(`components/tournament-leaderboard.tsx`, `/tournaments/[id]/
|
||
leaderboard`). Siste skjerm i "bygg i rekkefølgen ting brukes"-serien
|
||
for match-play-flyten (oppsett → program → blind draw → scorekort →
|
||
leaderboard, alle live).
|
||
- Summerer poeng på tvers av ALLE økter/matcher, total + per-økt-delsum.
|
||
**Reelt korrekthetshull designet rundt før bygging:** `match.team_a_id`/
|
||
`team_b_id` er ikke garantert konsistent på tvers av matcher (settes per
|
||
match) — summering skjer derfor KUN på ekte lag-id, aldri på "a"/"b"-
|
||
labelen. Verifisert med et scratch-scenario med en bevisst byttet om
|
||
team_a/team_b i én match — riktig total og per-økt-delsum likevel.
|
||
|
||
### Push-varsler
|
||
- **Status:** ✅ HELT FERDIG 2026-07-28 — se "Varsler: push til telefon +
|
||
in-app varslingssenter" lenger ned i denne filen for full detalj. Denne
|
||
linjen var et tidlig notat som aldri ble fjernet da resten ble bygget
|
||
(stale-opprydding 2026-08-14).
|
||
|
||
### Sanntid (WebSockets)
|
||
- **Status:** ✅ HELT FERDIG, LIVE 2026-07-19 (ADR-025 + ADR-027). Koblet
|
||
til BÅDE meldinger (ADR-025) OG `/t/[id]/live` (leaderboard/matcher/
|
||
scorekort, ADR-027). Ny, rutefri delt modul `app/realtime.py` (unngår
|
||
sirkulær import mellom `messaging.py`/`registration.py`/`scoring.py`/
|
||
`tournaments.py`) — sender et "noe endret seg"-signal (ikke selve
|
||
dataene) hver gang `recompute_and_cache_match_state`/`apply_concession`
|
||
kjører, klienten henter de vanlige REST-endepunktene på nytt. Samme
|
||
in-memory-per-prosess-begrensning som meldinger.
|
||
|
||
### Knockout / cup-turnering (egen turneringstype)
|
||
- **Status:** 💤 utsatt (egen fremtidig type, ADR-011)
|
||
- Utslagsmatcher: 128 spillere → finale + bronsefinale. Rundene er *avhengige*
|
||
(vinner går videre), krever bracket/progresjon, seeding, fripass. IKKE i v1.
|
||
- v1 er låst til nøyaktig to lag (Ryder Cup-format). Match-modellen er generell, så
|
||
denne typen kan legges til senere uten dataomskriving — den legger bare til et
|
||
progresjons-lag.
|
||
|
||
### Flere turneringsformater utover Ryder Cup (reist 2026-07-19)
|
||
- **Status (2026-07-30): ✅ BACKEND FERDIG for alle åtte formater** (Chapman/
|
||
Try-all, Nassau, Københavner, Bingo Bango Bongo, Flaggturnering, Shamble,
|
||
Money Ball/Lone Ranger, High-low-high) — motor, migrasjoner, API-wiring
|
||
for frittstående runder OG org-scopede turneringer (lag ELLER individuell,
|
||
avhengig av formatets struktur), scratch-verifisert grundig i BEGGE
|
||
systemer for hvert format (engine-enhetstester FØRST, deretter full
|
||
API-verifisering mot ekte HTTP-endepunkter). **Frontend fortsatt IKKE
|
||
bygget** — se eget avsnitt nederst i denne seksjonen for full detalj,
|
||
migrasjonsnumre, og kjente v1-avgrensninger per format. "Robbins" ble
|
||
bevisst DROPPET av bruker (ikke bekreftet i noen kilde). Se ADR-en i
|
||
ARCHITECTURE_DECISIONS.md for arkitekturbeslutningene.
|
||
- Brukeren ønsker at TeeCup etter hvert skal støtte flere turneringsformater enn
|
||
dagens rene to-lags Ryder Cup-modell (ADR-011). Fire konkrete eksempler gitt,
|
||
med ulik arkitektonisk konsekvens (fra «passer nesten inn i dag» til «krever
|
||
en helt egen datamodell») — notert ordrett under, ikke forenklet, for at
|
||
reglene skal være presise når dette tas fatt på:
|
||
|
||
**«Københavner»** (engelsk: trolig **"Copenhagen"** — direkte oversettelse,
|
||
brukes noen steder i engelskspråklig golf-litteratur om nettopp dette
|
||
poengsystemet, men usikkert om det er en universelt anerkjent
|
||
standardbetegnelse; verdt å dobbeltsjekke før navnet ev. brukes i UI-et).
|
||
3 spillere, alle mot alle (INGEN lag). 6 poeng fordeles på hvert hull etter
|
||
relativ plassering: vinner alene → 4-2-0. Vinner + de to andre deler →
|
||
4-1-1. To deler beste score, én taper → 3-3-0. Alle likt → 2-2-2. Flest
|
||
poeng totalt etter runden vinner. **Størst arkitektonisk avstand fra i
|
||
dag:** ingen to-siders match i det hele tatt, individuelt felt med
|
||
poeng-per-hull-fordeling — passer ikke inn i `match`/`team_a_id`/
|
||
`team_b_id`-modellen slik den er nå (se merknad ved ADR-011).
|
||
|
||
**«High-low-high»** (4 spillere, 2 lag à 2). Per hull: beste spiller
|
||
(«high») på hvert lag møter hverandre i en del-match, dårligste spiller
|
||
(«low») på hvert lag møter hverandre i en egen del-match — resultatet av
|
||
hele hullet i hovedmatchen avgjøres av disse to del-oppgjørene til sammen.
|
||
Eksempel: hull 1 — spiller A (lag 1) får 3 poeng og slår begge på lag 2
|
||
(høyest slår høyest), spiller B (lag 1) stryker og taper mot lagets
|
||
low-motpart. Hullet blir da delt 1-1 siden «high» vant for lag 1 og «low»
|
||
vant for lag 2. **Arkitektonisk vrien del:** hvem som er «high»/«low» per
|
||
hull avgjøres AV SCORENE selv, etter at de er registrert — ikke satt opp
|
||
på forhånd slik dagens `match_participant`-oppsett (fast rolle/side satt
|
||
ved blind draw) forutsetter.
|
||
|
||
**«Robbins»** (foursome med partnerbytte). De første 6 hullene spilles som
|
||
én foursome-match, deretter bytter alle makker og spiller neste 6 hull som
|
||
en ny foursome-match, og de siste 6 hullene spilles med den tredje/siste
|
||
kombinasjonen — slik at alle har spilt med og mot alle i løpet av runden.
|
||
Seier i en 6-hulls delmatch gir 2 poeng, uavgjort gir 1 poeng, flest poeng
|
||
totalt vinner. **Arkitektonisk konsekvens:** én økt blir egentlig TRE
|
||
sekvensielle del-matcher med roterende partnerskap innad i samme runde —
|
||
dagens modell (én match = ett fast lag-oppsett for hele økten) dekker ikke
|
||
dette direkte.
|
||
|
||
**«Try all»** (2 lag, variant av foursome). Begge spillerne slår egen ball
|
||
fra tee, men BYTTER ball til andreslaget (spiller A slår spiller B sin
|
||
ball og omvendt), og paret velger deretter hvilken av de to ballene som
|
||
skal spilles videre — resten av hullet spilles som ordinær foursome på den
|
||
valgte ballen. **Minst arkitektonisk avstand fra i dag:** sannsynligvis en
|
||
ren spilleregel-variant av eksisterende foursome-format (samme
|
||
poengmodell, bare en annen fremgangsmåte de to første slagene) — trenger
|
||
trolig ikke ny datamodell, bare en presisering i regelverket/UI-teksten.
|
||
|
||
**«Flaggturnering»** (Flag tournament), reist av bruker 2026-07-25 —
|
||
IKKE del av den opprinnelige fire-formater-listen over, notert som et
|
||
eget femte format. Hver spiller får et fast antall slag (typisk par +
|
||
handicap for hele runden) og spiller til slagene er brukt opp — den som
|
||
kommer lengst rundt banen før slagene tar slutt, vinner ("planter
|
||
flagget" der siste slag ble brukt). **Konkret krav fra bruker, eksplisitt
|
||
formulert:** en visning som viser GJENSTÅENDE slag for spilleren,
|
||
oppdatert etter hvert spilte hull (nedtelling, ikke bare et sluttall).
|
||
**Fremtidig idé, opprinnelig betinget av at GPS ble integrert i appen
|
||
først:** bruk GPS til å markere/registrere HVOR på banen spilleren
|
||
faktisk endte opp når slagene tok slutt, ikke bare hvilket hull.
|
||
**Selve Flaggturnering-formatet ER bygget og live** (se "2026-07-30:
|
||
alle åtte formater" lenger ned i denne filen — `flag_result`-motoren,
|
||
hull-granularitet). **GPS-avhengigheten er også borte** — GPS/
|
||
avstandsmåling ble bygget 2026-08-10/12 (ADR-048 slag-for-slag-måling,
|
||
ADR-064 rangefinder til grønn).
|
||
|
||
**✅ GPS-posisjon-ved-siste-slag ER NÅ BYGGET (ADR-066, 2026-08-14),
|
||
"Del A" av en tredelt utvidelse** (kartoversikt "Del B"/ADR-067 og
|
||
nytt format Eclectic "Del C"/ADR-068 er OGSÅ bygget nå, se egne
|
||
punkter -- alle tre deler ferdige): "Plant flagget"-knapp +
|
||
guidet sheet (GPS auto-fanget → "Hullet du ut på hull N?" → evt.
|
||
"Landet du på green?" + avstand i m/cm som påvirker rekkefølgen mot
|
||
andre som gikk tom på samme hull) i BEGGE hjem (frittstående runder OG
|
||
org-individuelle turneringer). Full runde 2+-scoreføring bygget for
|
||
spillere med uvanlig gunstig slagbudsjett (isolert overflow-tabell,
|
||
rører ikke delt `round_hole`/`tournament_round_hole`-infrastruktur).
|
||
UI-sheeten er en Claude-skrevet V0-eksport (zip 11), ikke håndkodet.
|
||
Migrasjon 069 (frittstående)/070 (org) — se ADR-066 for full detalj,
|
||
CHANGELOG.md for byggelogg/verifisering.
|
||
|
||
**✅ Kartoversikt ER NÅ BYGGET (ADR-067, 2026-08-14), "Del B"** — av/på-
|
||
bryter (standard AV, eier/org-medlem) som viser ALLE deltakeres flagg
|
||
på et satellittkart, lap 1 vs. lap 2+ skilt med farge+tekstbadge.
|
||
Org-individuelle turneringers offentlige tilskuervisning er BEVISST
|
||
utenfor omfang her (finnes ikke i det hele tatt ennå — `/t/[id]/live`
|
||
er lagturnerings-only — egen, større oppgave hvis/når etterspurt).
|
||
Migrasjon 071 — se ADR-067.
|
||
|
||
**✅ Eclectic ER NÅ BYGGET (ADR-068, 2026-08-14), "Del C" — siste del,
|
||
hele den tredelte utvidelsen er dermed ferdig.** Nytt format for
|
||
org-individuelle turneringer (`scoring_method = eclectic_gross/
|
||
eclectic_net/eclectic_stableford`) -- beste resultat PER HULL på
|
||
tvers av turneringens EGNE runder (krever samme bane, avvist tydelig
|
||
ellers). IKKE det samme som OOM sin ennå-ubygde "Eclectic på tvers av
|
||
lenkede turneringer" (se Order of Merit-seksjonen lenger ned) -- den
|
||
motoren kan trolig gjenbrukes derfra senere, men selve OOM-
|
||
integrasjonen er fortsatt ugjort. Migrasjon 072 — se ADR-068.
|
||
|
||
- **Ingenting av dette er designet eller bygget ennå** — kun fanget her slik
|
||
at det ikke går i glemmeboken. Naturlig neste steg når dette tas fatt på:
|
||
vurder de fire hver for seg (ikke som én stor runde), start med «Try all»
|
||
(lavest kostnad) hvis en rask seier er ønskelig, eller med «Københavner»
|
||
hvis en bredere individuell/felt-basert turneringstype uansett skal bygges
|
||
først som fundament for de andre.
|
||
- **Ressurs, lagt til 2026-07-19:** brukeren har lastet opp tre PDF-er til
|
||
prosjektroten (`spilletyper-og-spilleformer-2023.pdf`, `Live Tourney _ A
|
||
Guide to Handicap Scoring in Golf for Tournaments.pdf`, `SCGA Club
|
||
Digest.pdf`) som til sammen skal gi en tydelig beskrivelse av hvordan HCP
|
||
og mottatte/tildelte slag beregnes/fordeles — les disse FØR design av
|
||
handicap-/slagfordelingslogikken for disse fire formatene, se CLAUDE.md
|
||
sin "Autoritative kilder"-seksjon.
|
||
|
||
### 2026-07-30: alle åtte formater — BACKEND ferdig, frontend gjenstår
|
||
|
||
Brukeren utvidet listen fra fire til åtte format denne runden (Shamble,
|
||
Chapman/Pinehurst, Bingo Bango Bongo, Money Ball/Lone Ranger, Nassau Match
|
||
Play lagt til; «High-low-high»/«Try all» presist avklart med et fullt
|
||
utregnet eksempel fra bruker; «Robbins» droppet). Full plan skrevet og
|
||
godkjent (plan-modus), 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, per prosjektets etablerte
|
||
disiplin.
|
||
|
||
**Arkitektonisk hjem, bekreftet av bruker FØR bygging («begge, fra
|
||
start»):**
|
||
- Flatt felt, ingen sider (Københavner, Bingo Bango Bongo, Flaggturnering):
|
||
frittstående runder (`round.play_format`) OG den individuelle org-
|
||
turnering-modellen (`tournament.scoring_method`, ADR-037) — ALDRI
|
||
org-lagturneringer (ADR-011s to-lags-modell passer strukturelt ikke).
|
||
- To-siders (Chapman, Nassau, Shamble, Money Ball, High-low-high):
|
||
frittstående runder OG org-lagturneringer (`session.format`) — ALDRI den
|
||
individuelle org-modellen.
|
||
- **Shamble/Money Ball i frittstående runder:** bekreftet av bruker at ETT
|
||
LAG = HELE RUNDENS deltakersett, INGEN `round_side`-involvering — flere
|
||
lag som konkurrerer settes opp som separate runder koblet med det
|
||
allerede byggede `flight_group_id`-leaderboardet (2026-07-28).
|
||
|
||
**Per format, kort:**
|
||
1. **Chapman/Pinehurst ("Try all")** — INGEN ny motor. Ny
|
||
`ENGINE_FORMAT_ALIASES`-mekanisme i `app/handicap.py`
|
||
(`{"chapman": "foursome"}`, senere gjenbrukt for shamble/money_ball/
|
||
high_low_high) — strukturelt identisk med foursome. Migrasjon 041.
|
||
2. **Nassau Match Play** — INGEN ny motor, INGEN skjemaendring. Tre
|
||
parallelle vinduer (hull 1-9/10-18/1-18) av EKSISTERENDE hull-for-hull-
|
||
data, hver kjørt gjennom den allerede eksisterende `compute_match_
|
||
state`. Nye leseendepunkter (`/rounds/{id}/format-result/nassau`,
|
||
`/orgs/.../matches/{id}/nassau`). **Reelt funn under scratch-testing:**
|
||
org-matcher låser seg (409 ALREADY_DECIDED) straks OVERALL 18-hulls-
|
||
matchen er avgjort (ADR-012) — en runaway-margin kan derfor hindre at
|
||
alle 18 hull noensinne blir registrert, som igjen gir ufullstendige
|
||
front9/back9-vinduer. Ikke fikset (ville krevd å løsne en etablert
|
||
sikkerhetssperre) — dokumentert som en kjent v1-begrensning.
|
||
3. **Københavner** — ny motor (`copenhagen_points_for_hole`/`compute_
|
||
copenhagen_detail`, samme `(deltaker_id, verdi)`-per-hull-form som
|
||
`compute_skins_detail`). Migrasjon 042 (`round.play_format` +
|
||
`tournament.scoring_method` + `tournament_round_score.copenhagen_
|
||
points`). Alltid NETTO (individuell course handicap-allokering, ikke
|
||
match-play-relativ). **Reelt funn:** Københavner-poeng avhenger av ALLE
|
||
tre deltakerne samtidig — org-siden måtte derfor bryte det etablerte
|
||
"regn om for ÉN deltaker om gangen"-mønsteret (`_recompute_copenhagen_
|
||
points` regner om for alle tre ved hver hull-innsending).
|
||
4. **Bingo Bango Bongo** — ny motor (`bbb_points_for_hole`/`compute_bbb`).
|
||
Ny, liten dedikert tabell BEGGE steder (`round_bbb_hole`/`tournament_
|
||
round_bbb_hole`) siden bingo/bango/bongo er en per-HULL-fakta, ikke
|
||
per-spiller/side — passer ikke i `round_hole`s eksisterende XOR-form.
|
||
Migrasjon 043/044. Sveip-bonus (dobbelt poeng) er en valgfri
|
||
organisator-innstilling (`bbb_sweep_bonus_enabled`), IKKE fast på.
|
||
Lagvariant bevisst utenfor omfang v1.
|
||
5. **Flaggturnering** — ny motor (`flag_result`/`FlagResult`). INGEN nye
|
||
kolonner — gjenbruker eksisterende individuell-ball-lagring og
|
||
`course_handicap_snapshot`/`course_handicap` (allerede beregnet for
|
||
ALLE formater ved deltaker-opprettelse). Migrasjon 045/046 er REN
|
||
CHECK-utvidelse. v1: hull-granularitet, ingen fortsettelse på hull 19+.
|
||
Org-siden fikk et eget, PER-RUNDE (ikke summert-på-tvers-av-turneringen)
|
||
leseendepunkt (`/rounds/{id}/flag-result`), siden Flag strukturelt er en
|
||
enkelt-rundes-konkurranse, ikke et akkumulerbart poengtall.
|
||
6. **Shamble** — ny motor (`shamble_hole_score`, "beste N av M").
|
||
Migrasjon 047 (`shamble_best_n` på BEGGE `round`/`session`). Org-siden:
|
||
variabel lagstørrelse (2-4) håndteres via en NY, dedikert
|
||
`_compute_shamble_hole_results` i `scoring.py` (rå brutto, IKKE via den
|
||
generiske `_side_net`, som forutsetter en FAST enhetsstørrelse per
|
||
format) — kun et TAK (maks 4) håndheves ved tilføyelse, en tilsvarende
|
||
"minst 2"-fullstendighets-gate finnes bevisst IKKE for org-lagturneringer
|
||
(samme begrensning som fourball allerede har).
|
||
7. **Money Ball/Lone Ranger** — ny motor (`money_ball_hole_score`, fast
|
||
4-manns rotasjon). Ny `lineup_order`-kolonne BEGGE steder
|
||
(`round_participant`/`match_participant`, 0-3, unik per runde/side).
|
||
Migrasjon 048/049. **Viktig presisering:** rotasjonen er basert på
|
||
POSISJON i den faktisk spilte rekkefølgen (1. spilte hull = rotasjons-
|
||
indeks 0), IKKE rått hullnummer — riktig også for en runde/økt som ikke
|
||
starter på hull 1.
|
||
8. **High-low-high** — den mest nyskapende motoren
|
||
(`high_low_high_points_for_hole`/`high_low_high_running_score`) —
|
||
**verifisert tall for tall mot brukerens eget fullt utregnede eksempel**
|
||
(«1-1 etter hull 1», «2-1 til lag 2 før hull 3») i BÅDE enhetstestene og
|
||
scratch-API-testene. Nøkkelinnsikt fra brukerens presisering: et
|
||
uavgjort del-oppgjør («å dele et hull») gir NULL poeng til begge sider,
|
||
IKKE en 0,5/0,5-splitt. Migrasjon 050 er ren CHECK-utvidelse (individuell
|
||
ball, gjenbruker fourballs eksisterende lagring). Passer IKKE inn i den
|
||
ternære `HoleResult`-cachen (`match.status_text`/`points_side_a/b`) —
|
||
egen, dedikert leseendepunkt (`/orgs/.../matches/{id}/high-low-high`)
|
||
for org-siden, samme "regn ut ved lesing"-filosofi som Nassau. **Kjent,
|
||
ufarlig v1-kvirk:** den GENERISKE `match.status_text` viser en statisk
|
||
"AS"-plassholder for High-low-high-matcher (siden `_compute_hole_
|
||
results` bevisst returnerer tom liste for dette formatet) — ikke en
|
||
krasj, bare ikke spesielt informativt der; den ekte stillingen kommer
|
||
fra det dedikerte endepunktet.
|
||
|
||
**Verifisering, alle åtte formater:** isolerte `handicap_engine.py`-
|
||
enhetstester FØRST (98 totalt etter denne runden, opp fra 63), deretter
|
||
full scratch-API-verifisering i BEGGE relevante systemer per format
|
||
(isolert `teecup_app_scratch`-rolle + isolert scratch-MinIO + engangs
|
||
API-container, samme mønster som resten av prosjektet) — over 400
|
||
automatiserte sjekker totalt på tvers av de åtte formatenes test-skript,
|
||
pluss `test_isolation.sql` 12/12 uendret gjennom hele runden (migrasjonene
|
||
er rent additive).
|
||
|
||
**IKKE bygget i denne runden, bevisst neste steg:** frontend for samtlige
|
||
åtte formater — ingen ny UI for formatvalg (opprett-runde/opprett-økt-
|
||
skjemaene), ingen nye resultatvisninger (Nassau-vinduer, Københavner-/BBB-
|
||
poengtavler, Flag-nedtelling, Shamble/Money Ball-lagvisning, High-low-
|
||
high-stilling). 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/039: motor/skjema/API FØR frontend).
|
||
**Migrasjoner 041-050 er BYGGET OG SCRATCH-VERIFISERT, IKKE ENNÅ rullet ut
|
||
mot ekte `teecup_db`** — venter på eksplisitt bekreftelse fra bruker før
|
||
noen migrasjon kjøres mot produksjon, per CLAUDE.md sin ufravikelige regel.
|
||
|
||
**RETTELSE 2026-08-03 — avsnittet over er UTDATERT, stod aldri oppdatert
|
||
etter at frontend faktisk ble bygget:** verifisert direkte mot koden
|
||
(ikke bare mot denne filen) samme dag som rundeleaderboardets
|
||
V0-omskriving (se CHANGELOG.md 2026-08-03). Reell status nå:
|
||
- **Migrasjoner 041-052 ER rullet ut mot ekte `teecup_db`** — bekreftet
|
||
live (`round.play_format`-CHECK inkluderer alle 16 verdier,
|
||
`round_bbb_hole`/`lineup_order`/`tournament_round`-familien finnes alle
|
||
i produksjonsskjemaet).
|
||
- **Frontend for formatvalg ER bygget, i alle tre arkitektoniske hjem:**
|
||
`new-round.tsx` (frittstående runder, alle 8 + BBB-sveipbonus/Shamble-
|
||
beste-N-konfig), `tournament-program.tsx` sin `CreateSessionCard`
|
||
(org-lagturneringer, `SessionFormat` med alle 10 to-sidede format inkl.
|
||
chapman/shamble/money_ball/high_low_high), `individual-tournament-
|
||
detail.tsx` (org-individuell, `scoring_method`-bryter med
|
||
copenhagen/bingo_bango_bongo/flag).
|
||
- **Nye resultatvisninger ER bygget:** Nassau-vinduer og High-low-high-
|
||
stilling i `session-scorecard.tsx` (org) og `round-detail.tsx`s
|
||
`NassauPanel` (frittstående), Københavner-/BBB-poengtabeller i
|
||
`individual-tournament-detail.tsx`, Shamble/Money Ball-lagvisning i
|
||
`round-leaderboard.tsx`s `TeamFlightBoard` (frittstående — org-siden
|
||
bruker den eksisterende to-siders matchvisningen).
|
||
- Eneste sanne, fortsatt bekreftede GAP fra denne runden er separat og
|
||
udiskutert her: organisator-vendt opplasting av hero-/sponsorbilder
|
||
(se "Landingssider"-seksjonen lenger ned) — ikke relatert til de åtte
|
||
formatene.
|
||
|
||
### Utvidelse 2026-07-26: individuelle turneringer, flerrunde-turneringer, og Order of Merit
|
||
|
||
Reist av brukeren som svar på et spørsmål om et individuelt
|
||
turnering-leaderboard — svaret avdekket at ønsket er STØRRE enn bare
|
||
Københavner som ett format blant flere: TeeCup skal etter hvert kunne
|
||
arrangere ekte INDIVIDUELLE turneringer (ikke bare lagturneringer), disse
|
||
skal kunne gå over FLERE RUNDER, og det skal være mulig å sette opp et
|
||
**Order of Merit** — sesong-sammenlagt poeng/rangering på tvers av flere
|
||
separate arrangementer (brukerens eget eksempel: "klubbdager" som gjentas
|
||
gjennom en hel sesong, med en løpende sammenlagt-tabell). **Ren notat-
|
||
runde, ingen kode skrevet, ingen ADR skrevet ennå** — brukeren ba
|
||
eksplisitt om at dette kun noteres nå.
|
||
|
||
Tre distinkte, men beslektede strukturelle spørsmål — bevisst holdt fra
|
||
hverandre siden de har ulik arkitektonisk tyngde:
|
||
|
||
1. **Individuelle turneringer (ingen lag i det hele tatt).** ADR-011
|
||
låser v1 til NØYAKTIG to lag — en individuell turnering (et flatt felt
|
||
av spillere, som Københavner) bryter denne forutsetningen helt, ikke
|
||
bare "trenger flere enn to lag". Dette er allerede notert i ADR-011
|
||
sitt eget "Merk (2026-07-19)"-avsnitt via Københavner-eksemplet, men
|
||
brukerens presisering nå gjør det tydelig at individuelle turneringer
|
||
er et EGET, generelt tilfelle — ikke bare én formatvariant blant
|
||
fem. Trenger en egen ADR som avklarer om dette blir en helt egen
|
||
turnering-TYPE (parallell til dagens to-lags-type, med sin egen
|
||
`match`/scoring-modell) eller en utvidelse av eksisterende modell.
|
||
|
||
2. **Flerrunde-turneringer (samme arrangement, flere runder/dager,
|
||
sammenlagt resultat).** Dagens lagturneringer har ALLEREDE flere
|
||
`session`-er (f.eks. en Ryder Cup-helg med økter fredag/lørdag/søndag)
|
||
— men poengsummeringen er PER MATCH innad i hver økt, ikke en
|
||
sammenlagt SLAGSUM på tvers av runder slik en individuell
|
||
flerrunde-turnering (f.eks. en 3-dagers slagspillturnering) ville
|
||
trengt. **Reell strukturell kollisjon å avklare:** frittstående runder
|
||
(ADR-033 sin `round`/`round_participant`/`round_hole`, Beslutning A)
|
||
er BEVISST bygget helt UTENFOR organisasjon/RLS-systemet
|
||
(`plain_connection()`, eid av `user_id`, ingen `organization_id` i
|
||
det hele tatt) — mens turneringer er strengt org-scopet og RLS-
|
||
beskyttet. En org-arrangert flerrunde-individuell-turnering trenger
|
||
runder som lever INNENFOR en organisasjons/turnerings-kontekst — dette
|
||
er IKKE det samme systemet som de personlige rundene, selv om
|
||
datamodellen (hull-for-hull-registrering) sannsynligvis ligner mye.
|
||
Må avklares eksplisitt: gjenbruke `round`-tabellene (utvidet med en
|
||
valgfri turnering-/org-kobling), eller bygge en parallell, org-scopet
|
||
rundemodell? Dette er trolig den vanskeligste enkeltbeslutningen av de
|
||
tre.
|
||
|
||
3. **Order of Merit (sesong-sammenlagt på tvers av FLERE separate
|
||
turneringer/arrangementer).** Krever et HELT NYTT overordnet konsept
|
||
som ikke finnes i skjemaet i dag — noe a la en "sesong" eller "serie"
|
||
som grupperer flere separate turnering-rader og akkumulerer poeng per
|
||
spiller på tvers av dem, med sin egen løpende sammenlagt-rangering.
|
||
Forutsetter sannsynligvis at (1) og (2) over er løst først (det er
|
||
individuelle arrangementer som skal telle inn i et Order of Merit,
|
||
ikke lag-baserte Ryder Cup-turneringer) — naturlig SISTE steg av de
|
||
tre, ikke noe som kan designes isolert.
|
||
|
||
**Ingenting av dette er designet eller bygget** — kun fanget presist her
|
||
slik at retningen er dokumentert før noe glemmes. Se også ADR-011 sitt
|
||
eget notat (samme sak, kortere) og "Åpne spørsmål"-listen i
|
||
ARCHITECTURE_DECISIONS.md.
|
||
|
||
### Oppdatering 2026-07-26, samme dag: grunnstruktur for (1) og (2) AVKLART — se ADR-037
|
||
|
||
Brukeren ba om å starte ADR-runden på strukturspørsmålet direkte. Fire
|
||
load-bærende beslutninger avklart eksplisitt (AskUserQuestion), full
|
||
begrunnelse i **ADR-037** (ARCHITECTURE_DECISIONS.md):
|
||
- **Ny, parallell org-scopet datamodell** — IKKE en utvidelse av
|
||
ADR-033s `round`-tabeller (unngår hybrid/betinget RLS, bevarer et
|
||
tidligere bevisst valg).
|
||
- **Samme `tournament`-tabell**, ny `format_type`-diskriminator
|
||
(`'team'`/`'individual'`) — gjenbruker synlighet/join-kode/status
|
||
helt uendret.
|
||
- **Flerrunde fra start**: ny `tournament_round`-tabell (økt-lignende),
|
||
sammenlagt resultat summert ved lesing på ekte deltaker-id (samme
|
||
prinsipp som det eksisterende lag-leaderboardet).
|
||
- **Rå slag lagres OG poeng caches per format** — samme mønster som
|
||
`match.points_side_a/b` i dag. Nye formater (Københavner m.fl.) blir
|
||
dermed i hovedsak: én ny motorfunksjon i `handicap_engine.py` + én ny
|
||
CHECK-verdi, ikke en skjemaendring.
|
||
|
||
**Viktig presisering som oppsto underveis:** "flight" i en formell
|
||
org-turnering er KUN en tee-tid-gruppering, IKKE en leaderboard-grense
|
||
(leaderboardet spenner alltid hele feltet) — ULIKT den ad hoc
|
||
"flere flighter i en frittstående runde"-ideen (der leaderboardet
|
||
bevisst er avgrenset til det man selv satte opp). De to holdes bevisst
|
||
ADSKILT nå, ikke forent slik forrige runde antydet — se egen seksjon
|
||
lenger ned i denne filen ("Frittstående runder: flere flighter...").
|
||
|
||
**Fortsatt IKKE avgjort:** de fem konkrete formatenes egne poengregler
|
||
(punkt utenfor denne strukturrunden), og (3) Order of Merit — bekreftet
|
||
som naturlig SISTE steg, ikke designet.
|
||
|
||
### Oppdatering 2026-07-30: migrasjon + motor + API BYGGET OG SCRATCH-VERIFISERT, IKKE ENNÅ RULLET UT
|
||
|
||
Migrasjon `040_individual_tournaments.sql` skrevet nøyaktig etter ADR-037s
|
||
fire beslutninger (`tournament.format_type`/`scoring_method`,
|
||
`tournament_round`, `tournament_participant`, `tournament_round_participant`,
|
||
`tournament_round_hole`, `tournament_round_score` — full RLS org-isolasjon
|
||
på alle fem nye tabellene). Scratch-verifisert alene FØR API-et ble bygget
|
||
(alle 40 migrasjoner kjørte rent i rekkefølge, `test_isolation.sql` 12/12,
|
||
10 egne funksjonelle sjekker: kryss-org-isolasjon på 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 fordelingslogikk (bruker samme
|
||
`allocate_over_played_holes`-output som resten av motoren). 8 nye tester i
|
||
`test_handicap_engine.py`, alle 63 (55 eksisterende + 8 nye) bestått.
|
||
|
||
**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
|
||
`AllowanceStrategy`-familie som er bygget for et relativt to-siders
|
||
oppgjør), hull-for-hull-scoring (`PATCH .../holes/{n}`, cacher
|
||
brutto/netto/Stableford-total på nytt ved hver innsending -- samme mønster
|
||
som `recompute_and_cache_match_state`), og et leaderboard som summerer
|
||
`tournament_round_score` PÅ TVERS AV RUNDER ved lesing (Beslutning C).
|
||
`tournaments.py` sin `TournamentCreate`/`TournamentUpdate`/`Tournament`
|
||
utvidet med `format_type`/`scoring_method` (samme `exclude_unset`-PATCH-
|
||
mønster som resten av filen).
|
||
|
||
**Reelt funn under bygging, ikke antatt riktig:** leaderboard-endepunktet
|
||
kunne IKKE hete `/orgs/{id}/tournaments/{id}/leaderboard` -- den stien er
|
||
allerede `tournaments.py` sitt LAG-leaderboard (points_side_a/b, forventer
|
||
nøyaktig to lag), og siden `tournaments.router` registreres FØR
|
||
`individual_tournaments.router` i `main.py`, ville det eksisterende
|
||
endepunktet stille skygget for det nye (funnet presist ved en ekte
|
||
API-test som krasjet på `points_side_a`-formen den ikke fikk). Løst ved å
|
||
gi det et eget navn, `/individual-leaderboard` -- samme kollisjonsklasse
|
||
som `/rounds` vs. `/my-rounds` tidligere i prosjektet, denne gangen unngått
|
||
fra start i stedet for oppdaget i produksjon.
|
||
|
||
**Reelt funn under selve scratch-API-testingen, fikset FØR utrulling:**
|
||
`list_rounds`/`list_tournament_participants` manglet en eksplisitt
|
||
"finnes turneringen"-sjekk (samme mønster `list_sessions` allerede har for
|
||
lagturneringer) -- 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. Begge
|
||
rettet til å matche `list_sessions` sin konvensjon 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 (org→spillere→bane→18 hull→tee→individuell
|
||
turnering→runde→to turnering-deltakere→to rundedeltakere med riktig
|
||
beregnet course handicap→hull-for-hull-scoring→leaderboard), hånd-
|
||
utregnet netto-kryssjekk for begge spillere (course handicap 11/20,
|
||
stemte eksakt), `front_9`-runde avviser hull utenfor omfang, kryss-org-
|
||
isolasjon (bekreftet BÅDE at en fremmed org ikke ser dataene OG at en
|
||
FK-basert skriving på tvers av org blokkeres), slette-vern (runde MED
|
||
deltakere avvist 409, turnering-deltaker fortsatt referert av en
|
||
rundedeltaker avvist 400 RESTRICT-FK). `test_isolation.sql` 12/12
|
||
uendret (additiv migrasjon).
|
||
|
||
**Autorisasjon — ✅ SCORING STRAMMET INN, SCRATCH-VERIFISERT OG LIVE
|
||
2026-07-30** (samme dag, egen del-runde etter frontend-utrullingen): ny
|
||
`user_is_own_tournament_participant` (`app/team_authz.py`, samme mønster
|
||
som ADR-023s `user_is_match_participant`) brukt av `update_hole` — kun
|
||
deltakeren selv eller org-admin kan nå skrive score for en gitt
|
||
rundedeltaker. Runde-/deltaker-oppsett forblir bevisst på org-medlemsnivå
|
||
(samme presedens som `session`/`team_roster` i tournaments.py). 13 nye
|
||
scratch-sjekker + full 52-punkts regresjon, se CHANGELOG.md
|
||
2026-07-30 for full detalj. De fem konkrete formatene (Københavner m.fl.)
|
||
og Order of Merit fortsatt ikke designet.
|
||
|
||
### Oppdatering 2026-07-30, samme dag: frontend HÅNDKODET og LIVE
|
||
|
||
Brukeren ba eksplisitt om å kode det selv (ikke V0), bruke Chrome
|
||
DevTools til faktisk browserverifisering, og bruke mer enn grønn/oransje.
|
||
Ny `components/individual-tournament-detail.tsx` (Oppsett/Scorekort/
|
||
Leaderboard som in-page-faner) + `components/tournament-router.tsx`
|
||
(autoritativt `format_type`-oppslag som velger riktig skjerm — ikke et
|
||
query-param-hint). `dashboard.tsx` sin "Ny turnering"-flyt fikk et
|
||
Lag/Individuell-valg. `--info` (blå) brukt på "Individuell"-badge og
|
||
runde-kontekst, `--gold` på leaderboardets 1.-plass — samme validerte
|
||
tokens `round-card.tsx` allerede etablerte, ikke nye ukalibrerte farger.
|
||
|
||
**Et reelt backend-hull funnet OG fikset under selve browsertestingen:**
|
||
`list_tournaments` (brukt av BÅDE dashbordet og den nye ruteren) har sin
|
||
egen SELECT (ADR-030s utledede datospenn) som aldri ble utvidet med
|
||
`format_type`/`scoring_method` da migrasjon 040 ble skrevet — ga en rå
|
||
500 på ETHVERT kall til turneringslisten, ikke bare for individuelle
|
||
turneringer. Rettet. Et layout-hull i headeren (navn ble avkuttet på
|
||
smal mobil) og et manglende form-språk i scorekort-cellene (kun farge,
|
||
ikke sirkel/firkant som `round-scorecard.tsx` sin `ScoreMark`) ble også
|
||
funnet og rettet samme runde. Se CHANGELOG.md 2026-07-30 for full
|
||
verifiseringsdetalj (håndregnet HCP-kryssjekk, databasebekreftet
|
||
persistens, full opprett-ny-turnering-flyt testet fra bunnen).
|
||
**RETTELSE 2026-08-14 — de to linjene under var stale, aldri oppdatert
|
||
etter utrulling.** Migrasjon 040 er bekreftet live i ekte `teecup_db`
|
||
(`tournament.format_type`/`scoring_method` finnes i produksjonsskjemaet
|
||
med begge CHECK-constraints, verifisert direkte 2026-08-14). Individuelle
|
||
turneringer må ha vært rullet ut en gang mellom 2026-07-30 og 2026-08-04
|
||
— Order of Merit (migrasjon 055, se eget punkt lenger ned) forutsetter at
|
||
individuelle turneringer allerede er live, og ble selv bekreftet
|
||
"BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-04". Denne seksjonen
|
||
fikk aldri sitt eget "rullet ut"-notat da det skjedde — historikken under
|
||
er beholdt uendret, kun disse to linjene er korrigert:
|
||
~~IKKE rullet ut mot ekte systemer ennå~~ / ~~IKKE rullet ut mot ekte
|
||
`teecup_db` ennå~~ → **✅ rullet ut, live siden senest 2026-08-04.**
|
||
|
||
---
|
||
|
||
## Varsler: push til telefon + in-app varslingssenter — ✅ HELT FERDIG 2026-07-28 (begge deler)
|
||
|
||
**In-app varslingssenteret er nå komplett** (bygget 2026-07-25/26, utvidet
|
||
2026-07-28): bjelle-ikon i dashbord-header, `/my-notifications`, fire
|
||
triggerpunkter (venneforespørsel sendt/akseptert, medspiller lagt til på
|
||
en runde, en venn ser en synlig runde, en tilkoblet runde fullført) OG en
|
||
e-post-fallback PER TYPE (`user_notification_email_pref`, trygg standard
|
||
= ingen e-post). Se CHANGELOG.md for full detalj. Kun push til
|
||
telefonens eget OS-varslingssystem (under) gjenstår av det opprinnelige
|
||
forslaget.
|
||
|
||
Brukeren spurte om dagens PWA-oppsett kan varsle telefonens eget
|
||
varslingssystem (f.eks. ved en ny venneforespørsel), og ba om at det uansett
|
||
finnes en in-app-fallback på dashbordet (varslingsindikator + en side med
|
||
uleste varsler) — med et V0-prompt klart "i tilfelle".
|
||
|
||
**Push-varsler til telefonens OS — ✅ BYGGET, SCRATCH-/BROWSERVERIFISERT
|
||
OG LIVE 2026-07-28 (Web Push/VAPID):**
|
||
- Ny migrasjon `037_push_subscriptions.sql` -- `push_subscription`
|
||
(`user_id`, `endpoint` unik, `p256dh`/`auth`), samme `plain_connection()`-
|
||
mønster som `notification`/`round`/`friendship` (ingen RLS, bruker-eid).
|
||
Én bruker kan ha flere abonnement (flere enheter/nettlesere).
|
||
- Nytt `app/push.py` (`send_push_to_user`) -- `pywebpush`, kalt fra
|
||
`create_notification()` (den eneste skrivevegen inn, se
|
||
`app/routers/notifications.py`) for ALLE fire varseltyper. **Bevisst
|
||
ingen egen per-type opt-in** (ulikt e-post-fallbacken) -- selve det å
|
||
abonnere ER samtykket, en abonnert enhet får push for alt. VAPID-nøkler
|
||
er valgfrie (`settings.PUSH_CONFIGURED`, `app/config.py`) -- samme
|
||
grasiøs-degraderings-mønster som SMTP, push forsøkes rett og slett ikke
|
||
uten nøkler satt. En utløpt/tilbakekalt abonnement (404/410 fra
|
||
push-tjenesten) ryddes automatisk bort, andre feil isoleres med
|
||
`traceback.print_exc()` og lekker aldri til kalleren (samme mønster som
|
||
e-post-utsendingen).
|
||
Nye endepunkter: `GET /push/vapid-public-key` (offentlig), `POST/DELETE
|
||
/push/subscribe` (krever sesjon).
|
||
- Frontend: `public/sw.js` fikk `push`/`notificationclick`-håndtering
|
||
(viser native OS-varsel selv når ingen fane er åpen, klikk fokuserer
|
||
eller åpner appen på riktig `link_path`). Ny `lib/push-subscribe.ts`
|
||
(feature-deteksjon inkl. iOS-hjemskjerm-kravet, abonner/avmeld). Ny
|
||
seksjon "Push-varsler på denne enheten" i `/account`
|
||
(`PushNotificationSection`), enkel av/på-bryter, håndterer avslått
|
||
tillatelse med en forklarende tekst i stedet for en generisk feil.
|
||
- **Viktig plattformbegrensning, uendret:** på iOS Safari fungerer Web
|
||
Push KUN når PWA-en er lagt til på hjemskjermen -- dekket eksplisitt av
|
||
`isPushSupported()`.
|
||
- **Scratch-verifisert (27/27 sjekker, delt testløp med de tre andre
|
||
punktene under samme dag):** VAPID-nøkkel korrekt 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, ingen
|
||
krasj). **Browserverifisert:** UI-et rendrer korrekt, klikk kaller
|
||
`Notification.requestPermission()` -- i denne automatiserte nettleser-
|
||
økten var tillatelsen forhåndssatt til "denied" av miljøet selv (ingen
|
||
ekte permission-dialog kan trigges av testverktøyet), og avslag ble vist
|
||
med riktig forklarende tekst uten konsollfeil. **Ærlig begrensning:**
|
||
en ekte innvilget tillatelse → ekte abonnement → ekte levert OS-varsel
|
||
er IKKE bevist i denne runden -- krever en ekte enhet/nettleserøkt
|
||
utenfor automatisert testing. Bruker bør selv teste dette før full
|
||
tillit.
|
||
**Rullet ut live 2026-07-28**, bruker bekreftet eksplisitt: migrasjon
|
||
037 kjørt mot ekte `teecup_db`, VAPID-nøkkelpar generert lokalt
|
||
(`cryptography`, EC P-256) og lagt inn i ekte `.env` uten å noensinne
|
||
vises i klartekst i chatten (samme regel som alle andre hemmeligheter i
|
||
prosjektet), `docker compose up -d --build teecup_api teecup_frontend`.
|
||
Verifisert: `/health`/`/dashboard` → 200, `GET /push/vapid-public-key`
|
||
over ekte https ga korrekt offentlig nøkkel, `teeoff.no` upåvirket.
|
||
|
||
**In-app varslingssenter — ✅ BYGGET, i hovedsak akkurat som beskrevet
|
||
under (skjema/endepunkter/rute-navn stemmer med forslaget), pluss
|
||
per-type e-post-fallback som ikke var en del av det opprinnelige
|
||
forslaget:**
|
||
- Ny tabell `notification` (arbeidsnavn): `id`, `user_id` (mottaker),
|
||
`type`, `message` (ferdig norsk tekst, samme snapshot-prinsipp som
|
||
`author_display_name` i meldinger — unngår å måtte slå opp relaterte
|
||
data på nytt ved hver lesing), `link_path` (f.eks. `/my-friends`),
|
||
`created_at`, `read_at` (nullable).
|
||
- Triggerpunkter nå (matcher det som faktisk er bygget, ADR-036 fase 1):
|
||
`POST /friends` → varsel til mottaker ("X har sendt deg en
|
||
venneforespørsel"), `POST /friends/{id}/accept` → varsel til den
|
||
opprinnelige forespørreren ("X godtok venneforespørselen din").
|
||
Åpent for flere triggerpunkter etter hvert som appen får flere
|
||
hendelser verdt å varsle om.
|
||
- Nye endepunkter (arbeidsnavn): `GET /notifications` (liste, nyeste
|
||
først), `GET /notifications/unread-count` (lett, til selve
|
||
indikator-tallet), `POST /notifications/{id}/read`,
|
||
`POST /notifications/read-all`.
|
||
- Frontend: bjelle-ikon + tall-merke i dashbordets header, ny rute
|
||
`/my-notifications` (IKKE `/notifications` — det ville krasjet med det
|
||
nye API-prefikset, samme kollisjonsklasse som `/rounds`/`/friends`
|
||
tidligere, unngått fra start denne gangen).
|
||
|
||
**V0-prompt skrevet, IKKE sendt til V0 ennå:**
|
||
|
||
> Legg til en varslingsindikator i TeeCups dashbord-header (Next.js +
|
||
> Tailwind + shadcn/ui, mobil-først, eksisterende merkevarefarger): et
|
||
> bjelle-ikon ved siden av "Konto"/"Logg ut", med et lite, tydelig
|
||
> tall-merke når det finnes uleste varsler (ingen merke når null).
|
||
>
|
||
> Design i tillegg en egen "Varsler"-side den lenker til:
|
||
> - En liste over varsler, nyeste øverst, hver rad med kort tekst, et
|
||
> tidspunkt (f.eks. "for 2 timer siden"), og en tydelig visuell
|
||
> forskjell mellom lest/ulest (IKKE kun farge — bruk f.eks. en liten
|
||
> prikk pluss fet skrift for uleste, ikke fargen alene).
|
||
> - En "Merk alle som lest"-knapp øverst.
|
||
> - Hver rad er klikkbar og fører videre til det varselet gjelder.
|
||
> - En tydelig, vennlig tomtilstand ("Ingen varsler ennå").
|
||
>
|
||
> Tilgjengelighet er et ufravikelig krav: god kontrast, stor nok skrift,
|
||
> store trykkflater (min. 44px), aldri ikon-only uten tekstlabel på
|
||
> viktige handlinger (bjelleikonet i header er et unntak siden det er et
|
||
> universelt gjenkjent symbol, MEN skal ha en beskrivende
|
||
> `aria-label` som inkluderer antall uleste).
|
||
|
||
**Status: In-app varslingssenter BYGGET, SCRATCH-VERIFISERT OG RULLET UT
|
||
LIVE 2026-07-26** (bruker bekreftet eksplisitt, se CLAUDE.md sin
|
||
statuslogg for utrullingsdetalj — denne linjen var utdatert helt til den
|
||
ble rettet i en senere gjennomgang samme dag). V0 kjørte prompten (zip
|
||
20), backend bygget for å
|
||
matche eksakt: migrasjon `026_notifications.sql` (tabell `notification`,
|
||
`plain_connection()`-mønster, ingen RLS — samme som personlig profil/
|
||
runder/venner), ny `app/routers/notifications.py` (`create_notification()`
|
||
delt hjelpefunksjon + fire endepunkter: `GET /notifications`,
|
||
`GET /notifications/unread-count`, `POST /notifications/{id}/read`,
|
||
`POST /notifications/read-all`). To trigger-punkter koblet inn i
|
||
`friends.py` (kun venneforespørsel-hendelsene som faktisk finnes i dag,
|
||
ADR-036 fase 1): `POST /friends` varsler mottakeren, `POST /friends/{id}/
|
||
accept` varsler den opprinnelige forespørreren.
|
||
`components/notifications.tsx` + ny rute `/my-notifications` (IKKE
|
||
`/notifications` — samme kollisjonsklasse unngått fra start som
|
||
`/rounds`/`/friends`). Bjelle-ikonet fra V0s dashbord-eksport portert inn
|
||
i den LIVE `dashboard.tsx` sin header (ikke en full revert av filen —
|
||
samme kirurgiske uttrekk-mønster som alltid), koblet til et ekte
|
||
`GET /notifications/unread-count`-kall ved mount.
|
||
**Scratch-verifisert i samme testløp som rundeleaderboardet under** (se
|
||
den seksjonen for detaljer om selve scratch-infrastrukturen, totalt
|
||
39/39 sjekker på tvers av begge funksjonene): full
|
||
forespørsel→varsel→lest-syklus begge retninger, `mark-all-read`, og
|
||
eksplisitt kryss-bruker-isolasjon (bruker B kan ikke markere bruker A sitt
|
||
varsel som lest via id-gjetting — stille no-op, ikke en feilmelding som
|
||
ville lekket at id-en fantes). Ekte typesjekket produksjonsbuild kompilerte
|
||
rent, `/my-notifications` listet blant rutene.
|
||
**Ikke bygget i denne runden, bevisst utenfor omfang:** ekte push til
|
||
telefonens OS (egen, større runde, se vurderingen over), e-post-fallback
|
||
(se tillegget under, fortsatt kun foreslått).
|
||
**Venter på brukerens bekreftelse før migrasjon 026 kjøres mot ekte
|
||
`teecup_db` og containerne redeployes** — se CLAUDE.md for den samlede
|
||
utrullingsplanen (denne runden + rundeleaderboardet under ble bygget
|
||
sammen).
|
||
|
||
### Tillegg 2026-07-25: e-post som fallback-kanal, betinget av samtykke — ✅ BYGGET OG LIVE 2026-07-28
|
||
|
||
**Bygget rett til den "fremtidig presisering"-varianten under, ikke det
|
||
enklere forslaget:** brukeren ba eksplisitt om at mottakeren skal kunne
|
||
velge HVILKE varseltyper som skal gi e-post, ikke én global av/på-bryter
|
||
— ny `user_notification_email_pref`-tabell (migrasjon 033, samme
|
||
"tilstedeværelse = valgt"-mønster som `friend_categorization`/
|
||
`round_visible_category`, trygg standard = ingen typer valgt). E-post-
|
||
utsendingen ligger ETT sted (inni `create_notification()` selv,
|
||
`app/routers/notifications.py`), så den dekker automatisk alle nåværende
|
||
OG fremtidige varseltyper (`friend`/`round`/`result`/`tournament`) uten
|
||
at hvert kallsted må huske det selv. Ny `send_notification_email()` i
|
||
`app/email.py`, ny seksjon "Varsler på e-post" i `/account`. Se
|
||
CHANGELOG.md 2026-07-28 for full detalj (19/19 scratch-sjekker).
|
||
|
||
Opprinnelig forslag, for historikkens skyld: brukeren foreslo at TeeCup i
|
||
tillegg sender en e-post til mottakeren av en venneforespørsel ("du har
|
||
fått en forespørsel, åpne appen for å se den") — MEN kun hvis brukeren
|
||
har akseptert e-post som kommunikasjonskanal fra TeeCup.
|
||
|
||
**Sjekket eksisterende kode:** det finnes I DAG ingen generell
|
||
kommunikasjons-/varslings-samtykke-flagg på `app_user` — det eneste
|
||
samtykket i skjemaet er `tournament_registration.consent_given_at`
|
||
(ADR-017), som er noe HELT ANNET (samtykke til selve
|
||
turneringspåmeldingen, org-scopet, ikke en kontoinnstilling). Dette må
|
||
altså bygges som et nytt, eget felt, ikke gjenbrukes.
|
||
|
||
**Foreslått, ikke bekreftet:**
|
||
- Nytt `app_user.notification_emails_enabled boolean NOT NULL DEFAULT
|
||
false` — OPT-IN, ikke opt-out (samme "trygg standard"-filosofi som
|
||
resten av appen, f.eks. rundevisibilitet default `private`). Satt via
|
||
en ny bryter i kontoinnstillinger (`/account`), IKKE en del av den
|
||
obligatoriske profil-fullføringen (dette er valgfritt, ikke påkrevd).
|
||
- Ny `send_friend_request_email()` i `app/email.py` — følger EKSAKT
|
||
samme mønster som de fem eksisterende utsendingsfunksjonene der
|
||
(nb/en-maler, `_send_sync` via `asyncio.to_thread`, driftsfeil lekker
|
||
aldri til klientresponsen). Sendes fra `POST /friends`, KUN hvis
|
||
mottakeren har `notification_emails_enabled = true`.
|
||
- **Fremtidig presisering, ikke et problem nå:** med kun ÉN varseltype
|
||
(venneforespørsel) holder én global boolean. Den dagen appen får flere
|
||
varseltyper (ADR-036 fase 2s rundevisibilitet, fremtidige
|
||
turnering-hendelser, osv.) bør dette trolig bli et SETT av brytere per
|
||
type, ikke én global av/på — notert her for å ikke bli glemt, ikke
|
||
løst nå.
|
||
|
||
---
|
||
|
||
## PWA-installasjon: hvordan få brukere til å installere raskt — ✅ BYGGET OG LIVE 2026-07-28
|
||
|
||
Brukeren reiste dette som en oppfølging av varsel-diskusjonen: hvordan få
|
||
brukere til "nærmest umiddelbart" å installere TeeCup som app på
|
||
telefonen, gitt at ADR-028 allerede har bygget selve PWA-fundamentet
|
||
(manifest, service worker, ikoner, "Legg til på Hjemskjerm"-metadata) —
|
||
men ingenting proaktivt OPPFORDRER til installasjon i dag.
|
||
|
||
**Plattformvirkeligheten, avgjør hele designet:**
|
||
- **Android/Chrome-familien:** nettleseren fyrer selv av et
|
||
`beforeinstallprompt`-event når siden kvalifiserer (manifest+service
|
||
worker+https — alt allerede på plass). Fanges opp med
|
||
`event.preventDefault()` + lagres, og kan trigges SENERE fra en egen
|
||
knapp via `event.prompt()` — full kontroll på NÅR spørsmålet stilles,
|
||
ikke bare nettleserens egen timing.
|
||
- **iOS Safari: INGEN programmatisk vei finnes i det hele tatt.** Apple
|
||
har aldri implementert `beforeinstallprompt`. Eneste vei er den
|
||
manuelle Del-ikon → "Legg til på Hjemskjerm"-flyten — appen kan KUN
|
||
vise en instruksjonsoverlegg (f.eks. med skjermbilde/animasjon av
|
||
hvilken knapp som skal trykkes), aldri utløse selve installasjonen.
|
||
Dette er en hard Apple-begrensning, ikke noe TeeCup kan designe seg
|
||
rundt.
|
||
- **Deteksjon nødvendig for begge retninger:** `matchMedia(
|
||
"(display-mode: standalone)")` (evt. `navigator.standalone` på eldre
|
||
iOS) avslører om brukeren ALLEREDE kjører den installerte PWA-en — vis
|
||
ALDRI noe installasjons-UI da. iOS-vs-Android/Chrome avgjøres med
|
||
UA-sniffing (upresist, men standard og nødvendig her siden det ikke
|
||
finnes noen bedre feature-deteksjon) for å velge riktig av de to
|
||
variantene (ekte knapp vs. instruksjonsbanner) — andre nettlesere uten
|
||
noen reell installasjonsvei bør ikke vise noe i det hele tatt.
|
||
|
||
**Om "nærmest umiddelbart" — mild uenighet, med begrunnelse:** et
|
||
prompt FØR brukeren har vist noen interesse (f.eks. på selve
|
||
innloggingsskjermen) treffer typisk dårlig og føles påtrengende, OG
|
||
`beforeinstallprompt` har ikke alltid rukket å fyres av så tidlig uansett.
|
||
Appen har derimot ALLEREDE et universelt, høy-intensjons sjekkpunkt HVER
|
||
ny bruker går gjennom: den obligatoriske profil-fullføringen (ADR-031/
|
||
"obligatorisk profil-fullføring ved innlogging"-runden). Å vise
|
||
installasjons-oppfordringen RETT ETTER det steget — første gang brukeren
|
||
faktisk når `/dashboard` med en komplett profil — er trolig det beste
|
||
"nærmest umiddelbart"-tidspunktet som finnes: reelt tidlig, men etter at
|
||
brukeren allerede har investert litt og vist ekte intensjon, ikke et
|
||
kaldt overfall på innloggingssiden.
|
||
|
||
**Foreslått, ikke besluttet:**
|
||
- Vis KUN når `display-mode` ikke allerede er `standalone`.
|
||
- Første visning: rett etter fullført profil, første gang `/dashboard`
|
||
nås.
|
||
- "Ikke nå"-avvisning lagres i `localStorage` med en avkjølingsperiode
|
||
(f.eks. ikke vis på nytt før om N dager) — ingen server-side felt
|
||
nødvendig, dette er et rent klient-signal.
|
||
- To distinkte UI-varianter (Android: ekte "Installer"-knapp som kaller
|
||
`event.prompt()`; iOS: instruksjonsbanner) — INGEN visning for andre
|
||
nettlesere uten en reell installasjonsvei.
|
||
|
||
**Status: BYGGET akkurat som foreslått over, håndkodet (ikke V0), 2026-07-28:**
|
||
ny `lib/pwa-install.ts` (globalt fanget `beforeinstallprompt`-event via
|
||
`components/sw-register.tsx`, som allerede mountes på alle sider -- eventet
|
||
fyres kun én gang per side-liv og må derfor fanges tidligst mulig, ikke
|
||
først når selve dashbord-komponenten mountes). Ny `components/install-
|
||
prompt.tsx` (`InstallPrompt`) -- viser ALDRI noe hvis allerede installert
|
||
(`display-mode: standalone`) eller nylig avvist (`localStorage`-cooldown,
|
||
14 dager). To varianter: Android/Chrome-familien får en ekte
|
||
"Installer"-knapp (`event.prompt()`), iOS Safari får et instruksjonsbanner
|
||
(Del-ikon → "Legg til på Hjem-skjerm", INGEN programmatisk vei finnes der).
|
||
Andre nettlesere uten en reell installasjonsvei viser ingenting. Lagt inn i
|
||
`dashboard.tsx` rett under hilsenen -- akkurat sjekkpunktet foreslått over
|
||
(første/hver gang brukeren når dashbordet med komplett profil).
|
||
**Browserverifisert:** Chrome fyrte faktisk `beforeinstallprompt` i
|
||
testøkten, "Installer"-knappen rendret og var klikkbar, `event.prompt()`
|
||
ble kalt uten konsollfeil (selve den native installasjonsdialogen kan ikke
|
||
utløses i en automatisert nettleserøkt -- ærlig, forventet begrensning,
|
||
samme klasse som all annen "ekte OS-installasjon"-testing i prosjektet).
|
||
**Rullet ut live 2026-07-28** sammen med de tre andre punktene samme dag,
|
||
se CHANGELOG.md for felles utrullingsdetalj.
|
||
|
||
---
|
||
|
||
## Leaderboard for runder og turneringer — ✅ HELT FERDIG (runder 2026-07-26, turnering-individuell-rangering 2026-07-28)
|
||
|
||
Brukeren ba om et leaderboard for pågående og ferdige runder OG
|
||
turneringer, usikker på om det bør ligge der man allerede ser rundens
|
||
detaljer så langt (`round-stats.tsx`) eller integreres i selve
|
||
detaljsiden (`round-detail.tsx`) — og lastet opp to skjermbilder av
|
||
hvordan Golf GameBook har løst akkurat dette, som referanse. Bevisst
|
||
drøftet her, ikke besluttet — brukeren ba selv om å "drodle", ikke bygge.
|
||
|
||
**Referansen (Golf GameBook), oppsummert — IKKE noe TeeCup skal
|
||
kopiere rett av, kun inspireres av struktur/konsept:** en egen,
|
||
dedikert "Leaderboards"-fane nederst (sidestilt med "Rundeinfo" og
|
||
"Spill-feed", ikke en del av noen av dem), med faner ØVERST for ulike
|
||
scoringsmetoder ("Slagspill NET"/"Stableford NET"), en rangert liste
|
||
(#, navn, HCP, score, til par, "F" for ferdig), en blå "HCP-RUNDE"-
|
||
merkelapp, og at hver rad kan TRYKKES UT til å vise spillerens fulle
|
||
horisontale scorekort inline (samme hull-for-hull-tabellformat TeeCup
|
||
allerede har bygget i `round-scorecard.tsx`) pluss sosiale handlinger
|
||
(Lik/Kommentar/Statistikk).
|
||
|
||
**To reelle presiseringer funnet ved å faktisk sjekke koden, ikke antatt:**
|
||
1. **"Runder" kan få dette NÅ, ingen avhengighet til ADR-036 fase 3.**
|
||
En frittstående runde støtter allerede flere deltakere i dag (eier +
|
||
gjester, `round-detail.tsx` sine spiller-faner) med uavhengig
|
||
hull-for-hull-score hver — et rangert leaderboard PÅ TVERS av disse
|
||
deltakerne er fullt buildbart nå. Fase 3 (ekte medspillere med egen
|
||
konto) endrer ikke dette, det utvider bare HVEM som kan være en
|
||
deltaker.
|
||
2. **"Turneringer" har allerede et leaderboard** (`tournament-
|
||
leaderboard.tsx`, live) — men det er et LAG-POENG-leaderboard for
|
||
Ryder Cup-matchplay (ADR-011, to lag), strukturelt noe helt annet enn
|
||
Golf GameBooks individuelle slagspill-rangering. **Åpent spørsmål,
|
||
ikke avklart:** mener brukeren en individuell rangering INNAD i en
|
||
turnering (f.eks. rangere spillere etter brutto/netto score i en
|
||
økt, ved siden av det eksisterende lag-poeng-leaderboardet), eller
|
||
var "turneringer" ment mer løst/generelt? Bør avklares før noe
|
||
designes for turnering-siden av dette.
|
||
|
||
**Plassering — min foreløpige vurdering, ikke en konklusjon:** verken
|
||
`round-detail.tsx` (allerede tett under selve spillingen, samme
|
||
"for mye stablet oppå hverandre"-fare som tidligere runder denne uken
|
||
allerede ryddet opp i) eller `round-stats.tsx` (dedikert til DYP
|
||
enkelt-spiller-statistikk, ikke tvers-sammenligning) er et perfekt
|
||
hjem alene. To ideer, ikke gjensidig utelukkende:
|
||
- En KOMPAKT leaderboard-oppsummering (topp/posisjon, ikke full tabell)
|
||
øverst på `round-detail.tsx` — det man faktisk vil sjekke RASKT mens
|
||
man spiller ("hvem leder nå").
|
||
- Gjenbruk EKSISTERENDE infrastruktur for full detalj i stedet for å
|
||
duplisere Golf GameBooks "trykk ut for fullt scorekort inline":
|
||
`round-stats.tsx` har ALLEREDE en spillervelger (pill-rad) bygget for
|
||
flere-deltakere-runder — en leaderboard-rad kan trolig bare LENKE
|
||
dit/bytte valgt spiller, i stedet for å bygge en helt ny inline-
|
||
scorekort-mekanisme på nytt.
|
||
- Flere scoringsmetode-visninger (Slagspill NET vs. Stableford NET) —
|
||
TeeCup regner allerede Stableford klientside i `round-stats.tsx`, men
|
||
har ingen tilsvarende "netto slagspill"-rangeringsvisning i dag. Verdt
|
||
å designe eksplisitt om dette ønskes, ikke noe som følger gratis av
|
||
det som allerede finnes.
|
||
|
||
**Status: ingen kode, ingen V0-prompt ennå** — venter på at retning (og
|
||
særlig turnering-spørsmålet over) avklares før noe designes ferdig.
|
||
|
||
### Oppdatering 2026-07-26: RUNDE-leaderboardet HELT FERDIG (håndkodet, ikke V0 -- se under)
|
||
|
||
Brukeren ba eksplisitt om å få leaderboardet for frittstående runder på
|
||
plass (kun runder, ikke turneringer — turnering-spørsmålet over fortsatt
|
||
ikke avklart). Bygget som ny `GET /rounds/{round_id}/leaderboard`
|
||
(`app/routers/rounds.py`), samme `plain_connection()`/eier-only-
|
||
autorisasjon som resten av rundene (`_get_owned_round_or_404`).
|
||
|
||
**Datakontrakt:**
|
||
```
|
||
GET /rounds/{round_id}/leaderboard
|
||
{
|
||
"holes_planned": 9 | 18,
|
||
"completed": boolean,
|
||
"entries": [
|
||
{
|
||
"participant_id": string,
|
||
"display_name": string,
|
||
"is_owner": boolean,
|
||
"holes_played": number, // "thru"
|
||
"total_score": number | null, // null = ingen hull registrert ennå
|
||
"score_to_par": number | null,
|
||
"net_score_to_par": number | null // null hvis ingen course handicap
|
||
// (f.eks. gjest uten HCP oppgitt)
|
||
},
|
||
...
|
||
]
|
||
}
|
||
```
|
||
Sortert server-side stigende på `score_to_par` (lavest/best først, ingen
|
||
registrerte hull sist). Samme allokeringsalgoritme
|
||
(`allocate_strokes_by_index`) som resten av appen for netto — uavhengig
|
||
kryssjekket mot `handicap_engine.py` direkte i scratch, ikke bare "kjørte
|
||
uten feil".
|
||
|
||
**Scratch-verifisert (39/39 sjekker, to separate testløp):** varsel-
|
||
trigger-punktene fra samme runde (se "Varsler"-seksjonen), pluss full
|
||
leaderboard-runde (3 deltakere — eier ferdig 9 hull til par, gjest 9 hull
|
||
+1/hull, gjest kun 3 hull -1/hull — riktig rangering/thru/to-par for alle
|
||
tre), kryss-bruker-autorisasjon (403 for en annen bruker), ukjent
|
||
runde-id (404), OG en dedikert netto-kryssjekk (avvikende SI-rekkefølge,
|
||
kun 9 av 18 hull spilt, resultatet sammenlignet mot en UAVHENGIG
|
||
beregning via `handicap_engine.allocate_strokes_by_index` direkte —
|
||
stemte eksakt).
|
||
|
||
**V0-prompt sendt til bruker 2026-07-26, ikke kjørt i v0.app ennå:**
|
||
|
||
> Design et "Leaderboard"-visning for en enkelt frittstående golfrunde i
|
||
> TeeCup (Next.js + Tailwind + shadcn/ui, mobil-først, eksisterende
|
||
> merkevarefarger — grønn primær, oransje sekundær). Runden kan ha
|
||
> ALLE typer deltakerantall — fra kun eieren alene til flere flighter
|
||
> samtidig (opptil 13+ spillere er reelt mulig), så designet må skalere
|
||
> pent fra 1 til 15+ rader UTEN å bli en endeløs, monoton liste.
|
||
>
|
||
> Data kommer fra et allerede bygget API-endepunkt som returnerer, for
|
||
> runden: om den er fullført eller pågår, planlagt hullantall (9/18), og
|
||
> en liste med én rad per deltaker: navn, om det er rundens eier, antall
|
||
> hull spilt ("thru"), total score, score til par (brutto), og score til
|
||
> par netto (kan være fraværende — vises da ikke for den spilleren,
|
||
> IKKE som "0" eller en feil).
|
||
>
|
||
> **Ranger deltakerne** etter brutto score til par (lavest/best først).
|
||
> Gi et tydelig, men ikke overveldende, rangeringstall (#1, #2, ...) —
|
||
> delt plassering (likt resultat) skal vises tydelig som delt (f.eks.
|
||
> "T-2"), ikke to forskjellige tall for samme resultat.
|
||
>
|
||
> **Topp-plassering fortjener litt ekstra visuell vekt** (f.eks. en
|
||
> diskret kant/bakgrunnstone eller et lite ikon) — men ALDRI kun farge
|
||
> for å skille ledere fra resten (tilgjengelighetskrav, se under).
|
||
>
|
||
> **For en pågående runde:** vis "thru X" (f.eks. "thru 5") for spillere
|
||
> som ikke har fullført alle planlagte hull ennå, i stedet for en
|
||
> ferdig-markering. For en FULLFØRT runde: vis heller en tydelig
|
||
> "Ferdig"-markering per spiller i stedet for "thru X av X".
|
||
>
|
||
> **Score-til-par-tall** skal formateres på golfvis: "E" for jevnt med
|
||
> par (0), "+N" over, "−N" (ekte minustegn) under — ALDRI bare "0"/"-3"
|
||
> uten fortegn. Bruk FORM i tillegg til farge der du fremhever over/
|
||
> under par (f.eks. en liten sirkel/firkant-indikator, ikke bare
|
||
> tekstfarge) — samme "aldri kun farge"-prinsipp som resten av TeeCup.
|
||
>
|
||
> **Gi brukeren en brutto/netto-veksling** (to faner eller en enkel
|
||
> switch øverst) som bytter både HVILKET tall som vises OG selve
|
||
> rangeringsrekkefølgen mellom de to. Spillere uten et netto-tall (ingen
|
||
> HCP registrert) skal vises tydelig nederst/uten rangering i
|
||
> netto-visningen, ikke skjules eller krasje.
|
||
>
|
||
> **To distinkte merker, ikke ett:** et "Eier"-merke for rundens eier
|
||
> (kommer direkte fra API-et sitt `is_owner`-felt), OG uavhengig av det et
|
||
> "Deg"-merke for raden som tilhører DEN som ser på leaderboardet akkurat
|
||
> nå (siden runden også kan sees av lenkede medspillere, ikke bare eieren
|
||
> -- komponenten mottar hvilken `participant_id` som er "meg" som en egen
|
||
> prop utenfra, ikke fra selve leaderboard-dataen). De to kan gjelde samme
|
||
> rad (eieren ser sin egen runde) eller ulike rader (en medspiller ser
|
||
> både sin egen "Deg"-rad og eierens "Eier"-rad) -- design for begge.
|
||
>
|
||
> Design også en kompakt "mini-leaderboard"-variant (topp 3 + evt. "og
|
||
> N til") egnet til å vises øverst på selve rundens detaljside — et
|
||
> raskt "hvem leder nå"-blikk uten å måtte navigere til hele
|
||
> leaderboardet.
|
||
>
|
||
> Tilgjengelighet er et ufravikelig krav: god kontrast, stor nok skrift,
|
||
> store trykkflater (min. 44px), aldri kun farge for å formidle
|
||
> informasjon (rangering/over-under par), lesbar uten briller.
|
||
|
||
**Rettet i selve prompten 2026-07-26, FØR den ble kjørt i v0.app:** den
|
||
opprinnelige "Deg"-merke-instruksen antok feilaktig at kun eieren ser sin
|
||
egen runde -- ADR-036 fase 3 (bygget samme dag) gjør nå at en lenket
|
||
medspiller også kan se leaderboardet. Byttet til to uavhengige merker
|
||
("Eier" fra `is_owner`, "Deg" fra en egen `participant_id`-prop
|
||
komponenten mottar utenfra) -- se prompten over.
|
||
|
||
**Oppdatering, samme dag: HÅNDKODET i stedet for kjørt i v0.app.**
|
||
Brukeren gikk tom for V0-credits (samme situasjon som spillerliste-
|
||
redesignet rett over) og ba meg bygge direkte etter nøyaktig samme
|
||
designspesifikasjon som prompten over. Ny `components/round-leaderboard.tsx`
|
||
+ rute `/my-rounds/[id]/leaderboard`, pluss en `RoundLeaderboardMini`
|
||
(topp 3 + "og N til", lenket fra selve rundens detaljside) og en ny
|
||
"Se leaderboard"-lenke på hvert rundekort i `/my-rounds`-listen (krevde å
|
||
gjøre om `round-card.tsx` sin ytre `<Link>` til en `<div>` med to separate
|
||
lenker -- unngår en nestet `<a>`). Gjenbrukte bevisst etablerte mønstre
|
||
(`ScoreMark`s form+farge-språk fra `round-scorecard.tsx`, WS-sanntid-
|
||
mønsteret fra `round-stats.tsx`) fremfor å finne opp nye.
|
||
**To oppfølgingspunkter samme dag, begge bygget:** (1) leaderboardet
|
||
skulle vise hullscorer per spiller -- løst som en utvidbar rad (klikk for
|
||
å vise en horisontal strip med hull-for-hull brutto-merker), krevde en
|
||
liten backend-utvidelse (`LeaderboardHoleOut`/`holes`-felt, data var
|
||
allerede hentet, bare ikke eksponert). (2) et tredje "Poeng"
|
||
(stableford)-modus lagt til ved siden av Brutto/Netto (rangert synkende,
|
||
siden høyere poengsum er bedre) -- krevde `strokes_received` per hull +
|
||
`total_points` på hver leaderboard-rad (også backend, samme
|
||
utvidelsesmønster). Netto/poeng vises nå også som en rolig sekundærlinje
|
||
RETT under det fremhevede brutto-hullmerket (inspirert av et
|
||
referansebilde fra en konkurrentapp bruker delte, bevisst IKKE en kopi av
|
||
fargevalg/layout).
|
||
Full verifiserings- og utrullingsdetalj i CHANGELOG.md
|
||
(2026-07-26) -- ikke gjentatt her for å unngå duplisering.
|
||
|
||
### Oppdatering 2026-07-28: turnering-halvparten av spørsmålet AVKLART OG BYGGET
|
||
|
||
Det tidligere åpne spørsmålet ("mener brukeren en individuell rangering
|
||
INNAD i en turnering, ved siden av det eksisterende lag-poeng-
|
||
leaderboardet?") ble avklart eksplisitt (AskUserQuestion) — svar: JA, en
|
||
egen rangering PER ØKT (ikke summert på tvers av turneringen), ved siden
|
||
av lag-poeng-leaderboardet, ikke i stedet for det. Bygget: ny
|
||
`GET /orgs/{id}/sessions/{id}/individual-leaderboard`
|
||
(`app/routers/tournaments.py`) — KUN meningsfull for
|
||
`scoring_mode='stroke'`-økter i individuell-ball-format (singles/
|
||
fourball; delt-ball-formater og hole_result-modus avvises tydelig med
|
||
400, ikke krasj, siden de ikke har noen individuell brutto-score å
|
||
rangere fra). Gjenbruker `mp.playing_handicap` (allerede beregnet,
|
||
allowance-justert per format) + `allocate_over_played_holes` (samme
|
||
mønster som eksisterende match-play-scoring) for netto/poeng — ingen ny
|
||
regnelogikk. Respekterer samme reveal-gating (blind draw, ADR-013) som
|
||
matchlisten. Frontend: ny side
|
||
`/tournaments/[id]/sessions/[sessionId]/individual-leaderboard`
|
||
(`session-individual-leaderboard.tsx`, gjenbruker rangerings-/
|
||
mode-toggle-mønsteret fra `round-leaderboard.tsx` i en enklere,
|
||
sesjonsscopet variant), lenket fra blind draw-skjermen for kvalifiserende
|
||
økter. Scratch- og browserverifisert 2026-07-28, se CHANGELOG.md.
|
||
|
||
---
|
||
|
||
## Spillerliste-redesign (vertikal, utslag/HCP/rediger inline) — ✅ HELT FERDIG, håndkodet (V0 tom for credits), live 2026-07-26
|
||
|
||
Samme dag som HCP-i-søk/rediger-utslag-per-deltaker (se ADR-033-loggen i
|
||
CLAUDE.md), rett etter at den runden var rullet ut live. Brukeren viste et
|
||
skjermbilde av "+ Medspiller"-skjermen og pekte på to ting: (1) utslag+HCP
|
||
(nettopp bygget samme dag) bør flyttes OPP i selve spillerknappen i stedet
|
||
for å ligge som en egen rad under statistikknivå-velgeren, med Navn/
|
||
Utslag/Hcp/Rediger inni knappen, og spillerne listet VERTIKALT i stedet for
|
||
dagens horisontale scroll-rad. For en "midlertidig spiller" (gjest uten
|
||
konto) skal Rediger-flyten også dekke Navn/Kjønn/E-post. (2) Under selve
|
||
hull-registreringen bør det vises hvor mange mottatte slag aktiv spiller
|
||
har på det hullet, eksempel "Hull 7 - Par 4 - Hcp 5 - -1".
|
||
|
||
**Punkt 2 er ALLEREDE bygget og live** — ren frontend-tilføyelse i
|
||
`round-detail.tsx` sin hull-header, bruker data (`strokes_received`) som
|
||
allerede var hentet fra `GET .../holes` fra før (samme felt `ScoreSoFar`/
|
||
leaderboardet bruker til netto), bare aldri vist FØR scoring. Golfvis
|
||
fortegn (ekte minustegn), vist kun når spilleren faktisk mottar minst ett
|
||
slag på hullet (`Hull 7 · Par 4 · Hcp 5 · −1`).
|
||
|
||
**Punkt 1 krevde ny backend, bygget og scratch-verifisert (22/22 sjekker +
|
||
full regresjon av samme dags 27+35-punkts testsuiter) samme dag:** ny
|
||
migrasjon `029_round_participant_guest_email.sql` (`round_participant.
|
||
guest_email`, nullable). `ParticipantCreate` (`POST .../participants`)
|
||
fikk et valgfritt `guest_email: EmailStr | None` (avvist sammen med
|
||
`user_id` — en lenket bruker har allerede sin egen konto-e-post).
|
||
`ParticipantUpdate` (`PATCH .../participants/{id}`) fikk `guest_name`/
|
||
`gender`/`guest_email` — men KUN gyldig for en gjest (`user_id IS NULL`);
|
||
forsøk på en lenket deltaker avvises tydelig (400). `gender`-endring er nå
|
||
en del av samme "rating_changed"-bunt som utslag/HCP (påvirker hvilken
|
||
utslags-rating som er gyldig) — regnes om, avvist (400) hvis den nye
|
||
kombinasjonen (nytt/uendret utslag × nytt kjønn) mangler rating, og
|
||
bevisst avvist etter fullføring (409, samme presedens som utslag/HCP).
|
||
`guest_name`-endring alene har INGEN rating-implikasjon og forblir derfor
|
||
tillatt selv etter fullføring (ren metadata, samme som `RoundUpdate` sitt
|
||
navnefelt).
|
||
|
||
**V0-prompt sendt til bruker 2026-07-26, ikke kjørt i v0.app ennå:**
|
||
|
||
> Design om spillerlisten på en frittstående golfrundes hull-for-hull-
|
||
> registreringsside i TeeCup (Next.js + Tailwind + shadcn/ui, mobil-først,
|
||
> eksisterende merkevarefarger — grønn primær, oransje sekundær). I dag er
|
||
> spillerne en horisontal scroll-rad av små faner øverst på siden; dette
|
||
> skal bli en VERTIKAL liste av spillerkort i stedet. Runden kan ha alt fra
|
||
> kun eieren alene til mange spillere samtidig, så listen må skalere pent
|
||
> til 10+ rader uten å bli en endeløs, monoton vegg.
|
||
>
|
||
> **Hvert spillerkort viser:** navn, utslagssted, HCP (eller "ikke satt"
|
||
> hvis fraværende), og en tydelig "Rediger"-knapp/lenke. Kortet er OGSÅ
|
||
> selve trykkflaten for å VELGE spilleren som aktiv for hull-registrering
|
||
> (samme funksjon som dagens fane-klikk) — velg en tydelig, men ikke
|
||
> overveldende, måte å vise "dette kortet betyr både 'velg meg' og
|
||
> 'rediger meg'" på (f.eks. hele kortet velger, en distinkt undersone/
|
||
> knapp for rediger) uten at de to handlingene blandes sammen ved et
|
||
> uhell.
|
||
>
|
||
> **To distinkte merker på kortet**, kan begge gjelde samme kort: "Deg"
|
||
> (spilleren SOM SER PÅ siden akkurat nå — komponenten mottar egen
|
||
> deltaker-id som prop, siden både eieren og en lenket medspiller kan se
|
||
> og bruke siden) og "Eier" (rundens eier, fra et eget boolsk felt på
|
||
> spilleren).
|
||
>
|
||
> **Rediger-flyten** (åpnes fra kortet, f.eks. en utvidbar seksjon eller et
|
||
> ark/modal — din vurdering) inneholder:
|
||
> - Utslagssted: en nedtrekksliste hentet fra et eget endepunkt (banens
|
||
> faktiske utslag på DENNE runden), filtrert til utslag som har en
|
||
> rating for spillerens (gjeldende, eller nylig valgte — se under)
|
||
> kjønn.
|
||
> - HCP: et tallfelt, valgfritt (kan stå tomt/fjernes).
|
||
> - Statistikknivå for spilleren: tre valg ("Kun slag" / "Slag og putter" /
|
||
> "All statistikk") — samme tre-valgs-mønster som resten av appen bruker
|
||
> for dette (segmentert knapperad).
|
||
> - **KUN hvis spilleren er en "midlertidig spiller" (gjest uten TeeCup-
|
||
> konto — komponenten vet dette fra at spilleren mangler en konto-id)**:
|
||
> TRE EKSTRA felt — Navn (fritekst), Kjønn (mann/kvinne/annet), E-post
|
||
> (valgfritt, e-postformat). Endring av kjønn her skal oppdatere hvilke
|
||
> utslag som er valgbare i utslag-nedtrekkslisten (samme filter som
|
||
> over, reaktivt).
|
||
> - En tydelig "gjelder kun denne runden, endrer ikke [spillerens]
|
||
> profil"-forklaring for utslag/HCP-feltene (gjelder IKKE navn/kjønn/
|
||
> e-post for en gjest, siden en gjest ikke har noen egen profil å
|
||
> bevare uendret).
|
||
>
|
||
> **For en spiller MED egen TeeCup-konto** (ikke en gjest) skal Rediger-
|
||
> flyten KUN vise utslag/HCP/statistikknivå — ALDRI navn/kjønn/e-post-
|
||
> feltene (gir ingen mening, personen har sin egen konto).
|
||
>
|
||
> **"Legg til medspiller"-knappen/flyten** (søk-som-du-skriver mot ekte
|
||
> brukere, med et "legg til uten konto"-alternativ for gjester) beholdes
|
||
> konseptuelt som i dag, men tilpasses visuelt til den nye vertikale
|
||
> listen. Gjesteskjemaet i "legg til uten konto" bør få det samme
|
||
> valgfrie e-post-feltet som Rediger-flyten nå støtter (kan sette e-post
|
||
> allerede ved opprettelse, ikke bare i etterkant).
|
||
>
|
||
> **Skjul redigering/tilføyelse/fjerning helt** når komponenten får beskjed
|
||
> om at brukeren IKKE har lov til å forvalte runden (en prop, f.eks.
|
||
> `canManage`) — en medspiller uten forvaltningsrett skal fortsatt kunne
|
||
> VELGE et kort for å registrere score, bare ikke redigere/legge til/
|
||
> fjerne noen. Skjul ALL redigering (også for eieren) når runden er
|
||
> fullført (en `readOnly`-prop) — vis da utslag/HCP som ren tekst, ingen
|
||
> Rediger-knapp.
|
||
>
|
||
> Tilgjengelighet er et ufravikelig krav: god kontrast, stor nok skrift,
|
||
> store trykkflater (min. 44px), aldri kun farge for å formidle status
|
||
> (Deg/Eier-merkene trenger tekst, ikke bare farge), lesbar uten briller.
|
||
|
||
**Data komponenten skal jobbe mot (allerede live i produksjon):**
|
||
```
|
||
type Participant = {
|
||
id: string
|
||
user_id: string | null // null = gjest ("midlertidig spiller")
|
||
guest_name: string | null
|
||
guest_email: string | null // kun meningsfullt når user_id er null
|
||
display_name: string // alltid utfylt, bruk denne til visning
|
||
is_owner: boolean
|
||
gender: "m" | "f" | "x"
|
||
tee_name_snapshot: string
|
||
handicap_index_snapshot: number | null
|
||
course_handicap_snapshot: number | null
|
||
stat_level: "strokes_only" | "strokes_and_putts" | "full"
|
||
}
|
||
|
||
GET /rounds/{round_id}/tee-options
|
||
→ [{ name: string, genders: ("m"|"f")[] }]
|
||
|
||
PATCH /rounds/{round_id}/participants/{id}
|
||
body (alle valgfrie, kun de som faktisk endres sendes):
|
||
stat_level, tee_name, handicap_index (kan settes til null),
|
||
guest_name, gender, guest_email (kan settes til null)
|
||
-- guest_name/gender/guest_email avvises (400) hvis participant.user_id
|
||
er satt (ikke en gjest)
|
||
-- avvist (409) hvis runden er fullført OG tee_name/handicap_index/
|
||
gender er blant feltene som sendes
|
||
|
||
POST /rounds/{round_id}/participants
|
||
body: ENTEN { user_id, tee_name?, stat_level? }
|
||
ELLER { guest_name, gender, handicap_index?, guest_email?, tee_name?, stat_level? }
|
||
```
|
||
|
||
**Oppdatering 2026-07-26: bygget HÅNDKODET i stedet for via V0.** Brukeren
|
||
gikk tom for V0-credits rett etter at prompten over ble skrevet, og valgte
|
||
eksplisitt (spurt via AskUserQuestion) at jeg bygger det direkte fremfor å
|
||
vente på fornyede credits. Implementert etter nøyaktig samme prompt/
|
||
data-kontrakt som over, i samme Tailwind/shadcn-stil som resten av
|
||
`round-detail.tsx`: ny `PlayerList`-komponent (erstatter `PlayerTabs`) —
|
||
vertikal liste, hvert kort en stor `<button>` for "velg som aktiv spiller"
|
||
+ separate `Rediger`/fjern-knapper (bevisst IKKE nestede interaktive
|
||
elementer), "Deg"/"Eier" som `Badge`-komponenter (shadcn, gjenbrukt fra
|
||
`components/ui/badge.tsx` som fantes men ikke var i bruk i denne filen fra
|
||
før). `EditParticipantPanel` utvidet med statistikknivå (samme tre-valg
|
||
som den fjernede frittstående `StatLevelPicker`, nå død kode og fjernet)
|
||
og — kun for gjester (`player.userId === null`) — Navn/Kjønn/E-post,
|
||
kjønnsendring filtrerer utslagslisten reaktivt. "+ Medspiller"-flyten
|
||
(søk/gjesteskjema) flyttet fra en fast plassering utenfor spillerlisten
|
||
til å rendres INNI `PlayerList` selv, nederst i den vertikale listen.
|
||
Rediger-flyten ble en inline-utvidelse av kortet (ikke et eget ark/modal)
|
||
-- enklest å implementere korrekt uten et nytt UI-primitiv, og konsistent
|
||
med `EditRoundPanel`s eksisterende inline-mønster i samme fil.
|
||
**Ny `Player`-type-detalj funnet nødvendig under bygging:** `player.name`
|
||
er allerede viewer-relativt ("Deg" for egen rad, se `playerLabel()`) --
|
||
måtte legge til et eget `rawName` (urørt `display_name`) for å
|
||
forhåndsutfylle gjeste-navnefeltet korrekt, siden "Deg" åpenbart ikke skal
|
||
havne i et redigerbart tekstfelt.
|
||
**Verifisert:** ekte typesjekket produksjonsbuild (samme `Dockerfile`-steg
|
||
som deployes) kompilerte rent. Satte i tillegg opp en engangs `next dev`-
|
||
container mot en scratch-backend med en ekte runde (gjest med avvikende
|
||
utslag+kjønn+e-post, aktiv spiller med et registrert slag på et hull med
|
||
mottatte slag) og hentet siden sin server-rendrede HTML med en ekte
|
||
sesjonscookie -- bekreftet 200 (ikke en Next.js-feilside/digest), ruten
|
||
matcher riktig. **Viktig begrensning, ærlig flagget:** siden er en
|
||
klient-komponent (all spillerdata hentes via `fetch` i `useEffect` ETTER
|
||
hydrering) -- den server-rendrede HTML-en viser derfor kun last-skjelettet
|
||
(spinner), ikke selve spillerkortene/Rediger-panelet. Ingen ekte
|
||
nettleser-basert interaksjonstest (klikk "Rediger", bytt kjønn og se
|
||
utslagslisten filtrere reaktivt, lagre og se kortet oppdatere) er utført
|
||
-- intet nettleserverktøy tilgjengelig i denne økten. **Bruker bør selv
|
||
klikke gjennom flyten** (spesielt gjeste-kjønnsendringens reaktive
|
||
utslagsfilter og "velg vs. rediger"-trykkflatene) før full tillit.
|
||
|
||
**Oppfølging samme dag: tildelte slag + jevn korthøyde, HÅNDKODET OG LIVE.**
|
||
Brukeren rapporterte to ting rett etter forrige rullings: (1) spillerkortet
|
||
manglet "tildelte slag" (course handicap for runden, ikke selve HCP-
|
||
indeksen) når det spilles med HCP, (2) kortene burde ha lik høyde og ikke
|
||
være høyere enn nødvendig, slik at man havner under score-tastaturet uten
|
||
unødvendig scrolling. Begge rettet i `PlayerList`: `Player`-typen fikk
|
||
`courseHandicap: number | null` (fra `course_handicap_snapshot`, allerede
|
||
i API-et), lagt til i info-linjen som "Tildelte slag: N" -- KUN vist når
|
||
verdien faktisk finnes (samme betingelse som "spilles med HCP"). Kort-
|
||
knappen fikk en fast `min-h-[68px]` (var variabel `min-h-16` + fri
|
||
wrapping) med BEGGE tekstlinjer (navn+merker, utslag/HCP/tildelte slag)
|
||
trunkert til én linje hver (`truncate`, ikke wrap) -- alle kort blir dermed
|
||
like høye uansett antall merker eller lengde på navn/utslag, samtidig som
|
||
selve listen tar minst mulig vertikal plass.
|
||
|
||
**Oppfølging samme dag: rundeleaderboardet HÅNDKODET OG LIVE.** Brukeren
|
||
spurte eksplisitt om jeg, "med designerbrillene på", trodde jeg kunne få
|
||
det til å se like profesjonelt ut som V0 -- svarte ja (gjenbruk av
|
||
etablerte mønstre, ikke fri visuell utforskning) og bygget det. Ny
|
||
`components/round-leaderboard.tsx` + rute `/my-rounds/[id]/leaderboard`.
|
||
Gjenbrukte bevisst EKSISTERENDE etablerte mønstre fremfor å finne opp nye:
|
||
`signedToPar`-formateringen og "form + farge, aldri kun farge"-språket fra
|
||
`ScoreMark` i `round-scorecard.tsx` (sirkel = under par, firkant = over
|
||
par, her som et `ToParMark` for et rundetotal-tall i stedet for ett hull),
|
||
samme WS-sanntid-/viewerId-oppkoblingsmønster som `round-stats.tsx`/
|
||
`round-scorecard.tsx` (`refreshKey` bumpet av `/ws/rounds/{id}/live`),
|
||
`Badge`-komponenten fra spillerliste-redesignet over for "Deg"/"Eier".
|
||
Rangeringslogikk (delt plassering vist som "T-N", riktig "hopp over"
|
||
rangeringstall ved tie, ulik rangering brutto vs. netto, uspilte/uten
|
||
netto-tall sortert samlet nederst uten rangeringstall) og
|
||
`formatToPar`-formateringen VERIFISERT UAVHENGIG i et frittstående
|
||
Node-script (samme "test beregningen i Node"-mønster som tidligere brukt
|
||
for `round-stats.tsx` sin `computeStats()`) -- 19/19 sjekker, inkl. et
|
||
scenario med to spillere tidd for ledelsen på brutto men IKKE tidd på
|
||
netto, og et scenario med en spiller som ikke har startet ennå. Ny
|
||
`RoundLeaderboardMini`-variant (topp 3 + "og N til", hele kortet en lenke
|
||
til full side) lagt inn i `round-detail.tsx` rett under headeren -- viser
|
||
seg ikke i det hele tatt for en solo-runde (komponenten returnerer `null`
|
||
når runden har færre enn 2 deltakere).
|
||
**Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent (ny rute
|
||
`/my-rounds/[id]/leaderboard` listet). Satte opp en fersk scratch-runde med
|
||
tre deltakere via ekte API-kall (to tidd på brutto til par 0, ulik netto
|
||
pga. ulik HCP, én som ikke har spilt ennå) og bekreftet leaderboard-JSON-en
|
||
matchet forventningen presist. Samme engangs `next dev`-container-sjekk
|
||
som spillerliste-redesignet -- begge rutene (`/my-rounds/[id]` med den nye
|
||
mini-varianten, `/my-rounds/[id]/leaderboard`) ga 200, ingen Next.js-
|
||
feilside. **Samme ærlige begrensning som over:** ingen ekte nettleser-
|
||
interaksjonstest (brutto/netto-veksling, faktisk visuell høyde/form-språk)
|
||
er utført.
|
||
|
||
**Oppfølging samme dag: scoringsflyten redesignet til en skjermovertagende
|
||
veiviser (v1 avvist av bruker, v2 erstattet den helt) — ✅ LIVE
|
||
2026-07-26.** Brukeren delte en skjermopptaksvideo av en konkurrentapp
|
||
(Golf GameBook) sin scoreregistrering. Første forsøk (v1) la kun til
|
||
golf-term-taltastatur + en "Neste spiller"-knapp på den EKSISTERENDE lange
|
||
inline-siden — brukeren testet den live og avviste den eksplisitt ("ingen
|
||
forbedring i det hele tatt", "visuelt like overveldende og rotete"). v2
|
||
bygget samme dag: en ekte skjermovertagende `ScoringWizard` (tre steg
|
||
maks, drevet av `stat_level`), hovedsiden erstattet med én kompakt
|
||
spillerliste (navn, akkumulert til-par-så-langt, en stor rund knapp som
|
||
åpner veiviseren). Krevde også å laste ALLE deltakeres hull med det
|
||
samme (ikke lenger lat lasting), slik at akkumulert score kan vises for
|
||
alle samtidig. **Reell driftsfeil funnet OG rettet samme dag:** den gamle
|
||
"Så langt i runden"-boksen (`ScoreSoFar`) ble ved en feil IKKE flyttet i
|
||
v2-bygget — lå fortsatt øverst, uendret, og fikk brukeren til å
|
||
rapportere "ingen synlig endring i det hele tatt" (bekreftet presist ved
|
||
å hente den faktisk kjørende JS-bunten i produksjon og søke i den — ikke
|
||
en cache-feil, en ekte plasseringsfeil). Rettet ved å flytte boksen til
|
||
under scoringsseksjonen. Full beslutnings-/begrunnelsesdetalj i
|
||
ARCHITECTURE_DECISIONS.md (ADR-033-oppdatering 2026-07-26), full
|
||
verifiserings-/utrullingsdetalj i CHANGELOG.md — ikke
|
||
duplisert her.
|
||
|
||
**Oppfølging 2026-07-27 — `teecup-scorekort-og-entry-spec.md` (nytt,
|
||
brukeropplastet PRESKRIPTIVT motstykke til `DESIGN_SYSTEM.md`, basert på
|
||
en 5-app-sammenligning) evaluert, §3 "Compliance-pass" delvis BYGGET SAMME
|
||
DAG — ✅ LIVE:** "Deg Deg"-badge-duplikat fjernet (`playerLabel()` viser nå
|
||
alltid ekte navn), scoringslistens score-knapp gjort om til å følge
|
||
`§Golfscore-språket` (gjenbruker `ScoreMark`s fargespråk fra
|
||
`round-scorecard.tsx`), pluss et systematisk `tabular-nums`/44px-
|
||
trykkgulv-avvik-audit fikset (13 steder).
|
||
|
||
Spec-dokumentets §1 (scorekort som fullt grid) krevde et bevisst "JA" fra
|
||
brukeren først (egen informasjonsarkitektur enn ett-hull-om-gangen-listen)
|
||
— bekreftet SAMME dag, og BYGGET OG RULLET UT LIVE 2026-07-27 rett etter
|
||
compliance-passet: erstattet med en scrollbar `ScorecardGrid` (spillere
|
||
som rader, hull som kolonner, sticky navnekolonne + Ut/Inn/Sum), celler
|
||
følger `§Golfscore-språket` UBRYTELIG (gjenbrukt eksakt fra
|
||
`round-scorecard.tsx`). Tapp en celle/navnerad/hull-overskrift åpner
|
||
samme `ScoringWizard` som før, nå adressert direkte fra gridet.
|
||
|
||
**Reell alvorlig rendering-bug funnet OG fikset SAMME DAG, rett etter
|
||
utrullingen, via ekte nettleser-testing** (første gang en Chrome DevTools
|
||
MCP var tilgjengelig denne økten) — `position: sticky` på tabellceller
|
||
kombinert med sticky venstre+høyre kolonner samtidig rendret fullstendig
|
||
ødelagt/overlappende i Chrome. Fikset ved å droppe sticky-posisjonering
|
||
på `Ut`/`Inn`/`Sum`-kolonnene (scroller nå med resten av hullene i normal
|
||
flyt) og beholde kun den velprøvde sticky venstre navnekolonnen. Verifisert
|
||
med ekte skjermbilder + scroll-simulering mot ekte produksjonsdata.
|
||
|
||
**Samme dag, oppfølging: full nettleser-gjennomgang av alle 22 skjermer**
|
||
(brukeren spurte hvilke visninger som finnes, ba deretter om at alle
|
||
ikke-browsersjekkede ble sjekket). Fant OG fikset en ny, ekte "til par"-
|
||
bug i `round-scorecard.tsx` (regnet mot hele rundens par i stedet for
|
||
kun spilte hulls par — ga en absurd "−53 til par" midt i en runde).
|
||
Resten av de 22 skjermene bekreftet uten krasj/konsoll-feil. Full detalj
|
||
i CHANGELOG.md (2026-07-27) — ikke duplisert her.
|
||
|
||
---
|
||
|
||
## Symbolforklaringen på scorekortet fjernet — ✅ BYGGET OG LIVE 2026-07-30
|
||
|
||
Brukeren pekte direkte på "Under par (sirkel) / Over par (firkant) / Fylt
|
||
symbol = 2 slag eller mer"-forklaringen på `round-scorecard.tsx` og ba om
|
||
at den fjernes. `Legend`-komponenten (kun brukt ett sted) fjernet
|
||
fullstendig, ikke bare skjult. Den andre, urelaterte inline-teksten på
|
||
samme fil ("Fylt sirkel = vunnet hull ...", brukt av match-scorekortet)
|
||
er en annen streng, ikke rørt. Ekte typesjekket produksjonsbuild kompilerte
|
||
rent. Rullet ut (kun `teecup_frontend`), ingen migrasjon, `teeoff.no`
|
||
upåvirket.
|
||
|
||
## Notat 2026-07-30: per-hull historikk/statistikk for spilleren — 📋 NOTERT, IKKE bygget
|
||
|
||
Reist av brukeren: spilleren bør kunne se all historikk/statistikk for
|
||
NØYAKTIG det hullet vedkommende skal spille (eller har spilt) — f.eks. "du
|
||
har i snitt brukt X slag/Y putter på hull 7 på denne banen, GIR Z% av
|
||
gangene" — ikke bare hva som skjedde DENNE runden.
|
||
|
||
**Ikke trivielt, verdt å notere hvorfor:** dagens data er scoped PER
|
||
RUNDE (`round_hole`), ingen eksisterende spørring aggregerer "alle mine
|
||
tidligere runder på DENNE banen, filtrert til DETTE hullnummeret". Banen
|
||
identifiseres i dag kun ved navn-snapshot (`round.course_name_snapshot`,
|
||
se `PlayedCourses`/`course-rounds.tsx` sin eksisterende gruppering på
|
||
akkurat dette navnet) — samme identifikator kan trolig gjenbrukes til en
|
||
ny spørring: `GET /rounds/holes/{course_name}/{hole_number}/history` (eller
|
||
tilsvarende), som slår sammen `round_hole`-rader på tvers av alle
|
||
fullførte runder brukeren eier/har spilt på den banen. Naturlig plassering
|
||
i UI-et: en liten utvidbar seksjon i `ScoringWizard` sitt "strokes"-steg
|
||
(`round-detail.tsx`), og/eller i `round-scorecard.tsx`/`round-stats.tsx`
|
||
sine hull-visninger. Ingen design/ADR skrevet ennå — kun fanget opp her.
|
||
|
||
## Notat 2026-07-30: lenke til rundestatistikk fra scorekortet, og hvorvidt scorekort/statistikk/live-registrering bør slås sammen til én visning — lenke + prikker ✅ BYGGET, BROWSERVERIFISERT OG LIVE; sammenslåing bevisst UTSATT til brukertesting
|
||
|
||
Brukeren observerte at `round-scorecard.tsx` (det tradisjonelle,
|
||
front9/back9-delte scorekortet) mangler en lenke videre til
|
||
`/my-rounds/{id}/stats` (som selv HAR en lenke tilbake til scorekortet,
|
||
`CompletedBanner`) — og spurte om det heller burde vært ÉN samlet visning
|
||
i stedet for to, og i så fall om selve LIVE-registrerings-gridet
|
||
(`ScorecardGrid` i `round-detail.tsx`, bygget som spec-dokumentets §1
|
||
2026-07-27) burde erstattes av det samme front9/back9-delte formatet, med
|
||
mottatte slag vist som prikker i stedet for tekst.
|
||
|
||
**Svart direkte i chatten (ikke bygget), gjengitt her for historikken:**
|
||
anbefalte å IKKE slå sammen scorekort og statistikk til én visning — de
|
||
ble bevisst SKILT UT i to dedikerte sider 2026-07-25 (se eget punkt lenger
|
||
opp i denne filen: "Rundeliste + scorekort-redesign", "Den gamle vertikale
|
||
'Scorekort'-tabellen i round-stats.tsx FJERNET (erstattet med en lenke til
|
||
den nye siden)") — nøyaktig for å unngå at én side blir for lang/rotete.
|
||
Riktig, minimal fiks er heller den manglende lenken SCOREKORT → STATISTIKK
|
||
(symmetrisk med den allerede eksisterende STATISTIKK → SCOREKORT-lenken).
|
||
Anbefalte OGSÅ å IKKE erstatte selve live-registrerings-gridet med det
|
||
front9/back9-vertikalt-delte formatet — gridet ble nettopp bygget 2026-07-27
|
||
for å ligne et ekte scorekort (Ut/Inn/Sum, alle 18 hull som kolonner,
|
||
`§Golfscore-språket`), men er samtidig en AKTIV data-registreringsflate
|
||
(tapp en celle for å åpne veiviseren) — et vertikalt delt to-blokk-format
|
||
(designet for LESING) ville krevd smalere celler/mer scrolling og dermed
|
||
svekket selve registreringsergonomikken uten reell gevinst. "Prikker for
|
||
mottatte slag i stedet for tekst" ble vurdert som en god idé UAVHENGIG av
|
||
selve sammenslåings-spørsmålet — kan bygges som en liten, isolert
|
||
polish-oppgave på eksisterende `−N`-tekst-hint, i BEGGE grid-typer.
|
||
**De to avgrensede oppfølgerne bygget samme dag, sammenslåings-spørsmålet
|
||
bevisst utsatt** (brukeren: "Vi får senere gjøre en test med faktiske
|
||
brukere for å beslutte endelig om alt skal samles på en side"):
|
||
1. Ny "Se full rundestatistikk"-lenke nederst på `round-scorecard.tsx`
|
||
(symmetrisk med den eksisterende "Se scorekort"-lenken i motsatt
|
||
retning, `round-stats.tsx`).
|
||
2. `−N`-teksthintet for mottatte slag (FØR et hull er fylt ut) erstattet
|
||
med prikker (`StrokeDots`, ny liten komponent i `round-detail.tsx`) i
|
||
BÅDE `ScorecardGrid` (individuell-ball) og `SideScorecardGrid`
|
||
(delt-ball) — selve informasjonen bevart i knappens `aria-label`
|
||
("...mottar N slag"), siden prikkene alene er dekorative
|
||
(§tilgjengelighet: aldri kun et visuelt symbol for noe som betyr noe).
|
||
**Browserverifisert grundig i en isolert scratch-nettleserøkt** (samme
|
||
mønster som resten av uken -- fersk `teecup_scratch`-database + isolert
|
||
scratch-MinIO + engangs API-container + en isolert `next dev`-container
|
||
med kopiert kildekode): en HCP 28-spiller (course handicap 30, seedet via
|
||
API) bekreftet å vise nøyaktig to prikker på hull 1-12 (stroke index
|
||
1-12, riktig ut fra 30-18=12 ekstra slag) i live-registrerings-gridet;
|
||
den nye lenken på scorekortet bekreftet klikkbar og navigerte korrekt til
|
||
`/stats`-siden. Ekte typesjekket produksjonsbuild kompilerte rent. Ingen
|
||
konsollfeil (kun en godartet WebSocket-advarsel fra rask sidenavigasjon,
|
||
urelatert).
|
||
**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.
|
||
**Selve sammenslåings-spørsmålet (scorekort+statistikk+live-registrering
|
||
til én visning) er bevisst IKKE avgjort** — venter på en fremtidig
|
||
brukertest før noen endelig beslutning tas.
|
||
|
||
## En tredje (informasjons-)farge til designet — ✅ BYGGET OG LIVE 2026-07-28
|
||
|
||
Brukeren spurte om det ville vært en idé å introdusere én (eller kanskje
|
||
to) nye farger til TeeCups design — trolig utløst av Golf GameBook-
|
||
skjermbildene over, som bruker flere fargenyanser for score-mot-par-
|
||
indikasjon og en egen blå "HCP-RUNDE"-merkelapp.
|
||
|
||
**Sjekket faktisk palett i `globals.css` FØR noe ble foreslått:**
|
||
TeeCup har i dag `--primary` (grønn, hue ~130, ADR-016 — bevisst avledet
|
||
fra Teeoffs egen logo, se ADR-009/016 sin begrunnelse for hvorfor dette
|
||
IKKE er en tilfeldig fargevalg), `--brand-orange` (hue ~36.5, samme
|
||
opprinnelse), og `--destructive` (rød, hue ~27, reservert for slette-/
|
||
feil-handlinger). I TILLEGG finnes allerede en `--chart-1…6`-
|
||
datavisualiseringsskala (lagt til under rundestatistikk-arbeidet) —
|
||
`--chart-3` er ALLEREDE en blåtone (hue 210), med egne, ferdig avstemte
|
||
verdier for BÅDE lyst og mørkt tema.
|
||
|
||
**Anbefaling, forankret i dataviz-prinsippet om at statusfarger skal
|
||
være RESERVERTE (god/advarsel/alvorlig/kritisk) og aldri gjenbrukt som
|
||
"serie 4":** i stedet for å finne på en helt ny fargetone, LØFT den
|
||
allerede eksisterende `--chart-3`-blåtonen til en egen, navngitt
|
||
kjerne-designtoken (f.eks. `--info`/`--info-foreground`, samme mønster
|
||
som `--brand-orange`/`--destructive` allerede er egne tokens utover
|
||
selve chart-skalaen) — gjenbruker allerede validerte OKLCH-verdier for
|
||
begge temaer, i stedet for å øke det totale fargeantallet i appen. Denne
|
||
"informasjons"-fargen kunne dekke akkurat den typen behov Golf GameBook
|
||
løser med blått: en nøytral status mellom "bra" (grønt) og "trenger
|
||
oppmerksomhet" (oransje) — f.eks. en "teller for HCP"-merkelapp, eller
|
||
en mellomste score-til-par-kategori (par ↔ bogey ↔ dobbel bogey, hvis
|
||
TeeCup noen gang vil fargekode scorekortceller mer finmasket enn i dag).
|
||
|
||
**Anbefaler IKKE en fjerde/femte helt ny nyanse i tillegg** — grønn
|
||
(positiv/merkevare), oransje (merkevare/oppmerksomhet), en løftet blå
|
||
(nøytral/informasjon), og rød (destruktiv, reservert) dekker allerede de
|
||
fire klassiske statuskategoriene godt (god/informasjon/advarsel/
|
||
kritisk-destruktiv). Flere farger enn det risikerer å utvanne betydningen
|
||
uten en konkret, begrunnet bruk å vise til ennå.
|
||
|
||
**Oppdatering 2026-07-28:** brukeren ba eksplisitt om TO nye farger (ikke
|
||
bare den anbefalte ene). Løftet BEGGE `--chart-3` (blå → `--info`) OG
|
||
`--chart-4` (gul/gull → `--gold`, ikke drøftet over, men samme
|
||
gjenbruk-fremfor-ny-nyanse-logikk) til egne kjernetoken i `globals.css`.
|
||
Konkret førstebruk: `--info` på et nytt "Hcp spilt til X"-merke på
|
||
rundekort, `--gold` på et nytt "Personlig rekord"-merke (laveste til-par
|
||
blant minst to fullførte runder). Full detalj i CHANGELOG.md
|
||
(2026-07-28) — ikke duplisert her.
|
||
|
||
---
|
||
|
||
## Scramble: statistikk over utslag brukt per spiller — ✅ HELT FERDIG (frittstående runder 2026-07-28, org-scopede turneringer 2026-07-29)
|
||
|
||
Brukeren ba om at det i scramble-turneringer skal føres statistikk over
|
||
hvor mange utslag hver spiller har hatt (dvs. hvor mange ganger den
|
||
enkelte spillerens drive ble VALGT av laget som ballen man fortsetter
|
||
med).
|
||
|
||
**Reelt ny type data, ikke en liten utvidelse:** dagens
|
||
`hole_score`/`match_hole_result` er en DELT rad per side for scramble
|
||
(se det allerede eksisterende åpne punktet "Individuell-vs-delt-ball i
|
||
`hole_score`" i ARCHITECTURE_DECISIONS.md sin "Åpne spørsmål"-seksjon,
|
||
og "Scramble-grensesnitt" rett over det samme stedet) — det finnes i dag
|
||
INGEN kobling mellom en registrert hull-score og HVILKEN spiller sitt
|
||
utslag som faktisk ble valgt. Å telle "utslag brukt" krever et nytt,
|
||
eksplisitt datapunkt per hull (f.eks. hvem sitt utslag ble valgt), ikke
|
||
noe som kan utledes fra det som allerede lagres.
|
||
|
||
**Sannsynligvis samme problemstilling for greensome** (ikke eksplisitt
|
||
nevnt av bruker, men samme spilleregel-mekanikk: begge partnere slår
|
||
egen ball fra tee, ett velges) — verdt å vurdere sammen når dette
|
||
designes, ikke som to separate ting.
|
||
|
||
**Åpne spørsmål, avklart pragmatisk ved bygging (2026-07-28):** registreres
|
||
av den som fører score for siden (samme autorisasjon som selve hull-
|
||
scoringen, ingen ny sjekk), samtidig med slagtallet men som en HELT
|
||
UAVHENGIG, valgfri handling (kan settes/endres uten å røre selve
|
||
slagtallet). Vist per side (match) OG aggregert for hele runden
|
||
(`round-stats.tsx`). Uregistrerte hull telles rett og slett ikke med --
|
||
"registrert på N av M spilte hull" vises alltid ved siden av.
|
||
|
||
**Status: ✅ BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28 --
|
||
avgrenset til frittstående runder (ADR-039 sitt delt-ball-format), IKKE
|
||
org-scopede turneringer i denne runden** (samme underliggende problem,
|
||
men bevisst utsatt der -- se "bevisst utenfor omfang" under).
|
||
Ny migrasjon `036_round_hole_selected_participant.sql` --
|
||
`round_hole.selected_participant_id` (nullable, FK til
|
||
`round_participant`, `ON DELETE SET NULL`), kun meningsfull på en
|
||
delt-ball-hull-rad (`round_side_id IS NOT NULL`), håndhevet i app-laget.
|
||
`PATCH /rounds/{id}/sides/{side_id}/holes/{n}` (`SideHoleUpdate`) fikk et
|
||
nytt valgfritt felt, validert til å tilhøre NØYAKTIG denne siden (400
|
||
ellers). Samme "full overwrite hvert kall"-kontrakt som `played`/`score`
|
||
alltid har hatt -- frontend sender alltid gjeldende verdi for begge felt
|
||
sammen (`patchSideHole()` i `round-detail.tsx`), ellers ville en
|
||
score-oppdatering stille nullstilt et allerede registrert valg.
|
||
**Frontend:** `SideScoreWizard` fikk en ny "Hvem sitt utslag ble brukt?"-
|
||
seksjon (knapperad, ett trykk per spiller på siden, valgfritt) --
|
||
auto-hopp-mekanismen (2026-07-26-runden) ble bevisst SLÅTT AV for
|
||
delt-ball-formater med spillere å velge mellom (ville revet brukeren
|
||
videre før valget kunne gjøres), beholdt uendret for slagspill/skins/
|
||
individuell-ball-formater. Ny "Utslag brukt"-seksjon i `round-stats.tsx`
|
||
(`SelectedDriverSummary`) -- ren klientside-telling fra allerede hentet
|
||
`round_hole`-data, per side, med "registrert på N av M spilte hull".
|
||
**Scratch-verifisert** (del av samme 27-punkts testløp som de tre andre
|
||
punktene denne dagen): PATCH med valgt spiller lykkes, feil side avvist
|
||
(400), eksplisitt null nullstiller valget. **Browserverifisert:** valgte
|
||
"Gjest Partner" i veiviseren, bekreftet lagret DIREKTE i databasen
|
||
(`selected_participant_id` pekte riktig), OG bekreftet "Utslag brukt"-
|
||
oppsummeringen viste "Gjest Partner: 1 utslag" korrekt på statistikksiden,
|
||
ingen konsollfeil.
|
||
**Org-scopede turneringer — ✅ BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE
|
||
2026-07-29,** rask, avgrenset oppfølger rett etter Stableford-runden
|
||
samme uke: `hole_score.selected_participant_id` (migrasjon 039), samme
|
||
composite-FK-mønster som `match_participant_id` sin egen FK men `ON
|
||
DELETE SET NULL` (fjernes en deltaker skal ikke slette allerede
|
||
registrerte scorer). Validering i `submit_hole_score` (app/routers/
|
||
scoring.py): avvist for individuell ball, validert til samme `team_side`
|
||
som scoren for delt ball. Ny "Hvem sitt utslag ble brukt?"-knapperad i
|
||
`session-scorecard.tsx` (vises kun når et slagtall allerede er
|
||
registrert, siden `hole_score.gross_strokes` er `NOT NULL` -- en reell
|
||
strukturell forskjell fra `round_hole`, som alltid forhåndsoppretter en
|
||
nullbar rad). Ny `SelectedDriverSummary` i "Vis full oversikt"-
|
||
seksjonen. 51/51 scratch-sjekker (full org-turnering-scaffold bygget fra
|
||
bunnen) + full nettleser-gjennomgang (ekte klikk, persistens bekreftet
|
||
direkte i databasen, riktig opptelling). Se CHANGELOG.md 2026-07-29
|
||
for full detalj. **Begge domener (frittstående runder og org-scopede
|
||
turneringer) dekker nå samme funksjon** -- ingen kjent gjenstående
|
||
forskjell mellom scramble og greensome i noen av domenene.
|
||
|
||
---
|
||
|
||
## Kommunikasjon — ✅ HELT FERDIG 2026-07-19 (ADR-025)
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Lag-intern chat («det hemmelige rommet») | ✅ LIVE | `/tournaments/[id]/teams/[teamId]/chat`. Ekte privat — kun rostrede spillere, INGEN unntak for org-eier/admin (bevisst avvik fra appens vanlige autorisasjonsmønster). `user_is_rostered_on_team` i `app/team_authz.py`. |
|
||
| Offentlig runde-feed («Banter Board») | ✅ LIVE | Egen seksjon nederst på `/t/[id]`. Lesing gjenbruker `tournament.visibility`-mønsteret (ADR-018) uendret, inkl. anonym tilgang for `public`-synlige turneringer. Posting er strengere: krever innlogging OG org-medlemskap/deltakelse. |
|
||
| Bilder i feed/chat | ✅ LIVE | Med fra start (ikke utsatt) — gjenbruker `app/storage.py` sin MinIO+AVIF-pipeline fra ADR-018 uendret. |
|
||
| Sanntid-levering | ✅ LIVE | WebSockets, ikke polling — løser samtidig det tidligere åpne "sanntid vs. polling"-spørsmålet generelt. In-memory tilkoblingsregister per prosess (kun trygt med dagens ene `teecup_api`-container, se ARCHITECTURE_DECISIONS.md "Åpne spørsmål"). Egen Caddy-rute `/ws/*` rett til `teecup_api`. |
|
||
| Video | 💤 | Fortsatt utsatt, egen ADR om/når etterspurt. |
|
||
| 1-til-1 direktemeldinger | 💤 | Fortsatt utsatt, ikke etterspurt. |
|
||
| Moderering (offentlig feed) | ✅ LIVE | Forfatteren selv, ELLER org-eier/admin, kan slette et innlegg. Lag-chatten har ingen moderering utover forfatteren (rommet er privat, org-admin har uansett ikke lesetilgang). |
|
||
| Bilder + kommentarer i frittstående (personlige) runder + samlet `/my-feed` | ✅ LIVE 2026-08-06 | ADR-044, migrasjon 058. Skriverett = kan-se-kan-poste (ikke kun eier/medspillere), se ADR-044 for full begrunnelse. |
|
||
|
||
Se ARCHITECTURE_DECISIONS.md ADR-025 for alle fire hovedbeslutningene og
|
||
CHANGELOG.md for full byggerunde (datamodell, autorisasjon, scratch-
|
||
verifisering med 20 automatiserte sjekker inkl. reell WebSocket-sanntid,
|
||
og utrulling).
|
||
|
||
### Bilder + kommentarer i frittstående runder + samlet feed — ✅ HELT FERDIG, BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-06 (ADR-044, migrasjon 058)
|
||
|
||
Brukeren: "I org-modulen har vi muligheter til bilder og kommentarer. Jeg
|
||
ønsker at dette også skal være mulig i single runder... Det må kanskje
|
||
også være en sentral feed-side?" — samme idé som `message`-tabellens
|
||
`tournament_feed`-scope (bilde + tekst), nå bygget for en vanlig
|
||
frittstående/personlig runde (`round`-tabellen, ADR-033), PLUSS en ny
|
||
`/my-feed`-side som aggregerer dette på tvers av runder.
|
||
|
||
**Ny, egen `round_message`-tabell** (ikke en utvidelse av `message`) --
|
||
`round` har verken `organization_id` eller RLS (ADR-033 Beslutning A), så
|
||
den delte org-tabellen passet ikke uendret. Se ADR-044 for full
|
||
begrunnelse og alle seks beslutningene (skriverett, moderering, datamodell,
|
||
sanntid, feed-plassering, rutenavngiving).
|
||
|
||
**Rettelse av en tidligere antagelse i denne seksjonen:** utkastet notert
|
||
2026-08-06 tidligere samme dag foreslo "runde-eier + faktiske
|
||
medspillere" som autorisasjonskrets — brukeren avklarte eksplisitt (via
|
||
AskUserQuestion) en BREDERE modell: alle som kan SE runden (inkl.
|
||
`public`/riktig-kategorisert `friends`-synlighet, ADR-036 fase 2, som
|
||
viste seg å allerede være bygget og live siden 2026-07-28) kan også
|
||
POSTE, samme "kan se = kan bidra"-modell som org-feeden.
|
||
|
||
**Verifisert:** 30/30 håndregnede API-sjekker (hele autorisasjonsmatrisen),
|
||
ekte bildeopplasting→AVIF bekreftet, ekte nettleser (kommentar+bilde
|
||
postet/vist/slettet, `/my-feed` viste riktig aggregert sett, WS-refetch
|
||
bekreftet fungerende GJENNOM en scratch-Caddy satt opp spesifikt for å
|
||
teste dette). Én kritisk rute-/rewrite-kollisjon (`/feed` vs. API-ets
|
||
`GET /feed`) funnet og rettet under verifisering — se ADR-044 for detalj.
|
||
`test_isolation.sql` uendret 12/12.
|
||
|
||
**Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt: migrasjon 058
|
||
kjørt mot ekte `teecup_db`, begge containere redeployet. Se CHANGELOG.md
|
||
2026-08-06 for full byggelogg.
|
||
|
||
### @-tagging av medspillere i feed-innlegg/kommentarer — 💤 utsatt (bevisst), reist 2026-08-11
|
||
|
||
Bruker: "Det bør være mulig å tagge medspillere i feeden. Eks: Fredrik i
|
||
farta." — skrive `@Navn`, systemet foreslår faktiske medspillere/venner
|
||
med det navnet, tagger riktig person (eller lar deg velge ved flere
|
||
treff).
|
||
|
||
Vurdert samme dag (ADR-062), IKKE bygget — egen, betydelig funksjon
|
||
(datamodell for lagrede tags + søk/autocomplete-UI + rendering av
|
||
klikkbare tags i visningen), bakt inn i en runde som ellers dreide seg
|
||
om noe helt annet (ny-runde/score-registrering-UX). To ting bør avklares
|
||
FØR bygging:
|
||
- **Personvern:** treff bør begrenses til faktiske relasjoner (spillere i
|
||
samme runde, eller venner) — IKKE fritekstsøk i hele brukerbasen.
|
||
- **UX:** en ekte søkbar nedtrekksliste som åpnes ved `@`, ikke
|
||
fritekst-tolkning i etterkant av en allerede skrevet kommentar (upålitelig
|
||
med flere personer som deler navn).
|
||
|
||
---
|
||
|
||
## Landingssider (turnering + organisasjon) — ADR-018 ✅ HELT FERDIG 2026-07-18
|
||
|
||
Reist av brukeren 2026-07-18, rett etter registrerings-ADR-en (ADR-017).
|
||
Backend, begge frontend-skjermer, Open Graph-metadata OG MinIO-bilde-
|
||
opplasting (med AVIF-konvertering) bygget og live samme dag. Kun selve
|
||
dra-og-slipp-opplastingsskjermen i frontend (V0) gjenstår.
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Synlighetsnivå: offentlig / kun org-medlemmer / kun turnering-deltakere | ✅ | `tournament.visibility` (default `org`, trygg standard) + `organization.public_profile`. Håndheves eksplisitt i `registration.py` — RLS løser IKKE dette alene (se ADR-018 Beslutning B, reell presisering funnet under bygging). Samme trenivå-modell som «Banter Board» under bør gjenbruke dette. |
|
||
| Registrering følger samme synlighetsgrense | ✅ | ADR-018 Beslutning D, bekreftet med bruker FØR bygging. |
|
||
| "Deltaker"-tilgang (ikke org-medlem, men rostret/registrert) | ✅ | Ny `get_current_user_optional` + `_is_participant()`. Testet: rostret spiller som logget inn fikk tilgang, tilfeldig fremmed ble avvist. |
|
||
| Turnering-landingsside: tekst, program, sponsorer, påmelding (API) | ✅ | `description`-felt, `tournament_sponsor`-tabell (navn+lenke), `GET /public/tournaments/{id}/sessions` (gjenbruker blind draw-lås fra ADR-013). Selve SKJERMEN i frontend gjenstår. |
|
||
| Org-landingsside: klubbprofil, liste over turneringer (API) | ✅ | `GET /public/orgs/{slug}` — kun `public`-synlige turneringer. Bekreftet: klubb-profil KAN være offentlig (brukerens valg). |
|
||
| Lesbar URL (slug) for organisasjon | ✅ | Fantes faktisk allerede i skjemaet siden migrasjon 001 (oversett, funnet da migrasjon 009 feilet mot scratch — se CHANGELOG.md). Kun `CHECK`-constraints lagt til i 009. |
|
||
| Organisator kan faktisk SETTE disse feltene | ✅ | Implisitt hull fylt under bygging: `PATCH /orgs/{id}/tournaments/{id}` (visibility/description/registrering), `PATCH /orgs/{id}` (slug/public_profile), full sponsor-CRUD. |
|
||
| Bilder (hero, sponsorlogoer), backend | ✅ | Ny `teecup-minio`-tjeneste, ekte multipart-opplasting → AVIF-konvertering (Pillow) → lagring, live på `teecup.teeoff.no/teecup-media/*`. Selve opplastingsskjermen i frontend (V0) gjenstår. |
|
||
| Del-metadata (Open Graph: og:title/og:description) | ✅ | `generateMetadata()` på `/t/[id]`+`/clubs/[slug]`, ekte data fra API-et, verifisert mot produksjonsimaget. `og:image` gjenstår (MinIO). |
|
||
| Blind draw-skjuling på offentlig side | ✅ | Arves automatisk via delt `_fetch_sessions()`-hjelpefunksjon (ADR-018 Beslutning E) — ikke reimplementert. |
|
||
| Antall påmeldte / ledige plasser vist åpent | ✅ | `confirmed_count` i `GET /public/tournaments/{id}`. |
|
||
| Frontend: turnering-landingsside + påmeldingsskjema, LIVE | ✅ | `/t/[id]`. Tre bekreftelsestilstander (bekreftet/venteliste/godkjenning venter). Verifisert med ekte `POST`-registrering mot scratch. |
|
||
| Frontend: org-/klubb-landingsside, LIVE | ✅ | `/clubs/[slug]`. Gjenbruker `TournamentCard` (nå med valgfri `orgId`) for turneringslisten. Verifisert: viser kun `public`-synlige turneringer. |
|
||
|
||
---
|
||
|
||
### Program-skjerm (økter/tidsplan) — ✅ BYGGET OG SCRATCH-VERIFISERT 2026-07-18
|
||
|
||
Sjette V0-skjerm, første i "bygg i rekkefølgen ting brukes"-serien (etter
|
||
lag/roster kommer program, så blind draw, scorekort, leaderboard).
|
||
`components/tournament-program.tsx`, ny rute `/tournaments/[id]/program`.
|
||
Tidslinje over turneringens økter i rekkefølge + opprett-skjema (format,
|
||
hullomfang, scoringsmodus, poeng, klokkeslett+intervall, starthull, en
|
||
kollapsbar "avansert"-seksjon med ADR-014s fire handicap-brytere).
|
||
|
||
**Reelt blokkerende hull funnet FØR integrering, ikke etter:**
|
||
`SessionCreate.course_id` er påkrevd, men det fantes INGEN vei til å skaffe
|
||
en gyldig én — ingen `course`-endepunkt i API-et i det hele tatt, og ADR-004s
|
||
teeoff-integrasjon er fortsatt bare vedtatt (ingen utgående HTTP-klient
|
||
finnes noe sted i koden). Spurte bruker eksplisitt (samme mønster som andre
|
||
scope-avklaringer) — svar: bygg enkel course-CRUD nå. Lagt til
|
||
`app/routers/courses.py` (`GET`/`POST /orgs/{id}/courses`, kun
|
||
`source='custom'`, ingen hull-/tee-/rating-detaljer denne runden — motoren
|
||
bruker foreløpig kun `course_id` som fremmednøkkel). Program-skjemaet fikk et
|
||
type-ahead-felt for bane (samme mønster som spiller-type-ahead i
|
||
roster-skjermen: søk blant org-ens eksisterende baner, eller opprett ny
|
||
inline).
|
||
|
||
**Reell korrekthetsfeil funnet og rettet FØR den nådde V0-designet i det hele
|
||
tatt hadde blitt integrert:** 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 (antall
|
||
spillere per side er del av selve formatet) — ren `"scramble"` avvises med
|
||
400. Rettet i frontend-mappingen til to egne segment-knapper.
|
||
|
||
**`allowance_override`-mapping verifisert eksakt mot motor-kontrakten:**
|
||
`app/handicap.py` sin `parse_allowance_config`/`_strategy_from_json` forventer
|
||
`{type: "combined"|"per_player", percentage: 0..1}` — IKKE en flat prosent.
|
||
Frontend velger riktig `type` ut fra om formatet er side-enhet (foursome/
|
||
greensome/scramble_2/scramble_4 → `combined`) eller spiller-enhet (fourball/
|
||
singles → `per_player`), og konverterer skjemaets 0–100-prosentfelt til
|
||
0–1-brøk før sending. Full JSON-rundtur bekreftet i scratch (se under) —
|
||
ikke bare antatt riktig fra å lese motorkoden.
|
||
|
||
**Verifisert grundig mot fersk scratch-infrastruktur** (ny `teecup_scratch`-
|
||
database 001→009 + en ISOLERT `teecup_app_scratch`-rolle som arver
|
||
`teecup_app` sine grants via `GRANT teecup_app TO teecup_app_scratch` —
|
||
bevisst IKKE den ekte `teecup_app`-rollen, siden den nå er
|
||
produksjonskritisk og rollen er cluster-global på tvers av `teecup_db`/
|
||
`teecup_scratch`; en tidligere plandokument sin "drop teecup_app-rolle"-
|
||
opprydning er utdatert etter go-live og ble bevisst IKKE fulgt + isolert
|
||
scratch-MinIO-container): courses opprettet+listet, kryss-org-isolasjon
|
||
bekreftet (org 2 ser ikke org 1 sin bane), økt opprettet med klokkeslett,
|
||
økt opprettet med `scramble_4` + full `allowance_override`-rundtur, gammel
|
||
`"scramble"`-verdi korrekt avvist (400), `test_isolation.sql` fortsatt
|
||
12/12. Ekte typesjekket PRODUKSJONSBUILD (samme `Dockerfile`/multi-stage som
|
||
faktisk deployes, ikke bare en dev-server) kjørt og bekreftet — ny
|
||
`/tournaments/[id]/program`-rute listet korrekt i build-output.
|
||
|
||
**Diffet V0-eksporten mot live-treet før noe ble tatt inn** (samme mønster
|
||
som alle tidligere runder): kun tre reelt nye filer
|
||
(`tournament-program.tsx`, `program/page.tsx`, `ui/switch.tsx`) — resten var
|
||
forventede full-reverts av allerede tilpassede filer, ikke rørt.
|
||
|
||
**Mindre justeringer utover selve V0-promptet:** fjernet V0s dev-only
|
||
"forhåndsvis tom/med økter"-knapperad (ikke noe en ekte organisator skal se);
|
||
lagt til en fanerad ("Lag og spillere" / "Program") i BÅDE
|
||
`tournament-detail.tsx` og den nye skjermen, siden V0 ikke visste om den
|
||
andre skjermen når den ble generert i en egen prompt.
|
||
|
||
**Rullet ut live 2026-07-18**, bruker bekreftet eksplisitt: begge containere
|
||
(`teecup_api`, `teecup_frontend`) bygget og redeployet, live sjekker OK
|
||
(`/health`, `/dashboard` → 200), `teeoff.no` upåvirket.
|
||
|
||
**Nytt 2026-07-18, ✅ HELT FERDIG (backend + frontend live):** `PATCH`/
|
||
`DELETE` for økter (`app/routers/tournaments.py`) — kunne tidligere verken
|
||
rettes eller slettes etter opprettelse. `DELETE` kun for tomme økter (409
|
||
hvis den har matcher). `PATCH` dekker enkle felt fritt, pluss en egen,
|
||
forsiktig gren for bane-bytte (finner/flytter tilsvarende tee per allerede
|
||
tillagt deltaker, regner om handicap+matchstatus for hele økten etterpå —
|
||
også for allerede AVGJORTE matcher, bekreftet eksplisitt av bruker). Se
|
||
CHANGELOG.md for det fulle scenarioet (verifisert med et 10-hulls
|
||
avgjort-match-eksempel) og et urelatert funn (`front_9`/`back_9` +
|
||
`stroke`-modus kan aldri få handicap i dag, siden `tee_rating` alltid kun
|
||
lages med `full_18`-omfang).
|
||
**Rediger-/slett-UI LIVE 2026-07-19** — utvidelse av den eksisterende
|
||
Program-skjermen (ingen ny rute). Fanget en reell regresjon i selve
|
||
V0-eksporten før den ble tatt inn: samme runde hadde utilsiktet fjernet
|
||
bane-feltet fra "Legg til økt"-skjemaet — kun de nye rediger/slett-delene
|
||
ble hentet inn, det ekte opprett-skjemaet urørt. Se CHANGELOG.md for
|
||
detaljer.
|
||
|
||
---
|
||
|
||
### Offisiell banedata fra teeoff — ✅ BYGGET OG LIVE 2026-07-18 (ADR-019)
|
||
|
||
Bevisst sidesprang fra "bygg i rekkefølgen ting brukes" rett etter
|
||
program-skjermen: brukeren påpekte at ADR-004s teeoff-integrasjon fortsatt
|
||
bare var vedtatt, ikke bygget. Full design i ARCHITECTURE_DECISIONS.md
|
||
ADR-019 (fem delbeslutninger). Kort: organisator søker blant teeoff sine
|
||
baner i program-skjemaet, velger én, og teecup KOPIERER bane+hull+tee+
|
||
tee_rating inn i `teecup_db` (`source='official'`) — ikke et live oppslag
|
||
ved hver bruk. Ny `app/teeoff_client.py` (ren HTTP-klient mot
|
||
`http://teeoff_api:8000`, internt Docker-nettverk, ingen auth trengs — begge
|
||
containere deler allerede `teeoff_default`). To nye endepunkter i
|
||
`app/routers/courses.py`: `GET .../courses/official-search[/{slug}]` og
|
||
`POST .../courses/official-import`. Migrasjon `010` (unik
|
||
`external_course_ref` per org, hindrer dupliserte importer).
|
||
|
||
**Bevisste avgrensninger for denne runden:** kun 18-hulls baner kan
|
||
importeres (teeoffs skjema har ingen egen 9-hulls-inndeling); kun
|
||
`full_18`-rating importeres (teeoff har ingen separat front9/back9-rating,
|
||
samme valg som ADR-008 allerede tok for egendefinerte baner); ufullstendige
|
||
teeoff-data (manglende par/hcp_index på et hull, eller en tee uten NOEN
|
||
rating) avviser hele importen tydelig (`EXTERNAL_DATA_INCOMPLETE`) FØR noe
|
||
skrives, ikke en delvis importert bane.
|
||
|
||
**Verifisert grundig, inkludert mot EKTE `teeoff_api`** (ikke en simulert
|
||
respons): søk, anlegg-/banevalg, og import kjørt reelt mot den kjørende
|
||
produksjonscontaineren (kun lesing) — importerte Borregaard Golfklubb sin
|
||
hovedbane, bekreftet alle 18 hull + 8 tee/tee_rating-rader riktig i
|
||
databasen, og opprettet en ekte økt med den importerte banen (beviser hele
|
||
veien til handicap-motoren, ikke bare selve importen). Reimport avvist
|
||
(409), kryss-org-isolasjon bekreftet, ukjent teeoff-slug ga 404,
|
||
`test_isolation.sql` 12/12, ekte typesjekket produksjonsbuild av
|
||
frontend-utvidelsen.
|
||
|
||
**Rullet ut live**, bruker bekreftet eksplisitt: migrasjon 010 mot ekte
|
||
`teecup_db`, begge containere redeployet, `teeoff.no` upåvirket.
|
||
|
||
**Reell bug funnet og fikset samme dag, av en bruker som faktisk testet
|
||
funksjonen:** bane-søkeboksen ("Hent bane fra teeoff") var et `<form>`
|
||
rendret INNI det ytre økt-opprett-skjemaet — ugyldig, nestet HTML. Å klikke
|
||
"Søk" submittet i praksis det ytre skjemaet som en ekte side-navigasjon og
|
||
vasket bort `?org=...`-parameteren fra URL-en. Fikset ved å fjerne det
|
||
indre `<form>`-elementet (vanlig `<div>` + Enter-tast/knapp-klikk i
|
||
stedet). Se CHANGELOG.md for full root cause.
|
||
|
||
**Nok en reell bug funnet og fikset samme uke, rapportert fra ekte bruk mot
|
||
`teecup.teeoff.no`:** import av samme teeoff-bane til flere økter (helt
|
||
normalt — flere runder spilles ofte på samme bane) ga en 409-feil i stedet
|
||
for å bare gjenbruke banen, og det lagrede navnet ("Hovedbanen" alene) ga
|
||
ingen måte å se hvilken klubb det gjaldt. Fikset: `POST .../courses/
|
||
official-import` er nå idempotent (gir tilbake eksisterende rad ved
|
||
reimport, sjekket FØR teeoff-kallet), og navnet lagres nå som
|
||
"{anlegg} – {bane}". Ekte produksjonsdata for "De Gamle er Eldst" ryddet
|
||
opp (en økt hadde ved et uhell fått en tom søppel-`custom`-bane — se
|
||
CHANGELOG.md for full hendelse og rotårsak).
|
||
**Åpent, ikke løst i denne runden:** de to bane-søkeflatene (øverste felt
|
||
= organisasjonens egne baner + "opprett ny"-snarvei, "Hent bane fra
|
||
teeoff"-knappen lenger ned = offisielt søk) er lette å forveksle — det var
|
||
nettopp dette som forårsaket søppel-banen. Vurder en tydeligere UI-
|
||
sammenslåing eller rekkefølge-endring i en senere runde.
|
||
|
||
---
|
||
|
||
### Internasjonale baner (utenfor Norge) — ✅ BYGGET OG LIVE 2026-08-12, se ADR-064/ADR-065 (research 2026-08-03)
|
||
|
||
**RETTELSE 2026-08-14 — denne seksjonen var stale, aldri oppdatert etter
|
||
bygging.** Researchen under viste seg presis: GolfAPI.io ble faktisk
|
||
valgt (nøyaktig leverandøren anbefalt her) og bygget som **ADR-064**
|
||
(GolfAPI.io som tredje banekilde, delt cache-lag, kopier-inn i BEGGE
|
||
banemodeller) — Tjøme Golfklubb, Larvik Golfklubb (Seasidebanen) og
|
||
Nesbyen/Nesfjellet Golfklubb er alle importert og live i produksjon
|
||
(sistnevnte med 124 manuelt registrerte GPS-koordinater, siden GolfAPI
|
||
selv manglet koordinatdata for akkurat den banen — se ADR-064-tillegget
|
||
2026-08-13). Rangefinder til grønn (avstand front/midt/bak) er koblet inn
|
||
for frittstående runder. Researchen under er beholdt uendret som historisk
|
||
kontekst for HVORFOR GolfAPI.io ble valgt — les den som bakgrunn, ikke som
|
||
en gjenstående plan.
|
||
|
||
Brukeren undersøkte (i en ekstern Gemini-samtale, ikke denne økten — notert
|
||
her for å fange retningen før den glemmes, samme rutine som Order of
|
||
Merit-notatet over) hvordan TeeCup kan støtte baner utenfor Norge, siden
|
||
`teeoff_db` (ADR-004/ADR-019) kun dekker norske baner. Ingen kode skrevet,
|
||
ingen ADR-runde startet ennå — dette var et fremtidig, ikke-akutt behov,
|
||
relevant når/hvis TeeCup faktisk kommersialiseres og får brukere utenfor
|
||
Norge (se `teecup.golf`-domeneplanen, kjøpt med sikte på at TeeCup etter
|
||
hvert flytter dit fra `teecup.teeoff.no`).
|
||
|
||
**Valgt dataleverandør: Golf API (golfapi.io)** — ~42 000 baner globalt,
|
||
tilbyr BÅDE et REST-API og en fullstendig CSV-database-eksport
|
||
(`clubs.csv`/`courses.csv`/`tees.csv`/`coordinates.csv`). Andre vurderte
|
||
og forkastet: iGolf (40 000+, men B2B/enterprise-dyrt), GolfCourseAPI
|
||
(~30 000, gratisnivå men mer begrenset), Golf Intelligence (fokusert på
|
||
simulatorer/3D, feil målgruppe).
|
||
|
||
**Anbefalt tilnærming: Live API + egen caching, IKKE CSV-database-kjøp
|
||
foreløpig.** Dette er strukturelt identisk med mønsteret ADR-019 allerede
|
||
etablerte for teeoff-import (`source='official'` + `external_course_ref`,
|
||
kopiér-ved-eksplisitt-valg i stedet for live oppslag ved hver bruk) — samme
|
||
"importer én gang, lagre lokalt, aldri avhengig av en ekstern tjenestes
|
||
oppetid ved handicap-beregning"-prinsipp, bare med en ny ekstern kilde i
|
||
tillegg til teeoff. Golfapi.io sin avtale tillater eksplisitt caching
|
||
("you can store/cache the fetched course data... no need to call the API
|
||
to fetch the same course multiple times") — så en tredje `course.source`-
|
||
verdi (f.eks. `'international'`, mønster fra `'official'`) med et eget
|
||
`app/golfapi_client.py` (samme isolasjonsprinsipp som `app/teeoff_client.py`)
|
||
er den naturlige videreføringen NÅR dette designes, ikke en ny arkitektur.
|
||
CSV-database-kjøpet (3 995-5 995 € for alle baner, avhengig av om
|
||
koordinater inkluderes) er en betydelig forhåndskostnad som er unødvendig
|
||
så lenge caching-tilnærmingen dekker behovet organisk (bruker søker opp en
|
||
bane → 1 API-kall → lagres lokalt for alltid).
|
||
|
||
**Prisplan hos golfapi.io** (hentet 2026-08-03, kan være utdatert ved
|
||
faktisk bygging — bekreft på nytt): API-kall/måned: 50→29€, 500→99€,
|
||
2000→199€, 4000→299€, ubegrenset→399€ (krever årsabonnement). Et
|
||
engangskjøp på 500 tidsubegrensede kall finnes for 199€ (ingen løpende
|
||
kostnad, egnet for en beta-fase med få brukere). 15 dagers gratis prøve.
|
||
CSV-eksport (hvis man likevel velger det senere): «All courses» 3 995€
|
||
uten koordinater/5 995€ med, «Europa»/«USA»/«Asia-Pacific+Africa» hver
|
||
1 995€/2 995€. CSV gir 6 måneders oppdateringstilgang, deretter 20% av
|
||
opprinnelig pris per år for videre oppdateringer.
|
||
|
||
**Opprinnelig "neste steg"-plan, alle tre punkter nå avklart/bygget under
|
||
ADR-064/065 (2026-08-12/13):** (1) `course.source='international'` +
|
||
`external_course_ref` (org-siden) / `personal_course.external_golfapi_
|
||
course_id` (global) — ikke én ny verdi i ett skjema, men et kopier-inn i
|
||
BEGGE eksisterende banemodeller (ADR-064 Beslutning B, bekreftet med
|
||
bruker). (2) `app/golfapi_cache.py` (ikke `golfapi_client.py` som
|
||
opprinnelig navngitt her — client/cache-lagene ble delt i to, se ADR-064)
|
||
håndterer enhets-/datakvalitet-defensivt, inkl. en reell tomme-strenger-
|
||
i-stedet-for-null-bug funnet og fikset 2026-08-13. (3) Søkefeltet ble ETT
|
||
samlet felt akkurat som antatt her — "Fant ikke banen? Søk internasjonalt
|
||
(GolfAPI)"-lenke fra det vanlige egen-bane-søket, ikke to synlige knapper.
|
||
|
||
---
|
||
|
||
## Invitasjonskode, ledende side og projisert stilling — ADR-020
|
||
|
||
Reist av brukeren 2026-07-19, rett etter at kamp-play-flyten var komplett.
|
||
Full design i ARCHITECTURE_DECISIONS.md ADR-020.
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Kort invitasjonskode per turnering, overstyrer visibility | ✅ backend | `tournament.join_code` (migrasjon 011), genereres automatisk ved opprettelse. `GET /public/tournaments/by-code/{code}` + kode-bypass i `registration.py`. Løser "muntlig invitert spiller finner ikke turneringen"-hullet. |
|
||
| `match.leading_side` (strukturert, ikke tekst-parsing) | ✅ backend | Cachet i `recompute_and_cache_match_state` ved hver hull-innsending, eksponert på `MatchOut`. |
|
||
| Projisert stilling (hvis pågående matcher holder seg) | ✅ backend | `TeamStanding.projected_points` + `SessionStanding.projected_points_by_team` i leaderboard-endepunktet. Ikke-avgjorte matcher gir full poengsum til `leading_side`, delt 0,5/0,5 ved "AS". |
|
||
| Login-skjerm: kode-felt, tar deg direkte til turneringen | ✅ LIVE 2026-07-19 | `login-form.tsx` sin `JoinByCode`. `code` tres gjennom `/t/[id]` sin lesing OG registrering. |
|
||
| Turnering-detalj: vis invitasjonskode (kopier-knapp) | ✅ LIVE 2026-07-19 | `tournament-detail.tsx` sin `JoinCodeChip` — henter fra org-ens turneringsliste, ikke et nytt endepunkt. |
|
||
| Leaderboard: to stillingsbarer (faktisk + projisert) | ✅ LIVE 2026-07-19 | `tournament-leaderboard.tsx` sin `SegmentedBar` — fargesegmentert rektangel, ikke tall side om side. Eksisterende tall-scoreboard beholdt som detaljvisning. |
|
||
| Fargekoding av matchlister etter ledende lag | ✅ LIVE 2026-07-19 | `session-blind-draw.tsx` sin `RevealedView`/`RevealSide` — farget toppkant + status-chip, bruker `leading_side` + eksisterende `team.color`, ingen ny fargemodell. |
|
||
| Kode-regenerering (ved lekket kode) | 💤 bevisst utsatt | Ikke bygget denne runden — ingen organisator-vei til å bytte ut en kode ennå. Egen sak hvis etterspurt. |
|
||
|
||
**ADR-020 er dermed helt ferdig** — backend + frontend, alle fire
|
||
del-ønsker levert samme dag. Se CHANGELOG.md for full runde inkl. en
|
||
reell (og transparent håndtert) passord-eksponeringshendelse underveis.
|
||
|
||
---
|
||
|
||
## Innlogging: sesjon-bug + passord/2FA — ADR-021 (reist 2026-07-19)
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Flere organisasjoner per bruker | ✅ bekreftet allerede dekket | ADR-002 fra dag én. `POST /orgs` har ingen begrensning på antall org-er samme bruker kan eie. Ingen kodeendring — kun bekreftet ved gjennomgang 2026-07-19. |
|
||
| Sesjon holder seg ikke — ny magic-link-kode kreves ved hvert besøk | ✅ FIKSET og LIVE 2026-07-19 | Ikke en cookie-bug (brukerens ekte cookie var korrekt satt, 30 dager, `Secure`/`HttpOnly`). Root cause: `app/page.tsx` sjekket aldri om en gyldig sesjon allerede fantes før den viste innloggingsskjemaet. Fikset med server-side sesjonssjekk + redirect til `/dashboard`. Se CHANGELOG.md for full diagnose og verifisering. |
|
||
| Passord som valgfritt tillegg til magic-link | ✅ LIVE 2026-07-19 | Argon2id-hashing (ikke bcrypt — unngår 72-byte-trunkering). Verifisert med et ekte passord med mellomrom+æøå+spesialtegn. `POST /auth/login-password`, `/auth/set-password`, `/auth/remove-password`. Passord er ALDRI påkrevd. |
|
||
| 2FA: TOTP eller e-post-engangskode, brukerens eget valg | ✅ LIVE 2026-07-19 | SMS bevisst utenfor omfang (krever betalt leverandør). `POST /auth/2fa/setup/start`+`/confirm`, `/auth/2fa/verify`, `/auth/2fa/disable`. |
|
||
| 2FA påkrevd for org-eier/admin, valgfritt for medlemmer | ✅ LIVE 2026-07-19 | Håndheves ved hver innlogging via en `stage: "pending_2fa"`/`"must_enroll_2fa"`-mellomtilstand i sesjons-JWT-en. Verifisert: en fersk org-eier uten 2FA ble korrekt tvunget inn i oppsett ved neste innlogging. |
|
||
| Frontend: passord-innlogging, 2FA-verifisering/-oppsett, kontoinnstillinger | ✅ LIVE 2026-07-19 | `login-form.tsx` (passord-modus), `two-factor-flow.tsx` (delt mellom login/verify), `/account`. |
|
||
|
||
**ADR-021 er dermed helt ferdig.** Se CHANGELOG.md for full byggerunde,
|
||
inkl. tre reelle bugs funnet og fikset under scratch-testing.
|
||
|
||
### Organisasjonseierskap: dele, invitere, frasi seg, superadmin — ADR-022
|
||
|
||
Reist rett etter ADR-021. Avdekket et bredere, mer fundamentalt hull enn
|
||
bare "del eierskap": det fantes tidligere INGEN vei til å legge til et
|
||
organisasjonsmedlem etter opprettelse i det hele tatt — kun grunnleggerens
|
||
egen `owner`-rad ble noensinne satt inn.
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Flere eiere per organisasjon | ✅ LIVE | Skjemaet støttet det allerede (ingen unikhetssperre); nå faktisk brukbart via API. |
|
||
| E-post-invitasjon (owner→hvilken som helst rolle, admin→kun member) | ✅ LIVE 2026-07-19 | `POST/GET/DELETE /orgs/{id}/invitations`. Auto-akseptert ved neste innlogging (magic-link ELLER passord), samme mønster som spiller-e-post-kobling (ADR-017). Verifisert: admin som forsøkte å invitere som eier ble korrekt avvist. |
|
||
| Rollestyring + frasi seg eierskap (selvbetjent) | ✅ LIVE 2026-07-19 | `PATCH`/`DELETE /orgs/{id}/memberships/{id}`. «Siste eier»-vern (409 `LAST_OWNER`) OG selv-forfremmelse-vern verifisert eksplisitt i scratch. |
|
||
| Superadmin (manuelt DB-tildelt, ikke selvbetjent) | ✅ LIVE 2026-07-19 | `POST /superadmin/orgs/{id}/memberships`. Verifisert: ikke-superadmin avvist (403), superadmin kan sette eierskap på en org de selv ikke er medlem av, ukjent e-post avvist (404). |
|
||
| Fjernet eiers roster-/spillerdata | ✅ avgjort (Beslutning E) | Forblir urørt — organisasjons-styring ≠ deltakelse-historikk. Bekreftet av bruker 2026-07-18. |
|
||
| Frontend: medlemsstyring/invitasjon | ✅ LIVE 2026-07-19 | `org-members.tsx`, `/orgs/[id]/members`, lenket fra dashbordet. |
|
||
|
||
**ADR-022 er dermed helt ferdig.** Ingen dedikert superadmin-UI bygget
|
||
(bevisst — brukes via API av en betrodd operatør, matcher «sjelden,
|
||
manuelt tildelt makt»-designet).
|
||
|
||
---
|
||
|
||
## Rapporterte hull, blind draw + scorekort (2026-07-19)
|
||
|
||
Fire punkter rapportert av brukeren fra faktisk testing av
|
||
`/tournaments/.../sessions/...` (blind draw-skjermen) og scorekort-skjermen,
|
||
foursome-format. Root cause funnet ved kodegjennomgang for alle fire (det
|
||
fjerde, hcp, ble bekreftet mot EKTE produksjonsdata — ikke gjettet). Alle
|
||
fire er nå ✅ FIKSET OG SCRATCH-VERIFISERT samme dag — punkt 2 ble omdefinert
|
||
etter en presisering fra brukeren (se under), fikk en egen ADR (ADR-029) og
|
||
en reell skjemamigrasjon (`014_tee_gender_to_rating.sql`).
|
||
|
||
1. **✅ FIKSET 2026-07-19: valgt spiller forsvant ikke fra listen over
|
||
velgbare spillere.** Root cause: `AddSlotForm` i
|
||
`components/session-blind-draw.tsx` merket allerede brukte roster-rader
|
||
som `disabled` på selve `<option>`-elementet — et `disabled`
|
||
HTML-`<option>` blir stående synlig (kun gråtonet), forsvinner ikke.
|
||
Fikset: `roster`-listen FILTRERES nå ned til kun ledige spillere før
|
||
den rendres, i stedet for å deaktivere valget.
|
||
|
||
2. **✅ FIKSET OG LIVE 2026-07-19 (ADR-029).** Brukeren presiserte at min opprinnelige forståelse
|
||
var feil: en golfbane har IKKE fysisk kjønnsdelte utslag — begge kjønn
|
||
kan som regel spille fra ethvert utslag. Det eneste som faktisk
|
||
varierer per kjønn er om klubben har VALGT å slope (rate) et gitt
|
||
utslag for det respektive kjønnet (typisk: noen klubber sloper bevisst
|
||
ikke det lengste utslaget for damer). Bekreftet mot ekte produksjonsdata
|
||
(Tjøme Golfklubb, importert via ADR-019): hvert fysisk utslag ("32",
|
||
"44", "50", "55") lå som TO separate `tee`-rader med SAMME navn, én per
|
||
kjønn — selve konflateringen brukeren påpekte. **Fikset:** `gender`
|
||
flyttet fra `tee` til `tee_rating` (migrasjon
|
||
`014_tee_gender_to_rating.sql`, se ADR-029 for full detalj), tee-valg i
|
||
blind draw er nå HELT automatisk (organisator velger kun fysisk
|
||
utslag, riktig kjønnsrating løses fra spillerens `player.gender`
|
||
server-side — bruker bekreftet dette fremfor et forhåndsutfylt-men-
|
||
overstyrbart alternativ), manglende kjønn/rating avvises tydelig FØR
|
||
innsetting (bruker bekreftet "feil høyt" fremfor stille fallback).
|
||
ADR-019s teeoff-import, manuell tee-opprettelse og bane-bytte-remap
|
||
(`_remap_course`) alle oppdatert til samme modell.
|
||
|
||
3. **✅ FIKSET 2026-07-19: tallvelger (1–9 + utvidbar "10 eller flere" →
|
||
10–19) i stedet for pluss/minus-steppere.** Ny `StrokePicker`-komponent
|
||
i `components/session-scorecard.tsx`, erstatter den gamle ±-stepperen
|
||
helt (`stepStroke`/`Minus`/`Plus` fjernet som død kode). Ren
|
||
frontend-endring.
|
||
|
||
4. **✅ FIKSET 2026-07-19: HCP ble faktisk ALDRI beregnet for
|
||
front_9/back_9-økter — bekreftet ekte kodebug, ikke bare en
|
||
synlighetsmangel.** Diagnostisert presist ved å lese EKTE
|
||
produksjonsdata (read-only, `teecup_db`) for brukerens rapporterte
|
||
testøkt: `hole_config='front_9'`, `format='foursome'`, og ALLE fire
|
||
`match_participant`-radene hadde `course_handicap`/`playing_handicap =
|
||
NULL`. Root cause: `app/handicap.py` sin
|
||
`compute_and_store_side_handicaps` joinet `tee_rating` på ØKTENS
|
||
`hole_config` som ratingens `scope` — men `tee_rating`-rader lages i
|
||
praksis KUN med `scope='full_18'` (ADR-019 Beslutning D + tee-
|
||
endepunktet), og ADR-008 sin allerede etablerte design tilsier nettopp
|
||
dette: course handicap skal ALLTID regnes fra full_18-ratingen, uansett
|
||
øktens hole_config — front/back-9-fordelingen skjer SENERE, ved selve
|
||
slagtildelingen (`allocate_over_played_holes`), ikke ved rating-
|
||
oppslaget. Joinen matchet dermed aldri noen rad for en front_9/back_9-
|
||
økt, og handicap ble stille aldri beregnet — 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
|
||
`compute_and_store_side_handicaps`/`_recompute_session_matches` (var
|
||
død etter fiksen) og de tre kallstedene i `matches.py`/`tournaments.py`.
|
||
**Bekreftet at selve UTREGNINGEN matcher brukerens egen beskrivelse
|
||
nøyaktig** (kombinert course handicap / 2 via `CombinedPercentage(0.5)`,
|
||
laveste side satt til 0 mottatte slag via `match_play_strokes`, resten
|
||
fordelt fra stroke index 1 via `allocate_over_played_holes`) — bugen lå
|
||
i at beregningen ALDRI kjørte for front_9/back_9, ikke i selve formelen.
|
||
**Scratch-verifisert presist:** gjenskapte nøyaktig samme scenario
|
||
(foursome + front_9, hcp 10/20 vs. 5/15) — course/playing handicap
|
||
beregnet korrekt (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, ikke "halved") — direkte bevis på at handicap nå faktisk
|
||
brukes. Ingen migrasjon (ren Python-logikk-fiks).
|
||
**✅ FIKSET 2026-07-26** (i en egen "fiks alle kjente små bugs"-runde,
|
||
lenge etter oppdagelsen over): et stroke-modus scoreinnsending på en
|
||
bane UTEN registrerte hull (`hole`-tabellen tom) krasjet med en rå 500
|
||
(`IndexError: list index out of range` i `handicap_engine.py` sin
|
||
`allocate_over_played_holes`, kalt fra `scoring.py` sin
|
||
`_compute_hole_results`) i stedet for en tydelig `VALIDATION_FAILED`.
|
||
Fikset akkurat som foreslått da bugen ble oppdaget: eksplisitt sjekk
|
||
(`len(all_18_si) != 18`) rett før `allocate_over_played_holes` kalles,
|
||
ikke en try/except rundt symptomet — samme mønster som den allerede
|
||
kjente/fikset manglende-handicap-indeks-krasjen fra blind draw-runden
|
||
(2026-07-18). Scratch-verifisert presist (5/5 sjekker): en bane UTEN
|
||
hull gir nå ren `400 VALIDATION_FAILED` ved hull-scoreinnsending i
|
||
stedet for 500, OG en regresjonssjekk bekreftet at en NORMAL bane
|
||
(med alle 18 hull) fortsatt scorer helt uendret (begge sider, full
|
||
scorekort-henting). `test_isolation.sql` 12/12 uendret (ren
|
||
Python-logikk-fiks, ingen migrasjon). Rullet ut sammen med
|
||
dashbord-hilsen-fiksen under, se CHANGELOG.md.
|
||
|
||
**Designspørsmålene for punkt 2 er avklart** (bruker valgte det anbefalte
|
||
alternativet på begge, se ADR-029 Beslutning B/C): helautomatisk tee-valg
|
||
(ingen manuell kjønnsvelger), og "feil høyt" ved manglende kjønn/rating
|
||
(ingen stille fallback). Migrasjon 014 kjørt mot ekte `teecup_db`
|
||
2026-07-19, bruker bekreftet eksplisitt — Tjømes 8 tee-rader slått sammen
|
||
til 4, alle referanser intakte.
|
||
|
||
**Alle fire punkter rullet ut live 2026-07-19**, bruker bekreftet
|
||
eksplisitt: migrasjon 014 + `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Verifisert: `/health`/`dashboard` → 200, `teeoff.no`
|
||
upåvirket, 0 brutte `match_participant.tee_id`-referanser etter
|
||
sammenslåingen.
|
||
|
||
---
|
||
|
||
## Rapporterte UI-/UX-hull (2026-07-19) — ✅ ALLE FIRE FIKSET (siste 2026-07-21)
|
||
|
||
Fire punkter rapportert av brukeren fra faktisk bruk av `teecup.teeoff.no`.
|
||
Root cause funnet ved kodegjennomgang for de tre første (ikke bare gjettet);
|
||
det fjerde er et reelt manglende UI-element, ingen kodefeil. Ingen av de fire
|
||
er fikset i denne runden — kun dokumentert slik at de ikke går i glemmeboken.
|
||
|
||
1. **✅ FIKSET 2026-07-19/20 (ADR-030).** Dashboard-turneringskortet viste
|
||
"Ingen datoer satt" selv om øktene (rundene) hadde dato/klokkeslett
|
||
satt — bekreftet av brukeren med et faktisk skjermbilde (en økt tydelig
|
||
planlagt til "lør. 11. juli", men kortet viste fortsatt "Ingen datoer
|
||
satt"). Root cause: `tournament.start_date`/`end_date` var et EGET,
|
||
frittstående felt (ADR-015) som INGEN UI noensinne satte — helt atskilt
|
||
fra `session.scheduled_at`. Valgte retning (b) fra de to opprinnelig
|
||
skisserte alternativene: `GET /orgs/{id}/tournaments` utleder nå
|
||
datospennet fra øktenes `scheduled_at` (COALESCE med et evt. eksplisitt
|
||
satt `start_date`/`end_date`, som fortsatt vinner om det noensinne
|
||
settes). Se ADR-030 for full detalj og verifisering.
|
||
|
||
2. **✅ FIKSET 2026-07-20: `/orgs/{id}/members`-siden ga en rå API-404
|
||
(`{"detail":"Not Found"}`), ikke medlemssiden.** Bekreftet root cause,
|
||
ikke bare reprodusert: `frontend/next.config.mjs` sin `rewrites()`
|
||
returnerer en PLAIN ARRAY (implisitt "afterFiles"-semantikk i Next.js)
|
||
— statiske filer/sider sjekkes FØR rewrites, men DYNAMISKE sider (som
|
||
`app/orgs/[id]/members/page.tsx`) sjekkes ETTER. Rewrite-regelen
|
||
`{ source: "/orgs/:path*", ... }` (ADR-016) fanget derfor kallet FØR
|
||
Next.js noensinne nådde selve siden, og sendte det til FastAPI i
|
||
stedet (som naturligvis ikke har noen `GET /orgs/{id}/members`-rute).
|
||
**Fikset:** siden flyttet til `app/organizations/[id]/members/page.tsx`
|
||
(utenfor det proxyede `/orgs/*`-prefikset), eneste lenke til den
|
||
(`dashboard.tsx`) oppdatert tilsvarende. Lagt til en forklarende
|
||
kommentar direkte i `next.config.mjs` sin `rewrites()` slik at samme
|
||
feil ikke gjentas for en fremtidig ny side. Verifisert med ekte
|
||
produksjonsbuild + container-boot: den nye ruten rendrer faktisk
|
||
`OrgMembers`-komponenten (ikke en 404 eller innloggingssiden).
|
||
|
||
3. **Dashboard: ingen vei til å opprette/legge til en ANDRE organisasjon.**
|
||
Bekreftet i `dashboard.tsx`: `CreateOrganizationState`
|
||
(opprett-organisasjon-skjemaet) vises KUN når `hasOrg` er `false`, altså
|
||
når brukeren har null organisasjoner fra før. Har brukeren allerede én
|
||
org, finnes ingen knapp/lenke noe sted i UI-et for å opprette en til —
|
||
selv om backend-et støtter dette fullt ut og uten begrensning (ADR-021,
|
||
bekreftet: `POST /orgs` har ingen grense på antall org-er én bruker kan
|
||
eie). Ren manglende UI, ikke en backend-begrensning.
|
||
|
||
4. **Ingen måte å se, på ett blikk, at ALLE runder/økter i en turnering har
|
||
fått dato/klokkeslett satt.** `tournament-program.tsx` viser
|
||
`scheduled_at` per øktkort hvis satt, ingenting spesielt (ingen
|
||
fremhevet "mangler dato"-tilstand) hvis ikke. Ingen sammendrag/telling
|
||
noe sted ("X av Y runder har dato") — organisatoren må åpne
|
||
program-skjermen og lese hvert kort manuelt. Ren UX-mangel, ingen
|
||
bakenforliggende datamodell-begrensning (all nødvendig data finnes
|
||
allerede i `GET .../sessions`).
|
||
|
||
Punkt 1 (datovisning, ADR-030) og punkt 2 (medlemsside-ruten) er nå ✅
|
||
fikset, se over.
|
||
|
||
3. **✅ FIKSET 2026-07-21: ingen vei til å opprette en ANDRE organisasjon.**
|
||
Ny `NewOrganizationControl`-komponent i `dashboard.tsx` (identisk mønster
|
||
som `NewTournamentControl` — inline-ekspanderende navnefelt), plassert i
|
||
`OrganizationView` sin header ved siden av «Medlemmer»/«Ny turnering»,
|
||
uavhengig av antall org-er brukeren allerede har. Ingen backend-endring
|
||
nødvendig (`POST /orgs` hadde aldri noen begrensning).
|
||
|
||
4. **✅ FIKSET 2026-07-21: intet sammendrag for "alle runder har dato".**
|
||
Ny `DateCoverageSummary`-komponent i `tournament-program.tsx`, vist
|
||
øverst i øktlisten (kun når minst én økt finnes): «X av Y runder har
|
||
fått dato og klokkeslett» (uthevet/grønn når alle er satt). Ren
|
||
klientside-telling av allerede lastet `scheduled_at`-data, ingen
|
||
backend-endring.
|
||
|
||
Alle fire punktene fra 2026-07-19 er dermed fikset. Begge siste fikser
|
||
verifisert med ekte typesjekket produksjonsbuild, rullet ut live
|
||
2026-07-21 (kun `teecup_frontend`, ingen migrasjon), bruker bekreftet
|
||
eksplisitt.
|
||
|
||
---
|
||
|
||
### Rediger spiller (spillerpool) — ✅ BYGGET OG SCRATCH-VERIFISERT 2026-07-19/20
|
||
|
||
Reist av brukeren samme runde som ADR-030: ingen vei fantes til å rette en
|
||
feilregistrert spiller (f.eks. en HCP-skrivefeil) etter opprettelse — kun
|
||
`POST` fantes for `player`.
|
||
|
||
Ny `PATCH /orgs/{id}/players/{id}` (`app/routers/players.py`), vanlig
|
||
`exclude_unset`-PATCH-mønster, dekker alle spillerfelt (navn, HCP, kjønn,
|
||
mobil, e-post, fødselsdato, kallenavn, land, klubb, medlemsnummer). Frontend:
|
||
ny "Rediger spiller"-handling i rosterradens "⋮"-meny
|
||
(`tournament-detail.tsx`), inline skjema for navn/HCP/kjønn.
|
||
|
||
**Viktig, bevisst grense — kommunisert i selve UI-et, ikke skjult:** dette
|
||
endrer spilleren i POOLEN (brukes ved fremtidig rostring), IKKE et lags
|
||
allerede FROSNE `team_roster.handicap_index_snapshot` (ADR-007 — reproduser-
|
||
barhet for allerede opprettede turneringer/lag er et bevisst designvalg, ikke
|
||
noe denne rundens fiks endrer på). Skjemaet viser en tydelig forklarende
|
||
tekst om dette; å oppdatere et allerede rostret lags viste HCP-tall krever
|
||
fortsatt å fjerne og legge til spilleren på nytt (eksisterende funksjon).
|
||
|
||
**Scratch-verifisert:** HCP-endring lykkes, delvis PATCH (kun navn) lar HCP
|
||
stå urørt, tomt PATCH avvist (400), ukjent spiller-id gir 404, og — 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). Ekte typesjekket produksjonsbuild kjørt og
|
||
bekreftet.
|
||
|
||
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt, ingen migrasjon.
|
||
|
||
---
|
||
|
||
## Personlig landingsside for ENHVER registrert bruker + personlig profil — ✅ BYGGET OG SCRATCH-VERIFISERT 2026-07-20 (ADR-031)
|
||
|
||
Reist av brukeren 2026-07-20 som en "tenk igjennom og foreslå"-instruks,
|
||
deretter et "gjør det" med utvidet omfang (personlig profil-CRUD lagt til:
|
||
profilbilde, fornavn, etternavn, fødselsdato, kjønn, HCP, hjemmeklubb). Full
|
||
detalj i ARCHITECTURE_DECISIONS.md ADR-031 — kort her:
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Ett samlet dashboard (ikke to atskilte ruter) | ✅ | Ny "Mine runder"-seksjon øverst i `dashboard.tsx`, organisasjonsseksjonen uendret under, begge vises hvis begge finnes. |
|
||
| "Mine runder" — tverr-org spiller-oppslag | ✅ | Ny `player_organizations_for_user()`-bro (migrasjon 015, samme mønster som fire tidligere), `/auth/me` utvidet med `my_tournaments`. Kun rostrede lag i v1 (ikke rene påmeldinger uten roster). |
|
||
| Personlig profil (fornavn/etternavn/fødselsdato/kjønn/HCP/hjemmeklubb) | ✅ | Nye felt på `app_user` (migrasjon 015) — ETT sett per konto, BEVISST atskilt fra org-scopede `player`-rader (se ADR-031 Beslutning B for hvorfor). `PATCH /auth/profile`, ny seksjon i `/account`. |
|
||
| Profilbilde | ✅ | `POST`/`DELETE /auth/profile/avatar`, samme ekte multipart→AVIF-opplasting som turnering-hero-bilder (ADR-018). |
|
||
| **Sikkerhetsutvidelse funnet UNDER bygging:** deltaker-tilgang uansett synlighetsnivå | ✅ | `check_visibility()` ga tidligere kun deltaker-tilgang for `visibility='participants'` — IKKE for `'org'` (DEFAULT for enhver ny turnering), som ville stengt ute enhver spiller uten org-medlemskap fra sin EGEN turnering. Utvidet til å gjelde begge ikke-offentlige tier. Verifisert: deltaker FÅR nå tilgang, fremmed+anonym fortsatt avvist (ingen innstramming, ren utvidelse). |
|
||
|
||
**Bevisst UTENFOR omfang, kjent gjenstående begrensning (se ADR-031 for full
|
||
begrunnelse):** "Mine runder" lenker til den offentlige turnering-siden
|
||
(`/t/{id}`), IKKE til lagets private chat eller scorekortet — disse krever
|
||
fortsatt ekte organisasjonsmedlemskap (`get_authorized_org`), en strengere
|
||
sperre enn deltaker-status alene, brukt av dusinvis av endepunkter på tvers
|
||
av appen. Å utvide DEN sperren til også å godta "faktisk deltaker" er en
|
||
egen, større og mer risikofylt endring (påvirker autorisasjonsarkitekturen
|
||
bredt, ikke ett enkelt endepunkt) — naturlig neste steg, men bevisst ikke
|
||
gjort i denne runden. Notifikasjons-/aktivitetsfeed og HCP-historikk over
|
||
tid også bevisst utenfor omfang v1 (samme begrunnelse som opprinnelig
|
||
forslag).
|
||
|
||
**Oppdatering 2026-07-21 — deltaker-tilgang til lag-chat/scorekort er
|
||
dermed ✅ BYGGET OG LIVE, se egen seksjon lenger ned.**
|
||
|
||
**Scratch-verifisert, 15 sjekker** (profil-CRUD komplett, inkl. sletting av
|
||
enkeltfelt og avatar; en EKTE ren spiller uten org-medlemskap ser riktig
|
||
`my_tournaments`; den kritiske sikkerhetssjekken: samme spiller får nå se
|
||
sin `'org'`-synlige turnering, mens fremmed/anonym fortsatt avvises). Ekte
|
||
typesjekket produksjonsbuild kjørt og bekreftet.
|
||
|
||
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: migrasjon 015
|
||
kjørt mot ekte `teecup_db`, begge containere redeployet, `/health`/
|
||
`/dashboard`/`/account` → 200, `teeoff.no` upåvirket.
|
||
|
||
### Naturlig neste steg (ikke bygget, notert for senere)
|
||
- **Deltaker-tilgang (uten org-medlemskap) til lag-chat og scorekort — ✅
|
||
BYGGET OG LIVE 2026-07-21**, se egen seksjon lenger ned.
|
||
- **HCP-historikk over tid — ✅ BYGGET OG LIVE 2026-07-21**, se egen seksjon
|
||
lenger ned.
|
||
- Notifikasjons-/aktivitetsfeed på "Mine runder" — fortsatt IKKE bygget.
|
||
- "Mine runder" for RENE påmeldinger (`tournament_registration` uten
|
||
roster ennå) — v1 viser kun rostrede lag, fortsatt IKKE bygget.
|
||
|
||
### 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 at mobil (med landsnummer) burde være en opsjon. Full
|
||
detalj i ADR-032.
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Mobil (landsnummer + nummer, to separate felt) | ✅ | Del av samme `PATCH /auth/profile` som resten av profilen — ren tilføyelse. |
|
||
| E-post — verifisert to-stegs bytte, IKKE en enkel PATCH | ✅ | Ny `email_change_token`-tabell (migrasjon 016, samme mønster som `magic_link_token`). `POST /auth/profile/email` (krever sesjon, sender lenke til den NYE adressen) + `POST /auth/profile/email/confirm` (forbruker token atomisk, ingen sesjon påkrevd — samme som selve magic-link-verifiseringen). E-posten endres ALDRI før lenken faktisk åpnes. Ny `/verify-email`-side. |
|
||
|
||
**Scratch-verifisert, 10 sjekker** — inkl. at et bytte til en allerede brukt
|
||
adresse avvises, at e-posten forblir uendret helt til bekreftelse, at samme
|
||
kode ikke kan gjenbrukes, og at en ny innlogging med den GAMLE adressen
|
||
oppretter en fersk, tom konto (beviser byttet er reelt, ikke kosmetisk).
|
||
Ekte typesjekket produksjonsbuild kjørt og bekreftet.
|
||
|
||
**Rullet ut live 2026-07-20**, bruker bekreftet eksplisitt: migrasjon 016
|
||
kjørt mot ekte `teecup_db`, begge containere redeployet, `/health`/
|
||
`/dashboard`/`/account`/`/verify-email` → 200, `teeoff.no` upåvirket.
|
||
|
||
---
|
||
|
||
## Midlertidige spillere + automatisk etter-runde-invitasjon — ✅ BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28 (økt-nivå)
|
||
|
||
Reist samme runde som punktet over, uttalt som punkt 2 (ikke like prioritert
|
||
som "Mine runder"-dashbordet, men skal likevel dokumenteres grundig nå).
|
||
|
||
**Viktig presisering, funnet ved kodegjennomgang FØR noe ble antatt:** det
|
||
meste av "midlertidig spiller"-behovet er allerede dekket av eksisterende
|
||
funksjonalitet, ikke et hull i seg selv — `POST /orgs/{id}/players` krever
|
||
ALDRI at spilleren har en konto (`player.user_id` er nullable, kobles først
|
||
automatisk når/hvis noen logger inn på matchende e-post, ADR-017
|
||
Beslutning B). En organisator kan altså allerede legge til "Ola Nordmann,
|
||
ola@example.com" uten at Ola noensinne har hørt om TeeCup. Det som
|
||
FAKTISK mangler er den PROAKTIVE oppfølgingen brukeren ber om: et
|
||
automatisk e-post-utsendelse-steg etter runden, med scorekort + invitasjon
|
||
til å logge inn og "ta eierskap" over spiller-profilen sin — dette finnes
|
||
ikke i noen form i dag (en spiller må selv, uoppfordret, logge inn for at
|
||
koblingen skal skje).
|
||
|
||
**Foreslått design, IKKE bygget:**
|
||
- **Utløses av en EKSPLISITT organisator-handling, ikke en automatisk
|
||
bakgrunnsjobb** ("Send scorekort og invitasjon til alle med e-post i
|
||
denne økten", en knapp på øktnivå når øktens matcher er avgjort).
|
||
Anbefalt fremfor helautomatisk utsendelse ved et gjettet
|
||
"runden er ferdig"-tidspunkt — unngår uventede e-poster fra en
|
||
feilaktig auto-deteksjon, og matcher prosjektets øvrige mønster (blind
|
||
draw krever eksplisitt lås, walkover er en eksplisitt handling — ingen
|
||
"magisk" auto-trigger noe annet sted i appen).
|
||
- **Ingen ny databasekolonne nødvendig for selve "midlertidig"-begrepet**
|
||
— enhver `player`-rad UTEN `user_id` ER allerede "midlertidig" i praksis.
|
||
Kun en NY, liten `sent_at`-lignende sporingskolonne kan trengs for å
|
||
unngå dobbel utsending ved gjentatt klikk (åpent spørsmål, se under).
|
||
- **E-posten gjenbruker eksisterende infrastruktur** (`app/email.py`,
|
||
samme SMTP-oppsett som magic-link/2FA) — innhold: spillerens
|
||
hull-for-hull-resultat for økten + en ekte innloggingslenke (vanlig
|
||
magic-link, ingen ny auth-mekanisme nødvendig siden `link_player_by_
|
||
email()` allerede kobler kontoen automatisk ved første innlogging).
|
||
|
||
**Åpne spørsmål — alle avklart 2026-07-28 (AskUserQuestion), deretter bygget:**
|
||
- **Nivå: ØKT** (bekreftet, den anbefalte retningen). Ny
|
||
`POST /orgs/{id}/sessions/{id}/send-scorecard-invitations`
|
||
(`app/routers/tournaments.py`).
|
||
- **Dobbel-utsending-sperre: JA.** Ny `match_participant.
|
||
invitation_sent_at timestamptz` (migrasjon `034`) — samme
|
||
"tidsstempel = skjedd"-mønster som `round.started_at` m.fl. Kun
|
||
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 kvalifiserer — responsen skiller
|
||
eksplisitt mellom `sent`/`skipped_has_account`/`skipped_no_email`/
|
||
`skipped_already_sent` for full gjennomsiktighet i UI-et.
|
||
- **Locale: bevisst alltid `nb`** (ikke bygget som et valg) — en spiller
|
||
uten konto har ingen lagret språkpreferanse å lese fra, og dette er en
|
||
norsk klubb-app. Ny `send_session_result_email()` i `app/email.py`
|
||
har likevel BEGGE nb/en-maler klare (samme fil-konvensjon som ellers),
|
||
kun `nb` faktisk brukt i dag.
|
||
E-posten inneholder et EKTE, ferdig innloggingslenke (samme
|
||
magic-link-mekanisme som vanlig innlogging, ikke bare en «logg inn
|
||
senere»-henvisning) + matchresultat (status_text) + individuelt
|
||
slagtotal når tilgjengelig (individuell-ball-formater).
|
||
Scratch-verifisert grundig (54/54 sjekker, inkl. at den utstedte
|
||
lenken faktisk logger spilleren inn og kobler kontoen automatisk til
|
||
spiller-profilen, ADR-017-mekanismen uendret) OG bekreftet direkte i
|
||
en ekte innlogget nettleser (Chrome DevTools) — knappen viste korrekt
|
||
«1 invitasjon sendt, 1 mangler registrert e-post» første gang, «0
|
||
invitasjoner sendt, 1 allerede sendt tidligere, …» andre gang. Se
|
||
CHANGELOG.md 2026-07-28 for full detalj.
|
||
|
||
---
|
||
|
||
## Deltaker-tilgang til lag-chat og scorekort (uten org-medlemskap) — ✅ BYGGET OG LIVE 2026-07-21
|
||
|
||
Direkte oppfølging av ADR-031s kjente, notert begrensning: "Mine runder"
|
||
lenket til den offentlige turnering-siden, men IKKE til lagets private
|
||
chat eller det skrivbare scorekortet — begge krevde fortsatt ekte
|
||
organisasjonsmedlemskap (`get_authorized_org`), noe en ren, rostret/
|
||
påmeldt spiller (uten organisasjonsmedlemskap) ikke har.
|
||
|
||
**Kjernefunn ved gjennomlesing (ikke antatt):** de faktiske
|
||
autorisasjonsprimitivene (`user_is_rostered_on_team`,
|
||
`user_is_match_participant`, `user_is_team_captain`,
|
||
`own_team_ids` — alle i `app/team_authz.py`/`app/blind_draw.py`) støttet
|
||
ALLEREDE ikke-org-medlemmer korrekt overalt — de var bare plassert BAK en
|
||
ekstra, blank `Depends(get_authorized_org)`-sperre på ni endepunkter på
|
||
tvers av fire filer. Fikset ved kirurgisk å fjerne akkurat den sperren fra
|
||
disse ni (lag-chat lese/skrive/slette i `messaging.py`; scorekort-lesing,
|
||
slag-/hull-resultat-innsending, walkover i `scoring.py`; match-/lag-/
|
||
økt-listing i `matches.py`/`tournaments.py`; walkover-på-turnering-nivå i
|
||
`tournaments.py`; bane-hull i `courses.py`) — de eksisterende
|
||
domene-sjekkene (som allerede har egen org-admin-fallback der det er
|
||
tiltenkt) er den REELLE sikkerhetsgrensen, ikke `get_authorized_org`.
|
||
|
||
**For endepunkter som IKKE hadde noen finkornet sjekk i det hele tatt**
|
||
(f.eks. `get_scorecard`, `list_sessions`, `list_teams` — disse stolte
|
||
UTELUKKENDE på org-medlemskap) ville en ren fjerning av sperren latt EN
|
||
HVILKEN SOM HELST innlogget bruker se dem — løst med et nytt, eksplisitt
|
||
`is_org_member(...) OR user_is_tournament_participant(...)`-OR (begge nye
|
||
hjelpefunksjoner i `team_authz.py`). `user_is_tournament_participant` er
|
||
FLYTTET dit fra `registration.py` (het `is_participant` der) — org-scopede
|
||
routere kan ikke importere fra `registration.py` uten sirkulær import
|
||
(registration.py importerer FRA dem), men alle importerer allerede fritt
|
||
fra den avhengighetsfrie `team_authz.py`. `courses.py` sin `list_holes`
|
||
fikk en bevisst LØSERE sjekk (`user_is_org_player` — kun "koblet til NOEN
|
||
spillerprofil i org-en", ikke bundet til én turnering) siden par/
|
||
stroke-index er lavsensitiv banedata, ikke spillerdata.
|
||
|
||
**`/auth/me` utvidet** med `my_session_id`/`my_match_id` per rad i
|
||
`my_tournaments` (en av spillerens egne matcher, ikke-avgjort foretrukket)
|
||
— lar frontend lenke direkte til riktig chat/scorekort uten at spilleren
|
||
selv må navigere via program-/blind draw-skjermene (som fortsatt krever
|
||
org-medlemskap for andre formål). "Mine runder"-kortet i `dashboard.tsx`
|
||
fikk to nye handlingslenker: "Lag-chat" (alltid) og "Scorekort" (når
|
||
`my_match_id` finnes).
|
||
|
||
**Scratch-verifisert grundig, 43 automatiserte sjekker** (isolert
|
||
`teecup_app_scratch`-rolle, isolert scratch-MinIO, engangs API-container):
|
||
en rostret spiller UTEN org-medlemskap fikk korrekt tilgang til alt de ni
|
||
endepunktene dekker (inkl. faktisk å SENDE en chat-melding og et
|
||
hull-resultat); samme spiller fortsatt korrekt AVVIST fra det andre laget
|
||
sin chat; en helt fremmed innlogget bruker (ingen spillerkobling i org-en
|
||
i det hele tatt) avvist overalt; org-eier beholder full tilgang til alt
|
||
UNNTATT lag-chat (ekte privat, med vilje uendret — ADR-025); kryss-org-
|
||
isolasjon bekreftet (kan ikke nå egen turnering via en ANNEN org-id);
|
||
og — et reelt funn UNDER testingen, ikke en bug — en rostret-men-ikke-
|
||
utpekt-kaptein spiller ble først FEILAKTIG godtatt til walkover fordi
|
||
laget ennå ikke hadde noen utpekt kaptein (allerede dokumentert,
|
||
tiltenkt fallback i `user_is_team_captain`: "ingen kaptein ennå = enhver
|
||
rostret spiller godtas") — testen ble korrigert (la til en faktisk
|
||
kaptein) og bekreftet deretter riktig avvisning av ikke-kapteinen.
|
||
`test_isolation.sql` fortsatt 12/12 (ingen skjemaendring). Ekte
|
||
typesjekket produksjonsbuild av frontend 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.
|
||
|
||
---
|
||
|
||
## HCP-historikk over tid — ✅ BYGGET OG LIVE 2026-07-21
|
||
|
||
Siste av de tre konkrete følgepunktene brukeren bekreftet i dashboard/
|
||
konto-runden (2026-07-21) — ADR-031s "naturlig neste steg"-punkt: den
|
||
personlige profilens `app_user.handicap_index` endres i dag stille ved
|
||
hver `PATCH /auth/profile`, uten noen logg over tidligere verdier.
|
||
|
||
**Migrasjon `018_handicap_history.sql`:** ny append-only-tabell
|
||
`handicap_history` (`user_id`, `handicap_index`, `recorded_at`) — kun for
|
||
den PERSONLIGE profilens HCP, bevisst atskilt fra de org-scopede
|
||
`player.handicap_index`-radene og `team_roster.handicap_index_snapshot`
|
||
(som allerede har sitt eget reproduserbarhets-prinsipp, ADR-007, ikke rørt
|
||
her).
|
||
|
||
**Backend:** `update_profile` (`PATCH /auth/profile`) leser gjeldende HCP
|
||
FØR den overskrives, og logger en ny historikk-rad KUN når verdien faktisk
|
||
ENDRES til en tallverdi — ikke ved nullstilling (ingen "HCP fjernet"-
|
||
hendelse gir mening i en verdi-over-tid-logg), og ikke ved et PATCH som
|
||
gjentar samme verdi uendret (unngår støy fra en form som lagres på nytt
|
||
uten reell endring). Ny `GET /auth/profile/handicap-history`.
|
||
|
||
**Frontend:** en «Vis HCP-historikk»-lenke i `/account` sin
|
||
`ProfileSection`, ekspanderer til en dato+verdi-liste, hentes på nytt
|
||
automatisk rett etter en lagring.
|
||
|
||
**Scratch-verifisert, 18 sjekker:** tom historikk for en fersk bruker,
|
||
riktig logging ved første HCP-verdi, INGEN duplikat ved gjentatt lagring
|
||
av uendret verdi (selv sammen med en annen felt-endring i samme PATCH),
|
||
ny rad ved faktisk endring, kronologisk rekkefølge riktig, ingen logg ved
|
||
nullstilling, ny rad ved gjeninnsetting etter nullstilling, og full
|
||
isolasjon mellom to ulike brukeres historikk. `test_isolation.sql`
|
||
fortsatt 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 til lag-chat/scorekort,
|
||
sekundær e-postadresse (del 1), og HCP-historikk. Gjenstående, bevisst
|
||
utsatte punkter fra samme runde: dashbordets tom-tilstand-redesign
|
||
(venter på retning), frittstående rundeføring + statistikk (trenger egen
|
||
ADR), og konto-sammenslåing (del 2 av multi-e-post).
|
||
|
||
---
|
||
|
||
## Obligatorisk profil-fullføring ved innlogging — ✅ BYGGET OG LIVE 2026-07-22
|
||
|
||
Bygget som direkte svar på "hva skal møte en fersk bruker aller først"-
|
||
spørsmålet reist i tom-tilstand-diskusjonen under. Brukeren observerte selv
|
||
at en fersk konto (`hei@erol.no`, opprettet bevisst for å se førstegangs-
|
||
innloggingen) kun viste et tomt skall + opprett-organisasjon-skjermet, og
|
||
avklarte at riktig oppførsel er: **kontoinnstillinger/personlig profil skal
|
||
være det aller første som vises, og alt der (utenom bilde) skal være
|
||
obligatorisk**, før noe annet i appen (inkl. dashbordet) er tilgjengelig.
|
||
|
||
**Design:**
|
||
- Ny migrasjon `019_profile_country_bio.sql`: `app_user.country` +
|
||
`app_user.bio` (samme nullable-kolonne-mønster som resten av
|
||
ADR-031-profilen — "obligatorisk" håndheves i app-laget via et beregnet
|
||
`profile_complete`-felt på `/auth/me`, ikke som en DB `NOT NULL`).
|
||
- Obligatoriske felt: fornavn, etternavn, fødselsdato, kjønn, HCP,
|
||
hjemmeklubb, land. Valgfrie: beskrivelse, profilbilde.
|
||
- **HCP-grensetilfellet avklart eksplisitt med bruker før bygging** (via
|
||
AskUserQuestion): en fersk golfspiller har sjelden en offisiell HCP
|
||
ennå. Løsning: WHS-maksimum 54 er forhåndsutfylt i skjemaet som
|
||
utgangspunkt, og `handicap_index` har en hard `le=54`-validering i
|
||
`ProfileUpdate` (kan aldri registreres høyere) — ingen egen "har ikke
|
||
HCP ennå"-avkrysning trengtes.
|
||
- `/account` grener på `profile_complete`: ufullstendig → et nytt,
|
||
fokusert `ProfileOnboarding`-skjema (kun de obligatoriske feltene +
|
||
valgfri beskrivelse, «Logg ut» tilgjengelig, INGEN annen navigasjon) —
|
||
komplett → den vanlige innstillingssiden (nå med land+beskrivelse lagt
|
||
til i det ordinære profilskjemaet for redigering i etterkant, per
|
||
brukerens eget ønske: "Når dette er på plass kan informasjonen heller
|
||
kunne redigeres i 'Konto'-visningen").
|
||
- `app/page.tsx` (rot) 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` uansett hvilken flyt som ble brukt, som selv
|
||
gjør sjekken ved mount, så ingen av de tre separate login-komponentene
|
||
måtte endres).
|
||
- **Bevisst avgrenset:** gaten håndheves kun ved disse to inngangspunktene,
|
||
ikke ved dypere direktelenker til andre autentiserte sider (f.eks. en
|
||
bokmerket turnering-URL) — samme skope-disiplin som tidligere runder.
|
||
|
||
**Verifisert:** se full detalj i CHANGELOG.md 2026-07-22 — 16/16
|
||
scratch-backend-sjekker, `test_isolation.sql` 12/12, ekte typesjekket
|
||
produksjonsbuild, og et ekte HTTP-nivå-bevis mot en kjørende
|
||
produksjonscontainer (anonym → 200 innloggingsskjema, ekte innlogget-men-
|
||
ufullstendig sesjonscookie → `307 → /account`). Rullet ut mot ekte
|
||
`teecup_db`/containere, bruker bekreftet eksplisitt.
|
||
|
||
**Kjent, tilsiktet konsekvens:** brukerens BEGGE egne kontoer
|
||
(`erol.haagenrud@envide.no` og `hei@erol.no`) manglet alle disse feltene
|
||
og vil derfor begge se profil-fullførings-skjemaet ved neste innlogging.
|
||
|
||
---
|
||
|
||
## Dashboard: tom-tilstand ved første innlogging — ✅ HELT FERDIG (bygget som ADR-035, live 2026-07-25)
|
||
|
||
**Bygget som beskrevet i "2026-07-25"-forslaget under** (de sju blokkene:
|
||
Hurtighandlinger/Kommende runder/Kommende turneringer/Statistikk/Spilte
|
||
baner/Venner/Organisasjoner) — se ADR-035 i ARCHITECTURE_DECISIONS.md og
|
||
CHANGELOG.md 2026-07-25 for full bygge-/verifiseringsdetalj.
|
||
Organisasjon er ikke lenger første/eneste synlige vei inn; opprettes nå
|
||
usynlig i bakgrunnen når en bruker trykker "Ny turnering".
|
||
|
||
Brukeren påpekte 2026-07-20 at dagens tomme-tilstand ("Du har ingen
|
||
organisasjon ennå — opprett en") er organisator-vridd og ikke stemmer med
|
||
hva en fersk bruker faktisk trenger å se/gjøre. Et opprinnelig forslag
|
||
(utvid "Mine runder" til påmeldinger + nøytral to-valgs tom-skjerm, se
|
||
historikk under) ble lagt frem 2026-07-20 — brukeren ba 2026-07-21
|
||
eksplisitt om å justere retningen i lys av en dypere refleksjon, se under.
|
||
|
||
**2026-07-21 — premisset er endret, ikke bare forslaget:** brukeren stilte
|
||
selv spørsmålet om organisasjon fortsatt bør være "det som meldes først" —
|
||
gitt ADR-031 (Mine runder), ADR-032 (e-post/mobil som personlig identitet)
|
||
og det nye ønsket om frittstående rundeføring med statistikk (se egen
|
||
seksjon rett under), er en vanlig bruker først og fremst en GOLFSPILLER,
|
||
og det å arrangere turneringer er én av flere ting en spiller *kan* gjøre —
|
||
ikke forutsetningen for å bruke appen i det hele tatt. **Vurdering: ja,
|
||
organisasjon bør slutte å være default/første-handling**, og bli ett
|
||
likestilt valg blant flere fremtidige "første ting du kan gjøre"
|
||
(bli med i en turnering via kode, registrere en runde selv, ELLER
|
||
arrangere/opprette organisasjon) — ikke lenger den ENESTE synlige veien
|
||
inn.
|
||
|
||
**2026-07-22 — delvis besvart, ikke fullt løst:** brukeren avklarte at
|
||
det ALLER første en innlogget bruker med en ufullstendig personlig profil
|
||
skal se, er en obligatorisk «Fullfør profilen din»-visning (fornavn/
|
||
etternavn/fødselsdato/kjønn/HCP/hjemmeklubb/land — alt utenom bilde og
|
||
beskrivelse) — se CHANGELOG.md, ✅ BYGGET OG LIVE. Dette svarer på
|
||
"hva møter en fersk bruker aller først", men IKKE på det opprinnelige
|
||
spørsmålet i denne seksjonen: hva skal dashbordets tom-tilstand vise for
|
||
en bruker som HAR fullført profilen, men ennå ikke har noen organisasjon/
|
||
turnering å vise? Den vurderingen (organisasjon bør slutte å være
|
||
default/første-handling) står fortsatt ved lag og er fortsatt IKKE bygget.
|
||
|
||
**Konsekvens for byggerekkefølgen:** selve tom-skjerm-redesignet er satt
|
||
PÅ VENT til frittstående runder (under) er avklart nok til å vite hvilken
|
||
tredje kortform den skal ha på tom-skjermen — å bygge en to-valgs versjon
|
||
nå og redesigne den på nytt om kort tid ville vært dobbeltarbeid. Punktet
|
||
"utvid Mine runder til rene påmeldinger" (fra 2026-07-20-forslaget)
|
||
henger IKKE sammen med denne avhengigheten og kan bygges uavhengig når som
|
||
helst — fortsatt et åpent, godt avgrenset TODO.
|
||
|
||
**Opprinnelig forslag (2026-07-20), for historikkens skyld:**
|
||
1. Utvid "Mine runder" til også å vise rene påmeldinger (ikke bare
|
||
rostrede lag).
|
||
2. Gjør selve tom-skjermen nøytral: to likestilte valg side ved side —
|
||
"Har du en kode?" og "Skal du arrangere selv? Opprett organisasjon".
|
||
|
||
**Venter på:** en videre avklaring av frittstående runder (under) før
|
||
tom-skjermens endelige form kan bestemmes.
|
||
|
||
---
|
||
|
||
### 2026-07-25 — avhengigheten er løst, konkret forslag lagt frem — ✅ BYGGET (RETTELSE 2026-08-14)
|
||
|
||
**RETTELSE 2026-08-14 — "📋 DESIGNET, IKKE BYGGET" var stale.** Blokk-
|
||
forslaget under (Hurtighandlinger/Kommende runder/Kommende turneringer/
|
||
Statistikk/Spilte baner/Venner/Organisasjoner) er bygget nesten ordrett —
|
||
bekreftet direkte i `frontend/components/dashboard.tsx`, som har egne
|
||
seksjoner for nøyaktig disse syv blokkene i samme rekkefølge (bygget som
|
||
del av ADR-035, samme dag forslaget ble skrevet). Denne seksjonens egen
|
||
tidsstempel ga inntrykk av at forslaget fortsatt lå ubygget da resten av
|
||
filen (linje ~2830) allerede sa "✅ HELT FERDIG (bygget som ADR-035, live
|
||
2026-07-25)" — samme dags rekkefølge i filen var bare forvirrende, ikke
|
||
en reell uenighet.
|
||
|
||
Frittstående runder (ADR-033) er nå ferdig bygget (backend+frontend, alle
|
||
oppfølgingsrunder), så blokkeringen over er borte. Brukeren reiste samtidig
|
||
det dypere spørsmålet "hvorfor har vi organisasjon i det hele tatt" —
|
||
besvart og designet som **ADR-035** (organisasjon beholdes, men
|
||
opprettelsen gjøres usynlig/automatisk — Beslutning B, se
|
||
ARCHITECTURE_DECISIONS.md for full A-vs-B-avveining og
|
||
reversibilitetsvurdering).
|
||
|
||
**Konkret blokk-forslag for det nye dashbordet** (rekkefølge, topp til
|
||
bunn):
|
||
|
||
1. **Hurtighandlinger** — tre likestilte kort/knapper: "Ny runde", "Ny
|
||
turnering" (oppretter/gjenbruker organisasjon usynlig, ADR-035), "Bli
|
||
med med kode" (ADR-020). Ingen av de tre skal kreve noe org-steg
|
||
synlig for brukeren.
|
||
2. **Kommende runder** — egne runder som ikke er fullført
|
||
(`completed_at IS NULL`), sortert på dato, inntil 3 vist + "se alle"
|
||
til `/my-rounds`. Tomtilstand: kort tekst + snarvei til "Ny runde".
|
||
3. **Kommende turneringer** — SLÅR SAMMEN turneringer man er deltaker i
|
||
(dagens "Mine runder"-data) OG turneringer man arrangerer (dagens
|
||
organisasjons-turneringsliste, på tvers av ALLE organisasjoner
|
||
brukeren er medlem i, flatt) til ÉN tidssortert liste. Kort man
|
||
arrangerer får en liten "Arrangør"-merkelapp. Tomtilstand: snarvei til
|
||
"Ny turnering"/"Bli med med kode".
|
||
4. **Statistikk** — smått aggregert: antall runder spilt, HCP-trend
|
||
(sparkline fra den eksisterende `handicap_history`-tabellen, kun vist
|
||
ved ≥2 datapunkter), snitt til par siste 5 runder. Tomtilstand til
|
||
minst én runde er fullført.
|
||
5. **Spilte baner** — utledet fra `round.course_name_snapshot`, gruppert
|
||
med besøksantall + sist spilt. Ingen ny datamodell trengs.
|
||
6. **Venner** — kort med antall ventende forespørsler + snarvei til en ny
|
||
`/friends`-side (se ADR-036 under). Tomtilstand: "Du har ingen venner
|
||
ennå — søk etter noen".
|
||
7. **Organisasjoner** — KUN vist hvis brukeren er medlem i mer enn den
|
||
auto-opprettede sin egen (dvs. har en ekte, navngitt klubb-tilknytning)
|
||
— én liten, nedtonet lenke, ikke en egen fremtredende seksjon. Dette er
|
||
selve poenget med ADR-035: organisasjon skal ikke lenger dominere
|
||
dashbordet.
|
||
|
||
**V0-prompt skrevet, IKKE sendt til V0 ennå** (venter på brukerens
|
||
gjennomgang av blokk-forslaget over først):
|
||
|
||
> Design et nytt dashbord for TeeCup (golf-app, Next.js + Tailwind +
|
||
> shadcn/ui, mobil-først, eksisterende merkevarefarger: grønn primær,
|
||
> oransje sekundær — bruk appens eksisterende design-tokens, ikke nye
|
||
> farger). Dette ERSTATTER dagens dashbord, som feilaktig satte
|
||
> "organisasjon" som det første og viktigste en bruker møtte — ny
|
||
> retning: brukeren er først og fremst en GOLFSPILLER, organisasjon er en
|
||
> liten, valgfri detalj lengre ned.
|
||
>
|
||
> Innhold, i denne rekkefølgen, som distinkte kort/seksjoner (ikke faner):
|
||
> 1. Hurtighandlinger: tre like store, likestilte knapper/kort side ved
|
||
> side (stables på smal skjerm) — "Ny runde", "Ny turnering", "Bli med
|
||
> med kode". Tydelige, tekstede (ikke kun ikon), store trykkflater.
|
||
> 2. "Kommende runder" — liste over inntil 3 pågående/ikke-fullførte
|
||
> runder (banenavn eller eget rundenavn, dato, en liten
|
||
> fremdriftsindikator "X/18 hull"), med en "se alle"-lenke. Vis en
|
||
> tydelig, vennlig tomtilstand med snarvei til "Ny runde" hvis ingen.
|
||
> 3. "Kommende turneringer" — liste over turneringer brukeren enten
|
||
> deltar i eller arrangerer, sortert på dato, en liten
|
||
> "Arrangør"-merkelapp på kort man selv arrangerer. Tomtilstand med
|
||
> snarveier til "Ny turnering"/"Bli med med kode".
|
||
> 4. "Statistikk" — en kompakt rad med 2-3 nøkkeltall (antall runder,
|
||
> HCP nå + en liten trendpil/sparkline, snitt til par) i staselige
|
||
> tall-fliser (stort, fet skrift, høy kontrast).
|
||
> 5. "Spilte baner" — en kompakt liste/rutenett av baner med
|
||
> besøksantall og sist spilt-dato.
|
||
> 6. "Venner" — et lite kort: avatar-stabel av noen få venner (om noen),
|
||
> et tall-merke for ventende forespørsler, snarvei "Se venner".
|
||
> Tomtilstand: oppfordring til å søke opp noen.
|
||
> 7. "Organisasjoner" — KUN når relevant: én liten, nedtonet tekstlenke
|
||
> nederst, IKKE et fremtredende kort — dette skal se ut som en
|
||
> bakgrunnsdetalj, ikke en hovedseksjon.
|
||
>
|
||
> Tilgjengelighet er et ufravikelig krav, ikke en estetisk sluttpuss: god
|
||
> kontrast, stor nok skrift, store trykkflater (min. 44px), ALDRI
|
||
> ikon-only uten tekstlabel på viktige handlinger, appen skal være
|
||
> brukbar uten finmotorikk eller skarpt syn. Design ALLE tomtilstander
|
||
> eksplisitt, ikke bare den fylte varianten — de fleste nye brukere vil
|
||
> se flere tomme blokker samtidig, og det skal fortsatt se innbydende ut,
|
||
> ikke ufullstendig.
|
||
|
||
**Åpne spørsmål før bygging:**
|
||
- Skal blokk 3 og 4 lenke til nye, dedikerte sider (f.eks. en egen
|
||
aggregert statistikk-side), eller er de rene dashbord-widgets uten
|
||
"se mer"? Statistikk-tallene over er enkle å beregne, men en FULL
|
||
aggregert statistikk-side (grafer over tid på tvers av alle runder) er
|
||
et større, eget stykke arbeid — ikke inkludert i dette forslaget.
|
||
- Nøyaktig terskel for når "Organisasjoner"-lenken vises (mer enn 1 org
|
||
totalt? Eller kun når minst én org har et eksplisitt satt `slug`/
|
||
`public_profile`, dvs. faktisk er gjort til en "ekte" klubb?).
|
||
|
||
---
|
||
|
||
## Venner, kategorisert deling av runder, og tiered personsøk — ✅ HELT FERDIG, alle tre faser bygget og live (fase 1: 2026-07-25, fase 2 rundevisibilitet: 2026-07-28, fase 3 delvis: 2026-07-26)
|
||
|
||
Reist av brukeren samme runde som dashbord-forslaget over. Fullt design
|
||
skrevet som **ADR-036** i ARCHITECTURE_DECISIONS.md — se der for
|
||
datamodell, autorisasjonslogikk og søke-algoritme i detalj. Kort
|
||
oppsummert her, pluss åpne spørsmål og foreslått byggerekkefølge.
|
||
|
||
**Tre sammenhengende deler:**
|
||
1. Et ekte vennekonsept — gjensidig forespørsel/aksept (som org-
|
||
invitasjoner), pluss et fast sett kategorier en venn kan settes i
|
||
(flere samtidig): Make, Nær familie, Storfamilie, Nære venner,
|
||
Golfvenner, Kollegaer, Forretningsforbindelser, Studiekamerater,
|
||
Perifere bekjente, Ymse. **Kategoriseringen er privat** — vennen vet
|
||
ikke hvilken/hvilke grupper du har satt dem i.
|
||
2. Rundevisibilitet — ny `round.visibility_mode`
|
||
(`public`/`private`/`friends`, default `private`). Ved `friends`
|
||
velger man EKSPLISITT hvilke av gruppene sine som får se runden (ikke
|
||
"alle venner"). Styrer kun tredjeparts innsyn — en faktisk lagt-til
|
||
medspiller ser alltid runden uansett innstilling.
|
||
3. Tiered personsøk — samme søkbare-liste-mønster som bane-/klubbsøket
|
||
(`HomeClubField`/`OfficialSearchStep`), navnerekkefølge-uavhengig
|
||
(skriv for- ELLER etternavn først, begge treffer), rangert: venner →
|
||
samme hjemmeklubb → samme land → globalt. Ett delt endepunkt brukt
|
||
BÅDE til "finn en venn" og (i en senere fase) "legg til medspiller på
|
||
en runde".
|
||
|
||
**Reell synergi funnet under design, ikke tilfeldig:** forrige rundes
|
||
redesign av "Hjemmeklubb" til en ekte dropdown-verdi (i stedet for
|
||
fritekst) gjør nå "samme klubb"-rangeringen i søket pålitelig — et
|
||
eksakt strengmatch fungerer nå, noe det ikke ville gjort med den gamle
|
||
fritekst-versjonen.
|
||
|
||
**Byggerekkefølge, alle tre uavhengig leverbare faser — ✅ ALLE FERDIG:**
|
||
1. **✅ Venner-kjernen** (vennskap, kategorisering, `/people/search`, ny
|
||
`/friends`-side) — live 2026-07-25, se "Fase 1"-underseksjon under.
|
||
2. **✅ Rundevisibilitet** (visibility-valg ved rundestart, gatede
|
||
lese-endepunkter, sanntids-livevisning for venner/offentlighet) —
|
||
bygget, scratch-/browserverifisert og live 2026-07-28
|
||
(`round.visibility_mode`, migrasjon 032, `_can_view_round`/nye
|
||
`/public/rounds/*`-endepunkter, ny `/watch/[id]`-side og
|
||
`/my-friends/[id]`-profilside). Se CHANGELOG.md 2026-07-28 for
|
||
full detalj (191 automatiserte sjekker + full nettleser-gjennomgang
|
||
med tre reelle nettleserkontekster).
|
||
3. **✅ Ekte medspillere** (ikke bare gjester) — utvider
|
||
`POST /rounds/{id}/participants` til å godta et søkt `user_id`.
|
||
Bygget 2026-07-26 (se egen "Fase 3 (delvis)"-underseksjon under
|
||
Fase 1) — søk+legg til + "skriv for hele flighten" er live.
|
||
`list_rounds`/`RoundOut` sine viewer-relative `my_*`-felt (se under)
|
||
er del av denne leveransen.
|
||
Sletting av runden og bane-/utslagsbytte forblir eier-eksklusivt
|
||
(bekreftet, se ADR-036).
|
||
|
||
**Andre åpne spørsmål (se ADR-036 for full drøfting):**
|
||
- Skal en bruker kunne gjøre seg "usøkbar"? Foreslått default: alle med
|
||
fullført profil er søkbare, ingen opt-out i første omgang.
|
||
- Minimum antall tegn før søket returnerer globale (ikke-venn/klubb/land)
|
||
treff — foreslått 2, for å hindre triviell enumerering av alle
|
||
brukere.
|
||
- Skal en "offentlig" runde være synlig for ANONYME besøkende (som
|
||
turneringers offentlige side), eller kreve innlogging? Foreslått:
|
||
samme mønster som turnering — anonymt tilgjengelig.
|
||
|
||
**Ingen kode skrevet ennå** — dette var en design-/dokumentasjonsrunde,
|
||
ikke en byggerunde, på brukerens eksplisitte instruks.
|
||
|
||
---
|
||
|
||
### Fase 1 (venner-kjernen) — ✅ HELT FERDIG, backend+frontend rullet ut live 2026-07-25
|
||
|
||
Migrasjon `025_friends.sql` (`friendship` + `friend_categorization`, ingen
|
||
RLS — samme `plain_connection()`-mønster som runder), nytt
|
||
`app/routers/friends.py`: `GET /people/search` (tiered, navnerekkefølge-
|
||
uavhengig, min. 2 tegn), `POST /friends` (send forespørsel),
|
||
`POST /friends/{id}/accept`, `DELETE /friends/{id}` (avslå/kanseller/
|
||
avvenn — én operasjon dekker alle tre), `GET /friends` (venner +
|
||
inn-/utgående forespørsler), `PUT /friends/{friend_user_id}/categories`
|
||
(erstatter hele settet, krever akseptert vennskap).
|
||
|
||
**Verifisert grundig i scratch** (isolert `teecup_app_scratch`-rolle +
|
||
isolert scratch-MinIO + engangs API-container, 5 syntetiske brukere): 31
|
||
sjekker — navnerekkefølge-uavhengig søk begge veier, selv-ekskludering,
|
||
anti-enumerering under 2 tegn, dupliserte forespørsler avvist i BEGGE
|
||
retninger, kun mottaker kan akseptere, tiered rangering bekreftet presist
|
||
(venn → samme klubb → samme land → globalt, i RIKTIG rekkefølge for 4
|
||
distinkte brukere samtidig), kategorisering avvist for ikke-venn,
|
||
kategorisering BEKREFTET PRIVAT (B ser ALDRI kategoriene A har satt B i),
|
||
uvedkommende kan ikke slette andres vennskap, avvenning + ny forespørsel
|
||
etterpå fungerer. `test_isolation.sql` fortsatt 12/12. Ekte typesjekket
|
||
produksjonsbuild kompilerte rent (ingen frontend-endring ennå utover
|
||
`next.config.mjs` sine nye rewrites for `/people`/`/friends`).
|
||
|
||
**Frontend BYGGET 2026-07-25, samme dag** — zip 19 mottatt (V0-prompten
|
||
under kjørt av bruker), integrert som `components/friends.tsx` + ny rute
|
||
`app/my-friends/page.tsx` (IKKE `app/friends/page.tsx` slik V0 selv
|
||
foreslo — flyttet bevisst for å unngå kollisjon med API-prefikset
|
||
`/friends`, samme klasse feil som `/rounds` vs. `/my-rounds` tidligere,
|
||
denne gangen unngått fra start). Datalag skrevet om fra mock til ekte
|
||
fetch, kategori-koder mappet mot norske visningsnavn i samme rekkefølge
|
||
som backend. Dashbordets "Venner"-blokk (`dashboard.tsx`) koblet til
|
||
ekte `GET /friends`-data i samme runde — viser nå ekte antall
|
||
venner/ventende forespørsler, lenker til `/my-friends`. Ekte
|
||
typesjekket produksjonsbuild kompilerte rent.
|
||
|
||
**V0-prompten som ble brukt (til referanse):**
|
||
|
||
> Design en ny "Mine venner"-side for TeeCup (golf-app, Next.js +
|
||
> Tailwind + shadcn/ui, mobil-først, eksisterende merkevarefarger).
|
||
>
|
||
> Innhold:
|
||
> 1. Et søkefelt øverst ("Søk etter navn …") — søkbar liste som filtrerer
|
||
> live mens man skriver (samme mønster som bane-/klubbsøket ellers i
|
||
> appen), viser navn + evt. hjemmeklubb per treff, med en
|
||
> "Send forespørsel"-knapp per rad.
|
||
> 2. En "Forespørsler"-seksjon (kun synlig når det finnes noen): to
|
||
> undergrupper — "Mottatt" (med "Godta"/"Avslå"-knapper per rad) og
|
||
> "Sendt" (med en "Kanseller"-knapp per rad, og tekst som "Venter på
|
||
> svar").
|
||
> 3. En "Venner"-liste — hver rad har navn, avatar (initialer som
|
||
> fallback), hjemmeklubb, og en utvidbar kategori-velger: ti faste
|
||
> avkrysningsbare kategorier (Make, Nær familie, Storfamilie, Nære
|
||
> venner, Golfvenner, Kollegaer, Forretningsforbindelser,
|
||
> Studiekamerater, Perifere bekjente, Ymse) — flere kan velges
|
||
> samtidig for samme venn. Vis tydelig, med liten tekst under
|
||
> kategori-velgeren: "Kun du ser hvilke kategorier du har satt en venn
|
||
> i." En "Fjern venn"-knapp med bekreftelsessteg.
|
||
> 4. Tomtilstander for alle tre seksjonene (ingen venner ennå, ingen
|
||
> forespørsler).
|
||
>
|
||
> Tilgjengelighet er et ufravikelig krav: god kontrast, stor nok skrift,
|
||
> store trykkflater (min. 44px), aldri ikon-only uten tekstlabel på
|
||
> viktige handlinger.
|
||
|
||
---
|
||
|
||
### Fase 3 (delvis): søk+legg til medspiller, skriv for hele flighten — ✅ BYGGET OG SCRATCH-VERIFISERT 2026-07-26
|
||
|
||
Utløst av at brukeren rapporterte at "+ Gjest"-skjemaet på en frittstående
|
||
runde ikke søkte etter spillere — bekreftet reelt (rent tekstfelt, ingen
|
||
søk koblet på ennå). Bygget den etterspurte delen av fase 3 (søk-og-
|
||
legg-til + full skrivetilgang for medspillere), IKKE rundevisibilitet
|
||
(fase 2, fortsatt egen, senere runde).
|
||
|
||
**Backend** (`app/routers/rounds.py`): `POST /rounds/{id}/participants`
|
||
tar nå ENTEN `user_id` (funnet via `/people/search`, samme tiered
|
||
algoritme som vennesøket) ELLER `guest_name` (uendret fallback for
|
||
spillere uten konto) — kjønn/HCP hentes automatisk fra den valgte
|
||
personens EGEN profil ved `user_id`, ikke tastet manuelt. Ny migrasjon
|
||
`027_round_participant_user_unique.sql` (partiell unik indeks, hindrer
|
||
dobbel-lenking av samme bruker). Ny `_get_accessible_round_or_404`
|
||
(eier ELLER lenket medspiller) brukt 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 (bekreftet nødvendig allerede
|
||
2026-07-25). Ny `display_name` på `RoundParticipantOut` (levende
|
||
oppslått, ikke snapshot) — fanget og fikset en reell latent bug i
|
||
leaderboard-endepunktet (bygget samme dag) som ville vist blankt navn
|
||
for enhver lenket medspiller.
|
||
|
||
**Frontend** (`round-detail.tsx` + samme fiks portert til `round-stats.tsx`/
|
||
`round-scorecard.tsx`): "+ Gjest" omdøpt til "+ Medspiller", nytt
|
||
søk-som-du-skriver-felt (debounce, avatar-initialer, hjemmeklubb) med en
|
||
"Legg til uten konto"-fallback til det gamle tekstfeltet. Viewer-relativ
|
||
"Deg"-visning (krevde en `/auth/me`-utvidelse for å kjenne den
|
||
innloggede brukerens egen id) — en lenket medspiller ser nå seg selv som
|
||
"Deg" og eieren under sitt eget navn, ikke omvendt. Rediger/slett-runde
|
||
og legg til/fjern-medspiller-knappene skjules nå for en ikke-eier-viewer
|
||
(backend avviser uansett, men UI-et bør ikke vise handlinger som bare
|
||
feiler).
|
||
|
||
**Scratch-verifisert grundig, 39/39 sjekker i to testløp:** søk-og-
|
||
legg-til (kjønn/HCP auto-fylt fra profil), avvist duplikat/selv-tillegg/
|
||
ukjent bruker/ufullstendig profil (mangler kjønn), lenket medspiller kan
|
||
lese runden + registrere score for BÅDE egen OG andres rad (whole-
|
||
flight-regelen), men nektes å forvalte runden (rediger/slett/legge
|
||
til/fjerne/endre stat_level — alle 403), lenket medspiller KAN fullføre
|
||
runden, `/rounds`-listen viser nå runden for en lenket medspiller (med
|
||
DERES egen fremdrift, ikke eierens), en helt urelatert bruker fortsatt
|
||
403/ikke i listen, leaderboardets navn stemmer for alle tre
|
||
deltakertyper. `test_isolation.sql` 12/12 uendret. Ekte typesjekket
|
||
produksjonsbuild kompilerte rent.
|
||
|
||
### Oppfølging samme dag: sanntid — ✅ BYGGET OG SCRATCH-VERIFISERT 2026-07-26
|
||
|
||
Brukeren spurte om spillere ser i sanntid at en annen registrerer en
|
||
score — svaret var nei, og brukeren ba om at det bygges. Gjenbrukte det
|
||
etablerte WebSocket-mønsteret fra ADR-027 ("Følg live" for turneringer):
|
||
et rent "noe endret seg"-signal, klienten reagerer med de vanlige REST-
|
||
kallene. Nytt `/ws/rounds/{id}/live` i `rounds.py`, ALDRI anonym tilgang
|
||
(krever eier eller lenket medspiller), kringkasting lagt inn i alle
|
||
skrivende rundeendepunkter. Se ARCHITECTURE_DECISIONS.md/CLAUDE.md for
|
||
full detalj, inkl. et reelt Starlette-funn (alle avvisningskoder
|
||
kollapser til HTTP 403 i selve håndtrykket — sikkerheten er upåvirket).
|
||
Scratch-verifisert med en EKTE WebSocket-klient (10/10 sjekker) — ekte
|
||
kringkasting bekreftet begge veier, ikke bare REST-svar.
|
||
|
||
---
|
||
|
||
## Turneringsoppsett: flytte spillere mellom lag — ✅ BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28
|
||
|
||
Reist av brukeren 2026-07-25 samme runde som dashbord-integreringen. Løst
|
||
akkurat slik forrige runde antok tryggest: 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 = ...` i én operasjon, ingen
|
||
mellomtilstand. Avviser med 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` — å flytte
|
||
den ville gjort matchens `team_side` inkonsistent med spillerens
|
||
faktiske lag), avviser 400 ved flytting til samme lag, 404 ved ukjent
|
||
mållag/roster-id. Kapteinmerket følger IKKE med til det nye laget
|
||
(nullstilles eksplisitt ved flytting). 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.
|
||
Scratch-verifisert (happy path, kaptein-nullstilling, 409/400/404 alle
|
||
bekreftet) OG bekreftet direkte i en ekte innlogget nettleser (Chrome
|
||
DevTools): flytting oppdaterte begge lags roster-lister og -antall
|
||
umiddelbart, ingen konsollfeil. Ingen migrasjon. Se CHANGELOG.md
|
||
2026-07-28.
|
||
|
||
---
|
||
|
||
## Frittstående runder: flere flighter i én "vanlig" runde — ✅ BYGGET OG LIVE 2026-07-28 (retning 1, løs gruppering)
|
||
|
||
Reist av brukeren samme runde: eksempel gitt — "jeg går i den første
|
||
flighten sammen med tre andre, mens fire venner går i flighten bak."
|
||
Dagens datamodell (`round`, ADR-033 Beslutning A) er implisitt ÉN flight
|
||
= ÉN runde: `round.owner_user_id` er entydig, og alle
|
||
`round_participant`-rader (eier + gjester/medspillere) spiller sammen i
|
||
SAMME flight på SAMME hull-for-hull-registrering.
|
||
|
||
**Reell modelleringsspenning, ikke bare en UI-mangel:** å støtte flere
|
||
flighter "i samme runde" krever et valg mellom to prinsipielt ulike
|
||
retninger:
|
||
1. **Løs gruppering av flere separate `round`-rader** — hver flight er
|
||
fortsatt sin egen `round` (egen eier, egne deltakere, egen
|
||
hull-registrering, uendret datamodell), men et nytt, tynt
|
||
"delt arrangement"-konsept binder flere runder sammen visuelt (samme
|
||
dag, samme bane, "spilt sammen med disse flightene") — minst
|
||
invasivt, gjenbruker alt som allerede er bygget og scratch-verifisert.
|
||
2. **`round` blir en beholder for flere flighter** — ligner
|
||
turneringers `session`→`match`-struktur (én økt, flere matcher).
|
||
Større omskriving: `round_participant` må da vite hvilken flight den
|
||
hører til, og eierskap/autorisasjon (i dag: "eieren av runden ser/
|
||
redigerer alt") må revurderes for en modell med flere selvstendige
|
||
flighter under samme paraply.
|
||
|
||
**Ikke besluttet hvilken retning** — kun notert som et reelt, ikke-trivielt
|
||
spørsmål. Henger dessuten sammen med det pågående ADR-036-arbeidet
|
||
(medspillere/venner) — en avklaring bør trolig vente til minst fase 1 av
|
||
ADR-036 er bygget, siden "hvem er i min flight" og "hvem er min venn/
|
||
medspiller" er beslektede, men ikke identiske spørsmål.
|
||
|
||
### Presisert 2026-07-26 (fortsatt IKKE besluttet, kun tydeligere)
|
||
|
||
Brukeren presiserte tre konkrete ting ved oppfølging:
|
||
|
||
1. **Oppsettflyten er ÉN handling utført av ÉN person.** "Når jeg setter
|
||
opp en vanlig runde kan jeg sette opp for bare meg, for inntil 3 andre
|
||
i samme flight, eller for flere flighter." Det er brukeren selv som
|
||
tar ansvar for å sette opp ALLE flightene (også vennenes, i eksemplet)
|
||
— ikke at hver flight settes opp uavhengig av sine egne deltakere.
|
||
Speiler dagens gjest-mønster (eieren legger til gjester), bare
|
||
utvidet til å dekke flere adskilte flight-grupper i samme handling.
|
||
2. **Fremtidig, ikke motstridende:** "det er selvfølgelig de i flightene
|
||
bak som må føre sin egen score" — bekrefter at ansvaret for å SETTE
|
||
OPP og ansvaret for å REGISTRERE SCORE er to forskjellige ting, og at
|
||
det andre (scoreregistrering per flight) forventes løst av ekte
|
||
medspillere med konto (ADR-036 fase 3), ikke av oppsetteren manuelt.
|
||
3. **Leaderboard-omfanget er PRESIST det som ble satt opp SAMMEN, ikke
|
||
"alle som spilte samme bane samme dag":** brukerens eget eksempel —
|
||
setter Alice opp en flight med fire, og vennene deres setter opp en
|
||
HELT ANNEN flight med fire (uavhengig av Alices oppsett), skal disse
|
||
to leaderboardene IKKE slås sammen. Kun flightene som ble satt opp
|
||
SAMMEN i én handling deler leaderboard.
|
||
|
||
**Konsekvens for de to retningene:** punkt 3 er et sterkt signal FOR
|
||
retning 1 (løs gruppering av separate `round`-rader, bundet sammen av et
|
||
tynt "satt opp sammen"-konsept som samtidig er leaderboardets naturlige
|
||
omfang) — retning 2 (én `round` som beholder) ville krevd en ekstra
|
||
mekanisme for AKKURAT denne avgrensningen uansett, siden "alle som
|
||
spilte samme bane samme dag" aldri var riktig omfang i utgangspunktet.
|
||
**Fortsatt IKKE en endelig beslutning** — brukeren utforsket/presiserte
|
||
forståelse, bekreftet ikke eksplisitt en byggeretning.
|
||
|
||
**Brukerens egen observasjon, viktig å fange presist:** "jeg ser veldig
|
||
godt at jeg opererer i grenseland mellom frittstående runde og
|
||
turneringer her." Dette pekte først mot en mulig forening med
|
||
"Individuelle turneringer, flerrunde-turneringer og Order of Merit" —
|
||
**men presisert og IKKE lenger antatt samme konsept, se ADR-037
|
||
(2026-07-26):** i en formell org-turnering er "flight" KUN en
|
||
tee-tid-gruppering, og leaderboardet spenner alltid HELE feltet
|
||
uavhengig av hvem som spilte sammen. Her, i den ad hoc frittstående
|
||
runden, skal leaderboardet AVGRENSES til nøyaktig det som ble satt opp
|
||
sammen. De to "flight"-begrepene ligner i UI (flere grupper spiller
|
||
samme dag), men er strukturelt ulike leaderboard-omfang — holdes derfor
|
||
bevisst ADSKILT (to separate, mindre systemer), ikke forent til ett.
|
||
Denne seksjonens egen retning (løs gruppering av separate `round`-rader,
|
||
se over) står fortsatt som anbefaling, uendret av presiseringen.
|
||
|
||
**Status: ✅ BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-07-28 --
|
||
retning 1 bygget nøyaktig som anbefalt.** Ny migrasjon
|
||
`035_round_flight_group.sql` -- `round.flight_group_id uuid` (nullable,
|
||
INGEN egen tabell/FK-mål -- bevisst kun en delt UUID-VERDI på tvers av
|
||
N `round`-rader, det tynneste mulige "satt opp sammen"-konseptet, se
|
||
moduldoc for begrunnelsen: leaderboardets omfang blir da AUTOMATISK
|
||
presist det som deler samme verdi, aldri "alle som spilte samme bane
|
||
samme dag"). Klient-generert (ikke server-generert) -- eieren genererer
|
||
`crypto.randomUUID()` FØRST gang en andre flight legges til (via en ny
|
||
`PATCH /rounds/{id}` med `flight_group_id`), gjenbruker samme verdi for
|
||
en tredje+ flight.
|
||
Nye endepunkter: `GET /rounds/{id}/flight-group` (søsken-flighter
|
||
brukeren selv har tilgang til -- rader man IKKE har tilgang til
|
||
avsløres aldri, verken navn eller eksistens) og
|
||
`GET /rounds/{id}/flight-group/leaderboard` (slår sammen hver
|
||
tilgjengelig søsken-flights EGET leaderboard, `_build_leaderboard()`
|
||
uendret, til én rangert liste tagget med `flight_label` -- `score_to_par`
|
||
er allerede normalisert mot par og dermed sammenlignbart på tvers av
|
||
ulike baner/hullantall).
|
||
**Frontend:** ny `FlightGroupPanel` i `round-detail.tsx` (sibling-liste +
|
||
"+ Legg til en flight til"-knapp, som PATCHer gruppe-id på anker-runden
|
||
ved behov og navigerer til `/my-rounds/new` med dato/starthull/
|
||
hullantall/spilleform forhåndsutfylt via URL-parametre -- banen velges på
|
||
nytt for hver flight, bevisst, samme lave friksjon som vanlig bane-søk).
|
||
Ny side `/my-rounds/[id]/flights` (`flight-group-leaderboard.tsx`) --
|
||
egen, lettere lokal variant av `round-leaderboard.tsx` sitt rangerings-/
|
||
mode-toggle-mønster (brutto/netto/poeng), rader lenker videre til hver
|
||
flights EGET fulle leaderboard for hull-for-hull-detalj.
|
||
**Scratch-verifisert** (del av et 27-punkts testløp samme dag som de tre
|
||
andre punktene): anker uten gruppe viser kun seg selv, PATCH setter
|
||
gruppe-id, ugyldig UUID avvist (400), begge flighter vises etter
|
||
gruppering, fremmed bruker nektes BÅDE anker-runden og det slåtte
|
||
leaderboardet (403/404, ingen lekkasje), slått-sammen leaderboard
|
||
rangerer korrekt på tvers av to flighter (−1 foran +2), `flight_label`
|
||
satt for alle rader.
|
||
**Browserverifisert, inkludert hele "+ Legg til en flight til"-flyten
|
||
FRA KNAPPETRYKK TIL FERDIG RUNDE** (ikke bare API-kall): klikket
|
||
knappen på en gruppeløs runde, bekreftet riktig `flightGroupId`/
|
||
`playedAt`/`startHole`/`holes`/`playFormat` i URL-en, bekreftet
|
||
forhåndsutfylt-men-redigerbart skjema (inkl. synlig forklarende banner),
|
||
fullførte oppretting, bekreftet DIREKTE I DATABASEN at begge rundene nå
|
||
delte samme `flight_group_id`. Kombinert leaderboard-siden bekreftet
|
||
visuelt (skjermbilde) med korrekt rangering/farger, ingen konsollfeil
|
||
noe sted i hele flyten.
|
||
**Rullet ut live 2026-07-28**, se CHANGELOG.md for felles
|
||
utrullingsdetalj med de tre andre punktene samme dag.
|
||
|
||
---
|
||
|
||
## Frittstående rundeføring + detaljert statistikk (uten turnering/organisasjon) — ✅ BACKEND + FRONTEND LIVE, løpende oppfølging t.o.m. 2026-07-28 (se ADR-033/ADR-038 for full detalj per dag)
|
||
|
||
**Oppdatering 2026-07-28 (ADR-038):** en full gjennomgang av eksisterende
|
||
funksjonalitet mot .md-filene avdekket at hele WHS-indeksmotoren i
|
||
`handicap_engine.py` (bygget/testet under ADR-033) aldri ble kalt fra
|
||
noe API-endepunkt — score-differensialer ble lagret per runde, men
|
||
`app_user.handicap_index` endret seg kun manuelt. Bygget, scratch-
|
||
verifisert (111/111) og rullet ut live: to atskilte HCP-tall (manuelt
|
||
vs. faktisk/beregnet), en eksplisitt "overfør til manuelt HCP"-handling,
|
||
en manuell eksklusjons-toggel per deltaker (kontrollert av HVER
|
||
innlogget deltaker for sin egen rad), og selvdeklarert spilleform
|
||
(slagspill/matchspill) med en anbefalt-men-overstyrbar eksklusjon for
|
||
matchspill. Se CHANGELOG.md 2026-07-28 for full detalj (migrasjon
|
||
`030`, backend/frontend-endringer, verifisering).
|
||
**Fortsatt IKKE tettet, notert samme runde:** offline-kø kun for
|
||
turnering-scorekortet (ikke frittstående runder), ingen Stableford,
|
||
rundedeling/visibility (ADR-036 fase 2) fortsatt ikke bygget.
|
||
|
||
**Fremdrift 2026-07-22:** HCP-indeks-motor (43/43 tester), databaseskjema
|
||
(`020`+`021`, sistnevnte en fiks for manglende rating-snapshot-kolonner),
|
||
OG et fullt API-lag (`app/routers/rounds.py`) er alle bygget,
|
||
scratch-verifisert og rullet ut mot ekte `teecup_db`/`teecup_api`.
|
||
|
||
**Frontend bygget og rullet ut 2026-07-23** — se ADR-033 i
|
||
ARCHITECTURE_DECISIONS.md for full detalj om alle tre lagene (engine,
|
||
skjema, API) og hele frontend-runden (nye endepunkter, komponenter,
|
||
verifisering, utrulling). Kort: `/rounds` (liste), `/rounds/new`
|
||
(bane-søk teeoff/egen + opprett egen bane, utslag/dato/hull), `/rounds/[id]`
|
||
(deltakere, hull-for-hull-registrering med automatisk GIR-visning,
|
||
fullfør-runde med HCP-differensial). Lenket fra dashbordet som «Egne
|
||
runder». Ingen migrasjon i denne del-runden — kun tre nye,
|
||
organisasjonsuavhengige endepunkter i `rounds.py`
|
||
(`official-search`/`official-search/{slug}`/`personal-courses/{id}`/
|
||
`.../holes`) og en endring av hull-PATCH-responsen.
|
||
|
||
**Frontend-presentasjonen ERSTATTET med V0-design 2026-07-23, samme dag:**
|
||
den første frontend-runden var hånd-kodet direkte av meg (avvik fra
|
||
prosjektets ellers gjennomgående V0-mønster, påpekt av bruker). Skrev tre
|
||
V0-prompter, mottok tre zip-eksporter, diffet mot levende tre (samme
|
||
rutine som alltid — kun fire filer reelt nye), erstattet de hånd-bygde
|
||
komponentene med V0s presentasjon og kablet ekte data inn på samme måte
|
||
som enhver annen skjerm i appen. Se ADR-033 for full detalj om
|
||
tilpasningene (to-stegs teeoff-oppslag, tredje kjønnsvalg «Annet»,
|
||
merge-før-PATCH beholdt, dev-forhåndsvisningskontroller fjernet). Ingen
|
||
backend-endring i denne del-runden. Rullet ut live, `teeoff.no`
|
||
upåvirket.
|
||
|
||
**Reell produksjonsbug funnet og fikset 2026-07-23, samme dag, rapportert
|
||
av bruker med skjermbilder:** `/rounds` var samtidig frontend-side og
|
||
API-prefiks — samme fellesklasse som ADR-016s medlemsside-hendelse, men
|
||
rammet begge presedens-retninger samtidig (listesiden nådde aldri
|
||
backend, og rundedetalj-siden var helt uoppnåelig). Fikset ved å flytte
|
||
frontend til `/my-rounds/*`, API uendret. Se ADR-033 for full detalj,
|
||
inkl. `curl`-bevis før/etter.
|
||
|
||
**Sju punkter rapportert av bruker 2026-07-24 etter faktisk bruk, BYGGET
|
||
OG LIVE samme dag** (unntatt punkt 6, se eget notat under): starthull-bug
|
||
(currentHole respekterte aldri `round.start_hole`, forklarte trolig også
|
||
det rapporterte GIR-avviket — feil hull ga feil par inn i en ellers
|
||
korrekt formel), kølle-bag i profilen (28 faste kølletyper, maks 14 --
|
||
den ekte golfregelen -- brukt som knapp-utvalg for "kølle brukt ved
|
||
utslaget", kun for eieren selv siden gjester ikke har profil), nytt
|
||
statistikkfelt "Anywayslag" (siste punkt i "Flere detaljer", samme
|
||
tallvelger-stil som slag/putter), valgfritt statistikknivå per deltaker
|
||
(`strokes_only`/`strokes_and_putts`/`full`, default `strokes_only` --
|
||
kun slag er strengt tatt nødvendig for resultat/HCP, resten er valgfritt
|
||
og skjules helt til det slås på), putt-avstand endret fra fritekst-tall
|
||
til seks faste bøtter (`<1m`…`8m+`), og "Hullet er spilt"-avkrysningen
|
||
fjernet helt (var reelt overflødig -- spilt settes allerede automatisk
|
||
når et slagtall velges). Ny migrasjon `022_round_stats_and_bag.sql`.
|
||
**Punkt 6 (numpad-layout for tallvelgerne + retningskors-ikoner for
|
||
utslag/innspill + vurdering av sveip vs. scroll) er BEVISST IKKE bygget
|
||
selv** — brukeren ba eksplisitt om at dette prompres til V0 for en egen
|
||
vurdering, se egen V0-prompt utarbeidet samme dag (ikke kjørt av
|
||
brukeren ennå ved denne loggens skriving).
|
||
**Fanget 2026-07-25, ✅ BYGGET OG LIVE 2026-07-29:** "plukket opp"-
|
||
mulighet for Stableford-format -- i Stableford er det vanlig å plukke
|
||
opp ballen uten å fullføre hullet når det er klart 0 poeng uansett.
|
||
Løst nøyaktig som antatt her: `'stableford'` lagt til som eget
|
||
`round.play_format` (migrasjon `038_stableford_and_pickup.sql`), og
|
||
"plukket opp" lagres som en cap på (netto) score -- Net Double Bogey,
|
||
samme prinsipp som WHS allerede bruker, via den eksisterende
|
||
`max_hole_score_for_handicap()` i `handicap_engine.py` (ingen ny
|
||
motorlogikk). Full detalj i CHANGELOG.md 2026-07-29, inkl. to
|
||
reelle frontend-bugs funnet og fikset under browserverifisering
|
||
(`play_format === "stroke"`-spesialsjekker som ikke ekskluderte det
|
||
nye "stableford"-formatet, samme mønster funnet og rettet fem steder
|
||
på tvers av `new-round.tsx`/`round-detail.tsx`/`watch-round.tsx`).
|
||
|
||
**Notat fra bruker, IKKE designet/bygget ennå (fanget 2026-07-22):**
|
||
brukeren har tenkt å ha med (a) måling av lengde på slag, og (b) å kunne
|
||
få opplyst avstand til forskjellige steder på banen (typisk pin/hazard/
|
||
layup-punkter, à la en golf-GPS/rangefinder). **Reell, ikke-triviell
|
||
avhengighet, verdt å notere nå:** dette krever faktiske GPS-/geografiske
|
||
koordinater for banens features (pin-plassering, hazarder osv.) — data
|
||
INGEN av dagens kilder har. Verken teeoff sitt API (kun par/stroke-
|
||
index/rating, ingen geometri) eller den nye `personal_course`-katalogen
|
||
(samme enkle skjema som org-scopet `course`/`hole`) inneholder noe slikt
|
||
i dag. "Lengde på slag" krever i tillegg selve GPS-posisjonering av
|
||
SPILLEREN i sanntid (nettleser-Geolocation API, ikke bare statiske
|
||
baneddata) — en annen klasse funksjonalitet enn resten av appen, som til
|
||
nå ikke har hatt noe geografisk/posisjonsbasert element i det hele tatt.
|
||
Ingen beslutning tatt om omfang, datakilde (manuelt kartlagt per bane?
|
||
en ekstern golf-GPS-database?) eller UI — kun fanget som en kjent,
|
||
fremtidig ambisjon som statistikk-modellen (Beslutning B) og
|
||
banedata-modellen (Beslutning C) bør ha i bakhodet, siden begge kan
|
||
trenge en utvidelse den dagen dette faktisk designes.
|
||
**Reconfirmed 2026-07-25** (scorekort-redesign-runden): brukeren
|
||
gjentok at avstandsmåling "ligger i kortene". Fortsatt IKKE designet
|
||
eller bygget -- eneste konkrete tiltak er en kode-KOMMENTAR i
|
||
`round-detail.tsx` sin hull-header som reserverer visuell plass ved
|
||
siden av GIR-merket, slik at en fremtidig avstand-indikator kan legges
|
||
til uten en layout-endring. Ingen data, ingen funksjonalitet.
|
||
|
||
**Brainstorm-runde 2026-08-06 -- fortsatt IKKE designet/besluttet, kun
|
||
retning avklart.** Brukeren ba eksplisitt om å starte konkretisering av
|
||
avstandsmåling, pekte ut ekte spiller-GPS-posisjon (ikke bare statiske
|
||
banepunkter) som første prioritet, og ba meg lese en opplastet PDF
|
||
("Golfbane API for internasjonal app.pdf" -- en eksportert Gemini-
|
||
samtale om internasjonal banedata for TeeCup) som grunnlag.
|
||
|
||
**Fra PDF-en (fakta, ikke min vurdering):** anbefalt dataleverandør for
|
||
banedata utenfor Norge er golfapi.io (~42 000 baner, REST-API ELLER
|
||
CSV-database-eksport). Priser: API 29 €/mnd (50 kall) opp til 399 €/mnd
|
||
(ubegrenset, årsabonnement), eller en ikke-utløpende engangspakke på
|
||
500 kall for 199 €. CSV-eksport MED koordinater: 2 995 € (Europa) til
|
||
5 995 € (alle baner) -- betydelig dyrere engangskostnad. **Avgjørende
|
||
lisensdetalj:** golfapi.io tillater eksplisitt permanent caching av
|
||
hentede banedata ("no need to call the API to fetch the same course
|
||
multiple times") -- ett kall per bane NOEN GANG er nok, ikke ett per
|
||
oppslag. Alternativ leverandør nevnt: iGolf (40 000+ baner, har
|
||
"greenkart" -- trolig ekte polygon-geometri, ikke bare punkter -- men
|
||
eksplisitt "ofte dyrt for mindre apper", B2B/enterprise).
|
||
|
||
**Min vurdering (Claude, denne datoen) -- IKKE besluttet, kun anbefalt:**
|
||
1. Caching-tillatelsen gjør API-veien (billig inngangspakke, 199 €)
|
||
klart mer kostnadseffektiv enn CSV-kjøpet i tidlig fase -- enig med
|
||
PDF-ens egen anbefaling. **Merk arkitektur-nyansen:** dette er en
|
||
BEVISST ANNERLEDES policy enn teeoff-regelen i CLAUDE.md ("banedata
|
||
hentes fra teeoff via lesende API, ikke delt database") -- teeoff
|
||
forblir levende oppslag uendret, golfapi.io (skulle det velges) ville
|
||
fått sin EGEN, ny policy (persistent cache tillatt av leverandørens
|
||
egen lisens). Bør få en egen ADR den dagen dette faktisk bygges, ikke
|
||
stille anta at det er samme regel.
|
||
2. **Brukerens hovedbekymring (kostnad ved å illustrere hvert hull) er
|
||
en ANNEN datatype enn selve avstandsdataene, og bør skilles fra dem:**
|
||
punkt-koordinater (pin/green-senter/hazard) for AVSTANDSTALL er
|
||
billig og dekket av planen over. Et ekte VISUELT, konturriktig
|
||
hullkart (fairway-/bunker-/green-FORM, ikke bare punkter) er
|
||
polygon-geometri -- en annen, dyrere datatype som verken golfapi.io
|
||
sin liste eller PDF-en nevner at de har (det er nettopp det
|
||
iGolf/"Golf Intelligence" sin enterprise-prising dekker).
|
||
3. **Anbefalt v1, for å unngå den kostnaden brukeren er bekymret for:**
|
||
bygg avstandsmåling som REN TALL-/TEKSTVISNING (f.eks. "142 m til
|
||
green"), IKKE et tegnet hullkart -- løser hele "avstandsmåling"-
|
||
ambisjonen med kun punktdata, ingen kartrendring-kostnad i det hele
|
||
tatt. Et ekte visuelt hullkart bør være et bevisst SENERE, adskilt
|
||
steg. Når/hvis det steget kommer: undersøk OpenStreetMap sin
|
||
`golf=*`-tagging (fairway/green/bunker-polygoner, community-kartlagt,
|
||
GRATIS via Overpass API, rendret med et gratis bibliotek som Leaflet)
|
||
for banene brukerne faktisk spiller, FØR proprietær enterprise-
|
||
geometri (iGolf/Golf Intelligence) vurderes -- OSM-dekning varierer
|
||
per bane (bedre for kjente baner), men koster 0 kr der den finnes.
|
||
|
||
**Fortsatt åpent, ikke avklart:** eksakt datakilde-valg (golfapi.io vs.
|
||
alternativ), om/når et visuelt hullkart faktisk skal bygges, hvordan
|
||
spiller-GPS-posisjon (Geolocation API) kombineres med de lagrede
|
||
punktene til en løpende avstandsberegning, og UI-plassering (samme
|
||
reserverte plass ved GIR-merket nevnt 2026-07-25, eller noe nytt).
|
||
|
||
**Brainstorm-runde 2026-08-07 -- "slaglengde-måling" konkretisert
|
||
(fortsatt IKKE designet/besluttet).** Brukeren beskrev en konkret flyt:
|
||
mål avstand FRA enten (a) spillerens live GPS-posisjon, eller (b) et
|
||
manuelt valgt punkt på et satellittkart, TIL der spilleren står ved
|
||
ballen -- pluss hvilken kølle som ble brukt. Kan deles i feeden eller
|
||
holdes privat.
|
||
|
||
**Nøkkelinnsikt fra research:** dette er BILLIGERE enn "avstand til
|
||
faste banepunkter" (notatet over) -- det trenger INGEN forhåndskjent
|
||
banegeometri (pin/hazard-koordinater), kun to AD HOC-punkter nær
|
||
spilleren der og da. Kartet trenger bare sentreres på spillerens egen
|
||
posisjon, ikke vite noe om hullet på forhånd.
|
||
|
||
**Avklart med bruker (AskUserQuestion, to spørsmål):**
|
||
1. **Omfang v1:** IKKE bare utslaget -- full slag-for-slag-logg fra
|
||
start (ethvert slag på hullet skal kunne måles, ikke kun det første).
|
||
Krever en NY, egen tabell (dagens `round_hole` har kun kategoriske
|
||
per-hull-felt som `club_off_tee`/`tee_shot_result`, ingen ekte
|
||
slag-for-slag-logg med avstand). Kølle-valg gjenbruker den
|
||
eksisterende, faste 28-køllers `BAG_CLUBS`-listen
|
||
(`app/routers/auth.py`) uendret.
|
||
2. **Kart-leverandør:** usikker, ba om videre diskusjon FØR beslutning
|
||
(se under).
|
||
|
||
**Kart-leverandør-sammenligning (ferske, verifiserte tall/vilkår,
|
||
2026-08-07 -- IKKE fra treningsdata alene):**
|
||
- **Mapbox GL JS:** 50 000 gratis kart-innlastinger/mnd, INGEN
|
||
betalingskort kreves for å starte, kommersiell bruk eksplisitt tillatt.
|
||
Over grensen: ca. $5/1000 ekstra innlastinger.
|
||
- **Leaflet + Esri World Imagery** (den vanlige "gratis"-kombinasjonen):
|
||
**reelt IKKE gratis for et kommersielt produkt** -- Esri sine egne
|
||
vilkår sier eksplisitt at World Imagery-laget ikke er tillatt for
|
||
kommersiell bruk uten egen ArcGIS-lisens. Siden TeeCup skal
|
||
kommersialiseres, diskvalifiserer dette denne kombinasjonen som et
|
||
reelt gratis-alternativ, selv om Leaflet-BIBLIOTEKET selv er gratis.
|
||
- **Anbefaling:** Mapbox GL JS -- eneste av de to reelt gratis OG
|
||
ToS-trygt for et kommersielt produkt på TeeCups sannsynlige skala.
|
||
|
||
**Kostnadsoverslag ved 100 000 brukere (grovt anslag, IKKE en garanti):**
|
||
Nøkkelen er at én "kart-innlasting" telles per gang kartkomponenten
|
||
INITIALISERES, ikke per slag/trykk -- ÉTT kartobjekt holdt levende
|
||
gjennom en hel runde (oppdatert med nye punkter per slag, ikke
|
||
re-montert) gjør at 14 slag i én runde koster like mye som ett. Med
|
||
konservative antakelser (30 % månedlig aktive, 30 % bruker funksjonen,
|
||
3 runder/mnd) lander man rundt 27 000 innlastinger/mnd -- under
|
||
gratisgrensen. Et aggressivt anslag (50/50/4) lander rundt 100 000/mnd,
|
||
altså ~$250/mnd i overforbruk -- en normal, forutsigbar
|
||
infrastrukturkostnad ved den brukerskalaen, ikke noe som truer en
|
||
forretningsmodell med betalende klubber/bedrifter. To konkrete grep som
|
||
holder volumet nede uansett skala: (1) kartet vises KUN når brukeren
|
||
eksplisitt velger "velg punkt på kart" i stedet for "bruk min posisjon"
|
||
-- ren GPS krever ingen kart-innlasting i det hele tatt, (2) selve
|
||
avstandsberegningen (Haversine-formel) er ren matte og treffer aldri
|
||
Mapbox. Ved genuint mye større skala (millioner av brukere): forhandle
|
||
en Enterprise-avtale direkte med Mapbox, eller revurder da -- ikke bygg
|
||
inn kompleksitet for det nå.
|
||
|
||
**AVKLART OG DESIGNET 2026-08-07, se ADR-048 i ARCHITECTURE_DECISIONS.md
|
||
for den fulle beslutningen.** De tre gjenstående spørsmålene over ble
|
||
avklart med bruker (AskUserQuestion): v1 dekker BÅDE individuelle runder
|
||
OG lagformater fra start (ikke bare individuell), delingsteksten 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. Skjema: ny `round_shot`-tabell (migrasjon
|
||
060), skjema-detaljene i ADR-048.
|
||
|
||
**Status: backend FERDIG og scratch-verifisert 2026-08-07** (migrasjon,
|
||
alle endepunkter, delings-flyt med satellitt-bilde-generering) — se
|
||
CHANGELOG.md punkt 37 for full detalj. Frontend delvis (Haversine-
|
||
hjelper, delt `ClubPicker`, Mapbox-avhengighet + token-plumbing).
|
||
**Gjenstår:** V0-eksport for selve `ShotMeasurementSheet` (prompt sendt
|
||
til bruker, venter på svar), kobling av inngangspunktene i
|
||
`round-detail.tsx`, reelle Mapbox-token i `.env` (kun tomme
|
||
plassholdere satt inn foreløpig), ekte nettleserverifisering av
|
||
kostnadskontroll-invarianten (ADR-048 Beslutning B), og til slutt
|
||
utrulling til ekte database/containere (krever migrasjon 060 kjørt mot
|
||
ekte `teecup_db` — IKKE gjort ennå, venter på eksplisitt bekreftelse).
|
||
|
||
**Se ADR-033 i ARCHITECTURE_DECISIONS.md for den fulle, besluttede
|
||
arkitekturen** (eierskapsmønster, statistikk-datamodell, HCP-indeksmotor).
|
||
Brukeren bekreftet 2026-07-22 at teeoff-banedata skal hentes via LIVE
|
||
oppslag (ikke import), og lastet opp den offisielle "WHS Rules of
|
||
Handicapping 2024" (USGA/R&A) som kilde for HCP-indeksberegningen — alle
|
||
tre store åpne punktene fra brainstorm-runden er dermed enten bekreftet
|
||
eller presist kildebelagt, ikke lenger antatt. Notatene under er
|
||
brainstorm-historikken som ledet frem til ADR-en — beholdt for
|
||
sporbarhet, ikke lenger den autoritative kilden for dette punktet.
|
||
|
||
Reist av brukeren 2026-07-21, eksplisitt begrunnet som relevant for
|
||
hvordan dashbordet skal se ut fremover (se punktet over) — derfor fanget
|
||
grundig her selv om ingenting bygges i denne runden.
|
||
|
||
**2026-07-22 — brukeren sier dette skal bli HOVEDFOKUS i appen** (det mest
|
||
"kontroversielle" premisset i samtalen): når frittstående rundeføring med
|
||
detaljert statistikk er på plass, skal det deretter bli ekstremt enkelt å
|
||
sette opp turneringer i ulike formater — altså en reell prioritets-
|
||
omveltning, ikke bare en ny funksjon ved siden av de eksisterende.
|
||
Fortsatt IKKE designet/bygget — dette er en brainstorm-runde (bedt
|
||
eksplisitt om av bruker), ikke en beslutningsrunde. Neste steg når
|
||
brukeren er klar: en egen, dedikert ADR-runde (se arkitektur-gaffelen
|
||
under).
|
||
|
||
**Nye statistikk-elementer lagt til 2026-07-22** (i tillegg til de fem fra
|
||
2026-07-21 under): kølle brukt ved utslaget, om utslaget traff fairway
|
||
eller var til høyre/venstre for den, automatisk beregnet "green in
|
||
regulation" (GIR), og utfallet av innspillet til green (traff/lang/kort/
|
||
høyre/venstre).
|
||
**Viktig presisering fra min side, IKKE avklart med bruker ennå:** GIR er
|
||
en DERIVERT stat (kan regnes ut fra antall slag brukt + om ballen var på
|
||
green) — men fairway-treff og innspill-retning krever at SPILLEREN
|
||
vurderer og taster inn utfallet etter hvert slag, appen kan ikke "beregne"
|
||
dette uten GPS. Dette betyr i praksis SLAG-FOR-SLAG-registrering (hvert
|
||
slag = kølle + utfall/posisjon), ikke bare noen aggregerte tall per hull
|
||
slik dagens scorekort gjør — en vesentlig UX-heving fra dagens modell.
|
||
|
||
**Fire konkrete spørsmål brukeren stilte, med retning:**
|
||
- **Egendefinerte baner hvis de ikke finnes i TeeOff:** ja. Åpent
|
||
delspørsmål: uten organisasjon, hvor bor en custom-bane? Custom-baner er
|
||
i dag org-scopet (`course.organization_id`). Anbefaling: behold
|
||
offisiell teeoff-import (ADR-019) som primærvei (gir korrekt rating
|
||
"gratis", avgjørende for HCP-matte), og lag en NY, GLOBAL (ikke
|
||
org-scopet) pool for egendefinerte baner — med søk-før-opprett for å
|
||
unngå tusenvis av private duplikater av samme bane.
|
||
- **Tvinge 18/front9/back9:** nei, kun standardvalg. Konsekvens: par-sum
|
||
for statistikk må regnes fra hullene FAKTISK spilt, ikke anta 72.
|
||
Øktenes `start_hole`-felt (ADR-015, allerede bygget for turneringer) er
|
||
direkte gjenbrukbart. Bør også kunne avsluttes midt i (f.eks. 14 hull)
|
||
uten forhåndsdeklarert totalt antall.
|
||
- **HCP-tellende krever minst 9 hull:** riktig prinsipp (WHS aksepterer
|
||
9-hulls score), MEN WHS sin faktiske konvertering av en 9-hulls-score
|
||
til en Score Differential er en EGEN, presis justering — ikke "halvparten
|
||
av 18-hulls-formelen". Nøyaktig den typen regel de tre HCP-PDF-ene
|
||
(lastet opp 2026-07-19, se CLAUDE.md) er ment å dekke — MÅ leses før
|
||
denne logikken bygges. **Strukturelt større gap oppdaget under
|
||
drodlingen:** `handicap_engine.py` regner i dag KUN course handicap/
|
||
slagfordeling fra en ALLEREDE KJENT indeks — den regner IKKE ut selve
|
||
HCP-indeksen fra en historikk av runder (WHS sin Score Differential +
|
||
snitt-av-beste-8-av-20-algoritme). Skal frittstående runder faktisk
|
||
oppdatere `app_user.handicap_index` automatisk, er dette en HELT NY
|
||
motor-komponent, ikke et lite tillegg til den eksisterende.
|
||
- **Spiller velger starthull:** ja, gjenbruk av samme `start_hole`-konsept
|
||
som over.
|
||
|
||
**Shotgun vs. fortløpende start ved turneringsoppsett (eget spørsmål,
|
||
egentlig et TURNERING/økt-konsept, ikke selve rundeførings-pivoten):**
|
||
- I dag er `session.start_hole` økt-bredt. Shotgun trenger starthull PER
|
||
FLIGHT/MATCH (typisk trukket/tildelt), ikke ett felles for økten.
|
||
- Shotgun har samme klokkeslett for alle grupper — ikke en variant av
|
||
`tee_interval_minutes` (som gjelder fortløpende start), kun
|
||
`start_hole` varierer mellom gruppene.
|
||
- Konkret forslag: `session.start_type: 'sequential' | 'shotgun'`,
|
||
`start_hole` blir settbart PER MATCH når shotgun velges (økt-nivået
|
||
forblir default/fallback for fortløpende). `tee_time`-utledningen
|
||
(ADR-015) trenger en egen shotgun-gren.
|
||
|
||
**Andre punkter fra drodlingen, ikke avklart med bruker ennå:**
|
||
- Spenningen "detaljert statistikk" vs. "ekstremt enkelt": anbefaler at
|
||
ALLE detalj-felt er valgfrie per hull — rask "bare slagtall"-
|
||
registrering skal alltid fungere, detaljer legges på for dem som vil.
|
||
Ellers risikerer man at hovedfokuset blir for tungvint til daglig bruk.
|
||
- Personlig køllebag (driver/hybrid/jern/wedge/putter) som naturlig
|
||
følgefunksjon til "kølle brukt ved utslag" — kvikk-valg fremfor fritekst
|
||
hver gang.
|
||
- Flight-partnere uten TeeCup-konto: gjenbruk det allerede etablerte
|
||
mønsteret for org-scopede spillere uten konto + senere e-post-kobling
|
||
(`link_player_by_email`-familien), ikke finn opp noe nytt.
|
||
- Bør turnering-scoring til slutt bruke SAMME rike statistikk-registrering
|
||
som frittstående runder (én delt scoring-komponent), i stedet for to
|
||
ulike scoring-opplevelser i samme app? Ikke avklart, men verdt å ha i
|
||
bakhodet fra design-start siden brukeren kaller dette "hovedfokus".
|
||
- Historikk/trender over tid (beste runde, HCP-trend, snitt putter/runde)
|
||
— naturlig, senere konsekvens når data finnes, ingen egen beslutning
|
||
nødvendig nå.
|
||
|
||
**Brukerens beskrevne behov, fanget presist:** en bruker skal kunne
|
||
registrere en golfrunde HELT UAVHENGIG av enhver turnering eller
|
||
organisasjon — verken tilhørighet til et lag, en turnering, eller en
|
||
organisasjon skal være en forutsetning. Kan føres kun for seg selv, ELLER
|
||
for andre man spiller sammen med i flighten (ikke nødvendigvis
|
||
TeeCup-brukere). Statistikk utover selve slagtallet:
|
||
- Antall slag på hullet (allerede dekket av eksisterende `hole_score`-form)
|
||
- Antall putter
|
||
- Antall chip
|
||
- Antall bunkerslag
|
||
- Antall straffeslag
|
||
- Lengde på første putt
|
||
- Kølle brukt ved utslaget (2026-07-22)
|
||
- Om utslaget traff fairway, eller var til høyre/venstre for den (2026-07-22)
|
||
- "Green in regulation" (GIR), automatisk beregnet (2026-07-22 — se
|
||
presisering under om DERIVERT vs. OBSERVERT stat)
|
||
- Utfallet av innspillet til green: traff/lang/kort/høyre/venstre
|
||
(2026-07-22)
|
||
|
||
**Dette er IKKE en liten dashboard-finpuss — det utfordrer en av
|
||
arkitektur-invariantene i CLAUDE.md direkte:** "Tenant = organisasjon.
|
||
`organization_id` på alle domenetabeller, håndhevet av RLS." En
|
||
frittstående runde har per definisjon INGEN organisasjon å henge
|
||
`organization_id` på — dagens RLS-modell (org_isolation-policyer som
|
||
alle stoler på `app.current_org`) dekker rett og slett ikke dette
|
||
tilfellet. Dette krever en ny, egen beslutning (sannsynligvis en helt ny
|
||
ADR) om et PARALLELT eierskaps-/isolasjonsmønster keyet på `user_id`
|
||
(`app_user.id`) i stedet for `organization_id` — ikke en utvidelse av et
|
||
eksisterende mønster, men en ny gren i tenant-modellen. Presist hvilke
|
||
tabeller som trengs (egen `personal_round`? egen
|
||
`personal_round_hole_stat`? gjenbruk av eksisterende `hole_score`-form med
|
||
en nullable `organization_id` og en NY RLS-policy for "eier = current
|
||
user"?) er IKKE avklart — bevisst ikke gjettet på her.
|
||
|
||
**Andre åpne spørsmål som trengs FØR design/bygging, ikke besvart av
|
||
brukerens beskrivelse ennå:**
|
||
- Hvordan identifiseres "andre man spiller med i flighten" når de ikke
|
||
nødvendigvis er TeeCup-brukere — frittstående "midlertidige" spiller-
|
||
rader (ala `player`, men uten organisasjonstilhørighet), eller rene
|
||
navn uten noen kobling i det hele tatt?
|
||
- Skal disse rundene noensinne telle inn i HCP-beregning/-historikk (se
|
||
eget punkt under), eller er de rent loggførende (som en digital
|
||
scorekort-dagbok)?
|
||
- Skal banedata (hull/par/stroke index/tee-rating) hentes fra samme
|
||
`course`-modell som i dag (org-scopet), eller trengs en egen,
|
||
org-uavhengig banekatalog for dette bruksmønsteret (en spiller uten
|
||
noen organisasjon i det hele tatt må fortsatt kunne velge en bane)?
|
||
- Skal frittstående runder vises i "Mine runder" på dashbordet sammen med
|
||
turnering-rundene, eller i en egen seksjon?
|
||
|
||
**Bevisst IKKE startet i denne runden** — dette bør bli sin egen,
|
||
dedikerte ADR-runde (arkitektur-invariant-nivå beslutning, ikke et
|
||
tillegg til en dashboard/konto-poleringsrunde), men er tatt med i
|
||
vurderingen av tom-tilstand-redesignet over siden det direkte påvirker
|
||
hvilke "første handling"-alternativer dashbordet bør vise i fremtiden.
|
||
|
||
---
|
||
|
||
## Frittstående runder: ekte spillformer (slagspill/match/skins/par-lag) — ✅ HELT FERDIG, backend + frontend BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE (ADR-039)
|
||
|
||
**Oppdatering 2026-07-28 (ADR-039):** design + backend bygget samme dag
|
||
som punktet ble reist, frontend (sideoppsett/skins-konfig/matchstatus-
|
||
visning/delt-ball-scorekort) og selve utrullingen mot ekte `teecup_db`/
|
||
`teecup_api` fulgte rett etter, samme dag — se CHANGELOG.md
|
||
2026-07-28 for full detalj (migrasjon `031`, nye endepunkter, 96+26
|
||
backend-scratch-sjekker, full nettleser-verifisering av alle seks
|
||
formater). Kort: alle fire load-bærende spørsmål (sider/gruppering,
|
||
omfang av par-/lag-underformater, skins-regler, HCP-tellestatus for
|
||
delt-ball) avklart med bruker, hele match-play-motoren fra
|
||
org-turneringer PORTERT uendret (ingen ny regnelogikk for match/
|
||
fourball/foursome/greensome/scramble), kun skins fikk ekte ny
|
||
motorkode. Etterfølgende samme-dags oppfølgingsrunder (minimums-
|
||
spiller-håndhevelse, offline-kø utvidet til frittstående runder,
|
||
scorekort-bugfikser for spillformater) også alle live — se CLAUDE.md.
|
||
|
||
Opprinnelig reist av brukeren rett etter at ADR-038 (faktisk HCP) ble
|
||
rullet ut:
|
||
"Når en singlerunde settes opp: 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."
|
||
|
||
**Viktig presisering av hva som FINNES i dag, for å unngå forveksling:**
|
||
`round.play_format` (`'stroke'`/`'match'`, ADR-038) er i dag KUN en
|
||
selvdeklarert ETIKETT som styrer én ting — et forslag om å ekskludere
|
||
runden fra faktisk-HCP-grunnlaget. Den endrer INGENTING ved selve
|
||
scoringsmodellen eller scorekortet — en "matchspill"-runde i dag
|
||
registreres og vises identisk med en slagspill-runde (rå slag per hull
|
||
per spiller). Dette nye punktet ber om noe vesentlig større: at
|
||
spilleform faktisk STYRER både HCP-beregningen og scorekort-
|
||
presentasjonen, for fire distinkte typer.
|
||
|
||
**De fire spillformene, og hva som mangler for hver:**
|
||
1. **Slagspill** — dagens modell, uendret. Allerede fullt bygget
|
||
(Score Differential/AGS, ADR-033 Beslutning G).
|
||
2. **Match (to spillere)** — trenger match-play-slagfordeling
|
||
(`match_play_strokes` i `handicap_engine.py`, allerede bygget/testet
|
||
for turnering-matcher) i stedet for `allocate_strokes_by_index`, og et
|
||
scorekort som viser løpende matchstatus (hull for hull vunnet/tapt/
|
||
delt + "X UP"/"AS", samme presentasjonsspråk som
|
||
`session-scorecard.tsx` allerede har for turnering-matcher) i stedet
|
||
for rå slagsummer. **Reelt skjemahull:** `round_participant` har i dag
|
||
INGEN "hvem spiller mot hvem"-kobling — en runde med 3+ deltakere har
|
||
ingen måte å si at akkurat to av dem utgjør matchen.
|
||
3. **Skins** — hull-for-hull-konkurranse der laveste (netto eller
|
||
brutto) score på hullet vinner en "skin", uavgjort hull ruller
|
||
premien videre til neste hull. **Ingen eksisterende motorstøtte i det
|
||
hele tatt** — verken en beregningsfunksjon eller noen skjema-plass for
|
||
"skins vunnet"/gjeldende premieverdi. Må designes fra bunnen
|
||
(inkl. avklaring: netto eller brutto skins, rullerer uavgjort-verdien
|
||
videre eller deles, minst 3 spillere).
|
||
4. **Par- eller lag-konkurranse** — fourball/foursome/greensome/scramble-
|
||
type spill blant rundens deltakere. Motoren for AKKURAT dette
|
||
(`Format`-enum, `AllowanceStrategy`-familien, `unit_playing_handicap`)
|
||
er allerede bygget og testet — men KUN brukt av den org-scopede
|
||
turnering-modellen (`match`/`match_participant` under `session`).
|
||
**Reelt skjemahull, samme klasse som punkt 2:** frittstående
|
||
`round_participant`-rader er i dag rene individer uten noe
|
||
par-/lag-konsept — ingen kobling for "disse to er makkere denne
|
||
runden."
|
||
|
||
**Sentral arkitektur-spenning, verdt å legge merke til FØR design
|
||
starter:** punkt 2 og 4 er strukturelt nesten IDENTISKE med det
|
||
`app/routers/matches.py`/`scoring.py` allerede gjør for org-scopede
|
||
turneringer (samme `Format`-enum, samme match-play-motor) — bare uten en
|
||
organisasjon rundt. Dette overlapper direkte med det ennå ubesluttede
|
||
spørsmålet i ADR-037 ("individuelle turneringer") og det tidligere
|
||
presiserte "flere flighter i én frittstående runde"-spørsmålet (se egen
|
||
seksjon over) — tre beslektede, men foreløpig separat behandlede
|
||
problemstillinger som alle til slutt lander på "hvordan grupperer/
|
||
parer vi deltakere, og hvilken motor regner poeng fra rå slag." Bør
|
||
trolig avklares SAMMEN, ikke som tre uavhengige design-runder, for å
|
||
unngå tre parallelle, litt ulike implementasjoner av i bunn og grunn
|
||
samme idé.
|
||
|
||
**Åpne spørsmål under — HISTORISK, alle AVKLART OG BYGGET samme dag via
|
||
ADR-039 (se oppdateringsnotatet øverst i denne seksjonen). Beholdt uendret
|
||
som referanse for hvordan spørsmålene opprinnelig ble stilt, med fasiten
|
||
lagt til rett under hvert punkt — ikke slettet, per CLAUDE.md sin regel
|
||
om å ikke fjerne historikk:**
|
||
- Skal `round.play_format` utvides med `'skins'`/`'pair_team'` (ny
|
||
migrasjon, ny CHECK-verdi), eller er dette en helt egen entitet
|
||
parallelt med `round`?
|
||
→ **Avklart:** utvidet til 8 verdier (migrasjon 031), samme `round`-tabell.
|
||
- Match/par-lag: hvordan velges/lagres hvem som spiller mot/med hvem —
|
||
ved oppsett (som blind draw for turneringer), eller fritt valgt av
|
||
eieren i etterkant?
|
||
→ **Avklart:** ny `round_side`-tabell (maks to per runde), eieren
|
||
tildeler deltakere til en side fritt i etterkant via UI (`SidesPanel`).
|
||
- Skins: netto eller brutto, og hvordan behandles uavgjorte hull
|
||
(rullerer premien, eller deles)?
|
||
→ **Avklart:** begge akser konfigurerbare av oppsetteren
|
||
(`skins_scoring`/`skins_tie_handling`), ny `compute_skins()`-motor.
|
||
- Skal scorekortets NYE presentasjoner (matchstatus, skins-tavle,
|
||
side-score) bygges som varianter av eksisterende komponenter
|
||
(`round-scorecard.tsx`/`round-detail.tsx`), eller gjenbruke turnering-
|
||
sidens `session-scorecard.tsx`-språk direkte?
|
||
→ **Avklart:** egne lokale varianter i `round-detail.tsx`/
|
||
`round-scorecard.tsx` (samme visuelle SPRÅK som `session-scorecard.tsx`,
|
||
ikke delt kode — prosjektets etablerte "én fil, én kopi"-konvensjon).
|
||
- Hvordan påvirker dette allerede byggede ADR-038 (faktisk HCP)? Match/
|
||
skins/par-lag-runder trenger sannsynligvis EGNE regler for om/hvordan
|
||
de teller mot faktisk HCP (samme "matchspill telles vanligvis ikke"-
|
||
resonnement som allerede finnes for `'match'`, men skins/par-lag er
|
||
ikke vurdert i det hele tatt ennå).
|
||
→ **Avklart:** delt-ball-formater (foursome/greensome/scramble) teller
|
||
ALDRI (ingen individuell score å bygge en differensial fra — viste seg
|
||
automatisk av eksisterende `complete_round`-logikk, ingen kodeendring
|
||
krevdes). Skins teller NORMALT som slagspill. Match følger allerede
|
||
etablert `exclude_from_handicap`-mønster fra ADR-038.
|
||
|
||
---
|
||
|
||
## Én person, flere e-postadresser — DEL 1 (det enkle tilfellet) ✅ BYGGET OG LIVE 2026-07-21, DEL 2 (kontosammenslåing) fortsatt 📋 NOTERT
|
||
|
||
Reist av brukeren rett etter ADR-032 (verifisert e-postbytte). Et beslektet,
|
||
men DISTINKT behov: én person kan ha flere e-postadresser i omløp samtidig
|
||
(f.eks. registrert seg privat med én adresse, men fått en turnering-
|
||
invitasjon rettet mot en jobb-adresse organisator la inn) — ikke et BYTTE
|
||
(ADR-032 sin løsning), men en TILLEGGS-tilknytning. I dag oppretter et
|
||
magic-link-innlogg på en ny adresse alltid en HELT NY, tom `app_user`-konto
|
||
(ADR-009) — nøyaktig det som gjør at spilleren aldri "finner" turneringen
|
||
sin med sin vanlige, primære konto.
|
||
|
||
**Brukerens beskrevne flyt, ordrett fanget:** innlogget med
|
||
eksempel@domene.no, sier "jeg eier også test@domain.com". Ved dette kravet
|
||
sendes en e-post til test@domain.com med en nøkkel som limes inn et sted på
|
||
dashbordet. Etter bekreftelse dukker inviterte turneringer på den adressen
|
||
opp. Annen data (spilte runder, personlig informasjon) skal "forespørres
|
||
slått sammen eller justert". Fremtidige innlogginger skal kunne gjøres med
|
||
ENHVER av de tilknyttede adressene.
|
||
|
||
**Foreslått retning, basert på gjenbruk av allerede bygget mønster:**
|
||
samme token-i-e-post-bevis-eierskap-mekanisme som ADR-032 sin
|
||
e-postbytte-flyt (`email_change_token`), men ADDITIV i stedet for
|
||
ERSTATTENDE — en ny tabell for verifiserte SEKUNDÆRE e-poster knyttet til
|
||
kontoen (i stedet for å overskrive `app_user.email`). Login (`/auth/
|
||
request-link` m.fl.) må da slå opp BÅDE primær- og sekundær-e-poster.
|
||
`link_player_by_email()`/organisasjonsinvitasjon-aksept (som i dag kun
|
||
kjører mot `app_user.email`) må kjøres for HVER av kontoens verifiserte
|
||
adresser — naturlig utløst rett etter en ny adresse er bekreftet, og
|
||
sannsynligvis også trygt å kjøre på nytt ved hver innlogging (idempotent,
|
||
samme mønster som i dag).
|
||
|
||
**Det virkelig vanskelige, uløste spørsmålet, IKKE adressert av brukerens
|
||
beskrevne flyt:** hva skjer hvis den "krevde" adressen ALLEREDE er
|
||
primær- (eller sekundær-)adressen til en ANNEN, eksisterende `app_user`-
|
||
konto — altså at spilleren faktisk har logget inn med DEN adressen
|
||
tidligere og dermed har to helt separate kontoer med egen historikk
|
||
(ulike org-medlemskap, ulike spillerkoblinger, kanskje ulikt passord/2FA)?
|
||
Da holder det ikke å bare "legge til" adressen — det er en ekte KONTO-
|
||
SAMMENSLÅING (slå sammen organisasjonsmedlemskap uten å bryte "én rolle
|
||
per bruker per org"-unikheten, deduplisere spiller-koblinger, avgjøre
|
||
hvilken konto som "vinner" for tvetydige felt som `preferred_locale`/2FA
|
||
når begge har satt noe ulikt). Dette er en betydelig større og mer
|
||
risikofylt operasjon enn "legg til en frisk, ukrevd adresse" — bør
|
||
utredes og besluttes som en egen, separat sak, ikke antas løst av samme
|
||
runde som det enkle tilfellet.
|
||
|
||
**Plassering:** brukeren presiserte eksplisitt at dette må skje FRA
|
||
dashbord-siden (ikke `/account`, der ADR-032 sin e-postbytte-flyt ellers
|
||
naturlig ville hørt hjemme) — trolig fordi selve GEVINSTEN (nye turneringer
|
||
dukker opp) er noe som vises på dashbordet, så handlingen bør ligge der
|
||
resultatet vises.
|
||
|
||
**Ble delt i to separate runder, som foreslått:** (1) legg til en frisk,
|
||
ukrevd sekundær-e-post — ✅ BYGGET, se under. (2) Ekte konto-sammenslåing
|
||
for det vanskelige tilfellet — fortsatt IKKE designet, egen fremtidig
|
||
runde.
|
||
|
||
### Del 1 (det enkle tilfellet) — ✅ BYGGET OG LIVE 2026-07-21
|
||
|
||
Migrasjon `017_secondary_email.sql`: to nye tabeller
|
||
(`secondary_email_token` — midlertidig, samme token-hash-og-utløp-mønster
|
||
som `email_change_token`; `user_secondary_email` — den faktiske,
|
||
verifiserte adressen, globalt UNIQUE). Nye endepunkter i
|
||
`app/routers/auth.py`: `POST /auth/secondary-email` (send
|
||
bekreftelseslenke), `POST /auth/secondary-email/confirm` (ingen sesjon
|
||
påkrevd, samme mønster som selve magic-link-verifiseringen),
|
||
`DELETE /auth/secondary-email/{id}`.
|
||
|
||
**Kjernestykket, ikke bare CRUD:** `verify_magic_link` og
|
||
`login_with_password` sjekker nå `user_secondary_email` FØR de gjør sitt
|
||
vanlige `app_user.email`-oppslag — finner de en match, løses innloggingen
|
||
til DEN EKSISTERENDE eierens konto i stedet for å (som før) stille
|
||
opprette en helt ny, separat konto. Dette er selve mekanismen som gjør
|
||
adressen nyttig, ikke bare en liste over "andre adresser".
|
||
|
||
**Plassering, bevisst avvik fra brukerens opprinnelige "fra dashbordet"-
|
||
instruks:** lagt i `/account` (samme sted som ADR-032 sin e-postbytte),
|
||
IKKE dashbordet — begrunnet med at dette kun er del 1 (det enkle
|
||
tilfellet); når/hvis del 2 (kontosammenslåing, "data dukker opp") bygges,
|
||
er dashbordet trolig riktigere siden GEVINSTEN vises der. Flagget
|
||
eksplisitt til bruker, ikke stille besluttet.
|
||
|
||
**Scratch-verifisert, 20 sjekker:** adresse legges IKKE til før bekreftet;
|
||
token ikke gjenbrukbart; adresse som allerede er en ANNEN kontos
|
||
hovedadresse ELLER sekundæradresse avvist tydelig (409 DUPLICATE) i begge
|
||
retninger; innlogging via sekundæradressen (BÅDE magic-link OG passord)
|
||
løses korrekt til samme, eksisterende konto (bekreftet: samme `id`,
|
||
`email` i responsen forblir hovedadressen); en fremmed kan ikke slette
|
||
andres sekundæradresse; og — den kritiske sjekken — en ny innlogging på
|
||
adressen ETTER at den er fjernet oppretter en genuint NY, separat konto
|
||
(beviser fjerningen er reell, ikke kosmetisk). `test_isolation.sql`
|
||
fortsatt 12/12 (additiv migrasjon). Ekte typesjekket produksjonsbuild av
|
||
frontend kjørt og bekreftet.
|
||
|
||
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon 017
|
||
kjørt mot ekte `teecup_db` (bekreftet begge nye tabeller finnes,
|
||
`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 📋 NOTERT, IKKE designet
|
||
|
||
Uendret fra den opprinnelige analysen: hva skjer hvis adressen som legges
|
||
til ALLEREDE er primær- eller sekundæradressen til en ANNEN, eksisterende
|
||
konto (spilleren har altså to helt separate kontoer med egen historikk —
|
||
ulike org-medlemskap, ulike spillerkoblinger, kanskje ulikt passord/2FA)?
|
||
Dagens del 1-løsning avviser dette tydelig (409 DUPLICATE) i stedet for å
|
||
gjette — en ekte sammenslåing (slå sammen org-medlemskap uten å bryte
|
||
"én rolle per bruker per org", deduplisere spillerkoblinger, avgjøre
|
||
hvilken konto som "vinner" for motstridende felt) er en betydelig større
|
||
og mer risikofylt operasjon, fortsatt bevisst utsatt til en egen,
|
||
dedikert designrunde.
|
||
|
||
---
|
||
|
||
## UX / frontend (senere fase)
|
||
|
||
- 🔀 **Tilgjengelighet — STÅENDE krav, ikke lenger et enkeltpunkt
|
||
(skjerpet 2026-07-22, se CLAUDE.md):** all frontend, eksisterende og
|
||
fremtidig (inkl. alle nye V0-skjermer), skal være lesbar/forståelig/
|
||
betjenbar for noen med noe redusert syn UTEN briller. Det tidligere,
|
||
vagere punktet under ("høy kontrast, store knapper...") er nå en
|
||
KONKRET instans av dette generelle, varige kravet — ikke en egen,
|
||
isolert senere-fase-oppgave. Ingen dedikert retrofit-runde igangsatt
|
||
ennå; rettes opportunistisk når skjermer likevel røres, og tas inn i
|
||
enhver ny V0-prompt fremover.
|
||
- 📋 Høy kontrast, dark/light, store +/- knapper, stor «Neste hull»-knapp
|
||
(banebruk i sollys/med solbriller) — konkret eksempel på punktet over.
|
||
- ✅ Offline-first (ADR-006) — BYGGET 2026-07-19 (ADR-028), se eget punkt
|
||
under. Scoreregistrering (hole-scores/hole-results) fungerer nå offline
|
||
med automatisk synk.
|
||
- ✅ PWA: manifest, service worker, «Legg til på hjemskjerm» — BYGGET
|
||
2026-07-19 (ADR-028).
|
||
|
||
### PWA — ✅ BYGGET OG LIVE 2026-07-19 (ADR-028)
|
||
|
||
Full design i ARCHITECTURE_DECISIONS.md ADR-028. Kort:
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Installerbar app (manifest + ikoner + «Legg til på hjemskjerm») | ✅ bygget | `app/manifest.ts` (Next.js sin innebygde manifest-generator), `components/sw-register.tsx`, `appleWebApp`-metadata for iOS. |
|
||
| Ikoner | ✅ **ERSTATTET MED EKTE DESIGN 2026-08-10** | Opprinnelig et enkelt, midlertidig grønt golf-flagg generert programmatisk. Erstattet med et ekte app-ikon (ball/pokal/tee-motiv, full-bleed) — se ADR-055 i ARCHITECTURE_DECISIONS.md (inkl. en korrigeringsrunde etter en reell tegnefeil i første forsøk). Denne raden var stale frem til 2026-08-14-oppryddingen. |
|
||
| Service worker: cache app-navigasjon + `/orgs/*`-GET-er | ✅ bygget | `public/sw.js`, nettverk-først/cache-fallback (bevisst IKKE stale-while-revalidate, se ADR-028). `public/offline.html` som siste utvei. |
|
||
| Offline scoreregistrering (hole-scores/hole-results) | ✅ bygget | `lib/offline-queue.ts` (IndexedDB-kø) + `components/session-scorecard.tsx`. Synker automatisk ved `window`s `online`-event, pluss manuell "Synkroniser nå"-knapp. Bevisst IKKE Background Sync API (iOS Safari støtter den ikke). |
|
||
| Andre skrivehandlinger offline (walkover, chat/feed, oppsett) | 💤 bevisst utenfor omfang | Kun de to scoreregistrerings-endepunktene er køet — se ADR-028 Beslutning B for begrunnelse per type. |
|
||
| Faktisk browser-testet (DevTools Offline-modus) | ✅ **FERDIG 2026-07-28** | Både turnering-scorekortet (den opprinnelige ADR-028-flyten) og den senere utvidelsen til frittstående runder er nå bevist med ekte Chrome DevTools-nettverksemulering (offline → registrer slag → tilbake online → automatisk synk bekreftet server-side), ikke bare kodegjennomgang. Se CHANGELOG.md 2026-07-28. |
|
||
|
||
**Rullet ut live 2026-07-19**, bruker bekreftet eksplisitt: `docker compose
|
||
up -d --build teecup_frontend` (ingen migrasjon). Verifisert:
|
||
`/health`/`dashboard` → 200, `/manifest.webmanifest`/`sw.js`/ikoner alle 200
|
||
over ekte https, `teeoff.no` upåvirket.
|
||
|
||
---
|
||
|
||
## Notat 2026-07-30: GIR burde utlede "innspill traff green" automatisk, ikke kreve manuell merking — ✅ BYGGET, BROWSERVERIFISERT OG LIVE
|
||
|
||
Reist av brukeren: får man birdie med kun én putt, MÅ innspillet ha truffet
|
||
greenen (par − 2 slag brukt før putting = GIR per definisjon) — da bør
|
||
appen ikke kreve at spilleren i tillegg manuelt krysser av "Innspill: Traff"
|
||
i `DirectionCross`-feltet i `ScoringWizard` sitt "flere detaljer"-steg
|
||
(`round-detail.tsx`, `stat.approach`).
|
||
|
||
**Presisering, funnet ved kodegjennomgang:** appen har ALLEREDE en generell
|
||
GIR-inferens fra slag+putt (`score − putts ≤ par − 2`, `round-stats.tsx` sin
|
||
`isGir`/`round-detail.tsx` sin `showGir`) — brukt til selve GIR-STATISTIKKEN
|
||
(greentreff-donut, GIR-avhengige snitt). Det som IKKE er koblet sammen er
|
||
den separate, manuelt utfylte `approach_result`-verdien ("Innspill: Traff/
|
||
Kort/Langt/Venstre/Høyre") — brukt til RETNINGS-statistikken (hvilken vei
|
||
man bommer greenen). Disse to feltene lever i dag adskilt: den utledede
|
||
GIR-verdien styrer ALDRI hva som vises/kreves i `DirectionCross`-feltet.
|
||
|
||
**Naturlig fiks (ikke bygget):** når `score − putts ≤ par − 2` er sann for
|
||
et hull (dvs. GIR er logisk garantert), bør `stat.approach` auto-settes til
|
||
`"hit"` i stedet for å kreve et eget klikk — analogt de andre auto-hopp-
|
||
mekanismene i samme wizard (`autoAdvanceFieldFor`). Motsatt gjelder IKKE:
|
||
GIR kan være usann uten at man vet retningen (f.eks. en birdie med to
|
||
putter er ikke GIR-garantert, og en scrambling-birdie fra utenfor green gir
|
||
identisk slag/putt-mønster som en ekte GIR — kun i RETNING vet vi ingenting
|
||
automatisk der). Trigger-tidspunktet er litt kinkig: putt-tallet kommer
|
||
FØR "flere detaljer"-steget i wizard-rekkefølgen (`strokes → putts →
|
||
puttDistance → details`), så inferensen kan skje idet man ANKOMMER
|
||
detalj-steget (begge tall er da kjent) — ikke midt i et tidligere steg.
|
||
|
||
**Bygget nøyaktig som beskrevet over, samme dag:** ny `useEffect` i
|
||
`ScoringWizard` (`round-detail.tsx`) som trigges idet "details"-steget
|
||
nås — setter `stat.approach = "hit"` KUN når `stat.approach` fortsatt er
|
||
`null` (rører aldri et allerede satt, manuelt ELLER tidligere auto-satt
|
||
valg) og `stat.strokes - stat.putts <= hole.par - 2`.
|
||
**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):
|
||
positiv sjekk (birdie+1-putt på et par 4-hull, hull 17) bekreftet
|
||
"Innspill: Traff" auto-merket UMIDDELBART ved ankomst til detalj-steget,
|
||
UTEN noe klikk -- bekreftet BÅDE visuelt (skjermbilde) og direkte i
|
||
databasen (`approach_result='hit'`). Negativ sjekk (bogey+1-putt, hull 2,
|
||
satt via direkte API-kall FØR wizard åpnet) bekreftet "Traff" korrekt
|
||
IKKE forhåndsmerket ved ankomst til samme steg. Ingen konsollfeil.
|
||
**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` → 200, `teeoff.no` upåvirket.
|
||
|
||
## Notat 2026-07-30: auto-prompt "Fullfør runde" når alle hull er ført for alle spillere — ✅ BYGGET, BROWSERVERIFISERT OG LIVE
|
||
|
||
Reist av brukeren rett etter scoring-autorisasjonsfiksen for ADR-037.
|
||
Gjelder "single-runder" i CLAUDE.md sin etablerte betydning — frittstående
|
||
runder (`/my-rounds/{id}`, ADR-033), ikke org-scopede individuelle
|
||
turneringer.
|
||
|
||
**Nå:** `round-detail.tsx` sin "Fullfør runde"-knapp finnes allerede (i den
|
||
tonet-ned Rediger/Fullfør/Slett-raden øverst, se 2026-07-25s UX-fiks), men
|
||
er en helt PASSIV knapp — ingen kode sjekker i dag om alle hull faktisk er
|
||
ferdig utfylt for alle deltakere, og ingenting fremhever/popper opp knappen
|
||
når det skjer. Brukeren må selv huske å scrolle opp og trykke, selv lenge
|
||
etter siste hull er registrert.
|
||
|
||
**Naturlig fiks (ikke bygget):** en klientside-sjekk (samme type
|
||
`isEntryComplete()`-logikk som allerede finnes for wizard-fremdrift, se
|
||
`advanceToNextPlayerOrHole()`-mønsteret fra 2026-07-26) som kjører etter
|
||
HVER hull-innsending — er `stat_level`-relevante felt fylt ut for SISTE
|
||
hull for ALLE deltakere (ikke bare aktiv spiller), vis en tydelig CTA/
|
||
banner ("Alle hull er ført — Fullfør runden?") i stedet for kun den passive
|
||
knappen i den tonede raden øverst. For delt-ball-formater (foursome/
|
||
greensome/scramble) gjelder samme sjekk på SIDE-nivå (`round_side`), ikke
|
||
per spiller. Ingen backend-endring nødvendig — dette er ren
|
||
frontend-tilstand avledet fra data som allerede lastes.
|
||
|
||
**Bygget nøyaktig som beskrevet over, samme dag:** ny
|
||
`allHolesEnteredForEveryone`-beregning i `RoundDetail` — individuelle
|
||
formater: ALLE spillere har `played=true` på ALLE hull i `holeOrder`;
|
||
delt-ball-formater: BEGGE sider har `played=true` på alle hull. Gatet på
|
||
`round.setup_complete` (samme gate som selve knappen) og `!completed`. Ny
|
||
`AllHolesEnteredBanner`-komponent (samme visuelle språk som
|
||
`CompletedBanner`) vist øverst på Score-fanen når betingelsen er sann, med
|
||
en direkte "Fullfør runden"-CTA (kaller samme `finishRound()` som den
|
||
eksisterende knappen, inkl. samme `confirm()`-bekreftelse).
|
||
**Browserverifisert grundig i samme isolerte scratch-økt:** fylte de to
|
||
siste hullene (owner hull 17+18 via wizard, gjest hull 18 via API) for en
|
||
18-hulls 2-spiller-runde, reloadet siden — banneret dukket opp automatisk
|
||
med korrekt tekst ("Alle hull er ført — Klar til å fullføre runden?").
|
||
Klikket "Fullfør runden" (håndterte den native `confirm()`-dialogen via
|
||
Chrome DevTools) — runden ble korrekt fullført (HCP-differensialer
|
||
beregnet og vist, banneret erstattet av det eksisterende
|
||
"Runde fullført"-banneret). Ingen konsollfeil gjennom hele flyten.
|
||
**Rullet ut live 2026-07-30**, samme utrulling som GIR-fiksen over (én
|
||
felles `docker compose up -d --build teecup_frontend`), bruker bekreftet
|
||
eksplisitt.
|
||
|
||
## Notat 2026-08-01/02: «Det store grepet» — sammenhengende rundeoppsett + delt Score/Scorekort/Leaderboard-navigasjon — ✅ HELT FERDIG (alle 5 steg), se ADR-040
|
||
|
||
Full beslutningslogg i ARCHITECTURE_DECISIONS.md (ADR-040) — dette notatet
|
||
er kun arbeidssporet/gjenstående plan, ikke beslutningene selv.
|
||
|
||
**Bakgrunn:** oppstod som en direkte oppfølging av login-/dashbord-
|
||
redesign-rundene (inspirert av et eksternt design-verktøy, "Stitch").
|
||
Brukeren pekte på to strukturelle hull utover selve fargespråket:
|
||
rundeoppsettet er spredt over to helt separate skjermer, og Score/
|
||
Scorekort/Leaderboard for en frittstående runde deler ingen fast
|
||
navigasjon.
|
||
|
||
**Fem load-bærende beslutninger bekreftet av bruker** (se ADR-040 for full
|
||
tekst): (A) rundeoppsettet blir én 5-stegs veiviser, (B) HCP-prosent alltid
|
||
justerbar + egen Match-HCP-bryter (`use_matchplay_handicap`), (C)
|
||
"Spillere og runde" (inkl. flighter) blir værende på oppsettsiden, ikke i
|
||
den nye fane-raden — plassering av vei-tilbake/fullfør/slett overlatt til
|
||
V0, (D) Scorekort+Statistikk slås sammen til ÉN side (statistikk under
|
||
scorekortet) — fane-raden blir 3 faner, ikke 4, (E) et reelt, tidligere
|
||
udokumentert funn: spillere i scorekortet var ALDRI gruppert lagvis (ren
|
||
innsettingsrekkefølge) — fikset med stabil sortering på `round_side_id`.
|
||
|
||
**Status per 2026-08-02:**
|
||
1. ✅ Backend: migrasjon `051_round_allowance_override.sql` +
|
||
`_round_allowance_override()`-kobling i `app/routers/rounds.py`.
|
||
Scratch- og produksjonsverifisert (se ADR-040 for tallene).
|
||
2. ✅ Kode-fiks: lag-sortering i `ScorecardGrid` (`round-detail.tsx`) og
|
||
`MatchScorecardGrid` (`round-scorecard.tsx`). Browserverifisert mot
|
||
brukerens eget rapporterte bug-scenario (interleaved lag-tilføyelse).
|
||
3. ✅ **V0-prompt 1 BYGGET, VERIFISERT OG LIVE (2026-08-02):**
|
||
`frontend/components/new-round.tsx` er nå den samlede 5-stegs
|
||
veiviseren (zip 25 fra V0, flettet med ekte logikk — bane-søk/
|
||
-opprettelse, `/people/search`, `allowance_override`-konstruksjon,
|
||
full innsendingsorkestrering runde→sider→medspillere→tildelinger).
|
||
Full detalj (inkl. de to reelle feilene funnet under integrering) i
|
||
ARCHITECTURE_DECISIONS.md (ADR-040).
|
||
4. ✅ **V0-prompt 2 BYGGET, VERIFISERT OG LIVE (2026-08-02):** ny delt
|
||
header/fane-rad (`components/round-header.tsx`, tatt inn uendret fra
|
||
V0 zip 26, presentasjon-only) + ny `components/round-page-shell.tsx`
|
||
(ekte datalag/handlinger) — koblet inn på alle tre destinasjonene.
|
||
Scorekort+Statistikk slått sammen til ÉN side (ren kode-sammenstabling
|
||
av to eksisterende komponenter, ikke sendt til V0 — bevisst utenfor
|
||
prompt 2s omfang). "Spillere og runde"/Fullfør/Slett nås via en
|
||
"Administrer"-dialog i headeren. Full detalj (inkl. den ene reelle
|
||
feilen funnet — to `<main>`-landemerker på samme side) i
|
||
ARCHITECTURE_DECISIONS.md (ADR-040).
|
||
5. ✅ Integrering + browserverifisering + utrulling — ferdig samme dag.
|
||
|
||
**Bevisst IKKE dekket av V0-prompt 2, fortsatt et åpent spørsmål:**
|
||
Beslutning E sitt spørsmål om lag-grupperingen (sortering fikset i
|
||
steg 2) trenger mer enn celle-fargelegging for å skille lagene tydelig
|
||
nok i scorekortet — prompt 2 ble bevisst smalere i omfang enn først
|
||
planlagt (kun selve header/fane-chrome-en), så dette spørsmålet er
|
||
IKKE stilt til V0 ennå. Egen, senere, liten vurdering om ønskelig.
|
||
|
||
---
|
||
|
||
## Konkurranseklasser ("Damer fra 44, Herrer fra 50+") — ✅ HELT FERDIG, BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-04 (ADR-041)
|
||
|
||
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+. Full
|
||
plan-modus-runde med to `AskUserQuestion`-avklaringer FØR bygging (samme
|
||
disiplin som ADR-037/039) — se ADR-041 for de fire load-bærende
|
||
beslutningene (fritt navngitte klasser, delt tabell mellom begge
|
||
turneringstyper, standardutslag som ren frontend-bekvemmelighet, og —
|
||
den mest substansielle avklaringen — egen resultatliste KUN i
|
||
individuelle turneringer, aldri i lagturneringer siden poeng der er
|
||
knyttet til hele kamper).
|
||
|
||
Kort: ny `tournament_class`-tabell (migrasjon 053, org-isolert RLS) +
|
||
nullable `class_id` på `team_roster`/`tournament_participant` (`ON
|
||
DELETE SET NULL`). Organisator oppretter klasser med navn + valgfritt
|
||
standardutslag i et nytt "Klasser"-kort (BÅDE `tournament-detail.tsx` og
|
||
`individual-tournament-detail.tsx`). Standardutslaget forhåndsvelges
|
||
automatisk når en spiller med klasse legges til en match/runde (fortsatt
|
||
overstyrbart) — INGEN endring i selve `tee_id`-kontrakten, som allerede
|
||
var required og satt per deltaker. I individuelle turneringer deler
|
||
`individual_leaderboard` seg nå i egne, riktig rangerte seksjoner per
|
||
klasse; i lagturneringer er klasse bevisst KUN et utslag-forslag, ingen
|
||
leaderboard-endring (bekreftet med bruker — matchpoeng er lag-mot-lag,
|
||
ikke enkeltspiller-atomisk).
|
||
|
||
**Scratch-/browserverifisert grundig** (isolert `teecup_app_scratch`-
|
||
rolle + engangs API-/frontend-container, ekte nettleser-innlogging inkl.
|
||
2FA): full API-rundtur, ekte klikk gjennom BEGGE turneringstyper —
|
||
kaskaderende bane→utslag-velger ved klasse-opprettelse, dropdown-
|
||
klassetildeling, utslag-forhåndsutfylling bekreftet i `AddSlotForm` OG
|
||
`AssignRoundParticipantControl`, og det klasse-delte leaderboardet
|
||
tall-for-tall bekreftet korrekt (Damer: Kari 72 slag rang 1, Siv 108
|
||
slag rang 2; Herrer: Ola 90 slag rang 1 — alle mot håndregnet
|
||
slagsum). 98/98 eksisterende `handicap_engine.py`-enhetstester uendret
|
||
(ingen ny motorlogikk), `test_isolation.sql` 12/12 uendret. Ett reelt
|
||
TypeScript-funn under selve verifiseringen (ikke antatt riktig fra
|
||
implementasjonen alene): `classes`-propen manglet på `TeamColumn` i
|
||
`session-blind-draw.tsx` (egen underkomponent, arver ikke overordnet
|
||
komponents state automatisk) — fanget av produksjonsbuildens
|
||
typesjekk, rettet før utrulling.
|
||
|
||
**Rullet ut live 2026-08-04**, bruker bekreftet eksplisitt: migrasjon
|
||
053 mot ekte `teecup_db`, `docker compose up -d --build teecup_api
|
||
teecup_frontend`. Full detalj i CHANGELOG.md 2026-08-04 og ADR-041.
|
||
|
||
---
|
||
|
||
## Augusta-stil resultattavle for slagspill-turneringer — ✅ HELT FERDIG, BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-04
|
||
|
||
Brukeren viste et bilde av en fysisk leaderboard-tavle fra Augusta
|
||
National (POS/PLAYER/TODAY/THRU/TOTAL/R1-R4) og ba om samme oppsett for
|
||
individuelle turneringer med brutto/netto/stableford-scoring — lederen
|
||
alltid øverst, valgfri automatisk rulling, må se bra ut på storskjerm
|
||
(ikke bare mobil). Bygget via V0 (zip 29) etter etablert mønster: ren
|
||
visuell komponent med mock-data først (`stroke-play-leaderboard.tsx`),
|
||
ekte backend-kobling etterpå. Ny `Tone`-tredeling ("under"/"even"/"over")
|
||
rettet inn i komponenten ved integrering — V0s eksport hadde ikke fått
|
||
med seg appens egen "E → ren tekst, ingen ramme"-regel for rundekolonnene
|
||
presist nok (kun for TODAY/TOTAL, ikke R1-Rn).
|
||
|
||
Backend (`individual_tournaments.py`, `individual_leaderboard`-
|
||
endepunktet) utvidet med POS/TODAY/THRU/TOTAL/runde-for-runde-data — kun
|
||
populert for brutto/netto/stableford, ingen endring for København/BBB.
|
||
"I dag" utledes som runden med høyest sekvensnummer noen har påbegynt
|
||
(ingen eksplisitt aktiv-runde-markering finnes eller trengs).
|
||
|
||
**Reell rangeringsbug funnet og rettet under scratch-verifiseringen**
|
||
(ikke antatt riktig fra koden alene): den eksisterende sorteringen
|
||
rangerte på RÅ slagtotal/poengtotal — riktig for én runde, men i en
|
||
flerrunde-turnering med deltakere på ulike stadier (noen midt i runde 2,
|
||
andre ikke startet den) er rå sum ikke sammenlignbar. En deltaker med
|
||
færre spilte hull kunne rangere FORAN den faktiske lederen kun fordi det
|
||
rå tallet var lavere. Rettet ved å innføre en til-par-normalisert
|
||
rangeringsnøkkel brukt til BÅDE sortering og uavgjort-håndtering.
|
||
|
||
Ingen migrasjon (rene response-felt-tillegg). Scratch-verifisert grundig
|
||
med et bevisst konstruert flerrunde-scenario (ekte uavgjort i toppen, én
|
||
spiller midt i runde, én ikke startet runde 2) — alle håndregnede tall
|
||
bekreftet eksakt riktige, både API-JSON og ekte nettleser på 390px mobil
|
||
og 1920px storskjerm. Full detalj i CHANGELOG.md 2026-08-04.
|
||
|
||
---
|
||
|
||
## Baneoppsett i turneringer: rekkefølge, TeeOff-import og delt bane-mal-bibliotek — ✅ HELT FERDIG, BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-04 (ADR-042, migrasjon 054)
|
||
|
||
Brukeren oppdaget at "Klasser" (med Standardutslag-velgeren, ADR-041)
|
||
vises FØR bane-/rundeoppsett i turneringsmodulen — meningsløst å velge
|
||
utslag for en klasse før man vet hvilke utslag banen har. Samtidig
|
||
bemerket hen at TeeOff-baneimport ikke var tilgjengelig i
|
||
turneringsmodulen, og ba om en ny funksjon: "bruk en eksisterende
|
||
TeeOff-bane som mal" ved manuell baneoppretting, i BÅDE
|
||
turneringsmodulen og single-runde-modulen.
|
||
|
||
**Del I (rekkefølge + TeeOff-import-gap):** `ClassesCard` flyttet til å
|
||
vises etter runde-/baneoppsett i begge turneringstyper (individuelle:
|
||
etter `RoundsCard` i samme fane; lagturneringer: flyttet fysisk fra
|
||
"Lag og spillere"-siden til "Program"-siden). TeeOff-import
|
||
(`OfficialCourseSearch`-mønsteret) koblet inn i den individuelle
|
||
turneringsflyten, som manglet det helt fra før.
|
||
|
||
**Del II (mal-basert baneoppretting + delt bibliotek, ADR-042):** avdekket
|
||
under planlegging at turneringsmodulens "opprett manuell bane" i praksis
|
||
var ubrukelig (kun et navnefelt, ingen hull-/utslag-skjema fantes noe
|
||
sted der). Bygget: et nytt, kompakt hull-/utslag-editorskjema for
|
||
turneringsmodulen; en malvelger (fra bunnen av / TeeOff-bane som mal /
|
||
offentlig custom-bane som mal); gjenbruk av `personal_course`
|
||
(020_personal_rounds.sql) som et allerede-globalt, plattform-omfattende
|
||
bane-bibliotek brukerens egen oppfølging ba om ("disse banene bør lagres
|
||
og være offentlige") — se ADR-042 for de fem arkitektoniske
|
||
beslutningene (delt tabell fremfor ny, plattform- IKKE org-omfattende
|
||
synlighet, applikasjonslags-eierskap, fork-ved-fremmed-redigering +
|
||
egen dupliser-handling, engangs-kopi ingen vedvarende kobling). Ny
|
||
"Mine baner"-administrasjon i kontoinnstillinger (rediger/dupliser/
|
||
slett egne publiserte baner).
|
||
|
||
Reell nestet-`<form>`-HTML-bug funnet og rettet UNDER scratch-
|
||
verifisering (ikke antatt riktig fra koden alene) — se CHANGELOG.md
|
||
2026-08-04 for full detalj. 34/34 håndregnede API-sjekker + grundig
|
||
ekte nettleser-verifisering (to brukere, to organisasjoner) bestått,
|
||
`test_isolation.sql` 12/12 uendret.
|
||
|
||
**Bevisst utenfor omfang:** ingen moderasjon/vetting av offentlige
|
||
baner; ingen privat/kun-min-org-synlighet per bane (alt publisert via
|
||
denne veien er plattform-offentlig).
|
||
|
||
---
|
||
|
||
## Order of Merit (sesong-sammenlagt rangering) — ✅ SPILLER-OOM BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-04 (ADR-043, migrasjon 055). Eclectic + lag-OOM 📋 GJENSTÅR
|
||
|
||
Brukeren ba om en vurdering av GolfBox sin Order of Merit-funksjon og
|
||
tok den til en full designrunde. Bygget denne runden: skjema (fire nye
|
||
tabeller), motor (`order_of_merit_points_for_position`/`_aggregate`),
|
||
full CRUD + "regn ut ved lesing"-leaderboard for SPILLER-OOM med alle
|
||
fem resultattyper (poeng/Stableford/brutto/netto/pengeliste), sum/snitt-
|
||
aggregering, og behold-N-beste/minimum-resultater/aldersgrense-grenser.
|
||
Håndkodet frontend (liste + detalj) siden brukeren gikk tom for
|
||
V0-credits midt i planleggingen av frontend-tilnærmingen. Se ADR-043
|
||
for de fem arkitektoniske beslutningene og CHANGELOG.md 2026-08-04 for
|
||
full bygge-/verifiseringsdetalj (63/63 håndregnede API-sjekker + RLS-
|
||
isolasjon + ekte nettleser-gjennomgang).
|
||
|
||
**Gjenstår fra opprinnelig plan (bevisst utsatt, ikke glemt):**
|
||
- **Eclectic-aggregering PÅ TVERS AV LENKEDE TURNERINGER (OOM-nivå)**
|
||
("drømmerunde" -- beste resultat PER HULL på tvers av ALLE lenkede
|
||
turneringer en spiller deltok i gjennom sesongen, kun for
|
||
resultattype Stableford/brutto/netto). **IKKE det samme som** det
|
||
Eclectic-formatet som ble bygget 2026-08-14 (ADR-068, migrasjon
|
||
072) -- det er scoping til RUNDENE INNENFOR ÉN ENKELT turnering
|
||
(`tournament.scoring_method = eclectic_*`), ikke på tvers av flere
|
||
turneringer i en OOM-sesong. Motoren (`eclectic_best_per_hole()` i
|
||
handicap_engine.py) er generell nok til trolig å kunne GJENBRUKES
|
||
direkte for OOM-varianten også -- selve per-hull-datainnsamlingen på
|
||
tvers av turneringer (ikke bare runder) er fortsatt ubygget og
|
||
krever egen design-/verifiseringsrunde.
|
||
- **Lag-OOM sin faktiske leaderboard-beregning.** Skjema
|
||
(`order_of_merit_team`/`_team_member`) og prinsippet (sesong-par satt
|
||
opp DIREKTE i OOM-en, summerer/velger-beste-N-av medlemmenes allerede
|
||
beregnede individuelle OOM-resultater -- IKKE strengmatching på
|
||
turnering-lag, se ADR-043 Beslutning C) er avklart og bygget inn i
|
||
skjemaet, men selve `GET .../leaderboard`-grenen for `kind='team'`
|
||
finnes ikke ennå (avviser eksplisitt med en tydelig feilmelding).
|
||
Trenger CRUD-endepunkter for lag/medlemmer (ingen finnes ennå) pluss
|
||
selve aggregering-oppå-aggregering-logikken.
|
||
- **Offentlig/delt visning.** `public_visible`-feltet finnes og er
|
||
redigerbart i innstillinger, men ingen egen, ikke-innlogget-tilgjengelig
|
||
visning er bygget (kun den vanlige org-scopede detaljsiden).
|
||
|
||
---
|
||
|
||
## Scramble mot enkeltspiller — ✅ BEGGE VARIANTER (slagspill + matchspill) BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-05 (migrasjon 056+057). Org-lagturneringer 📋 GJENSTÅR
|
||
|
||
Brukeren ba om et nytt, niende turneringsformat: et scramble-lag
|
||
(variabel størrelse, N>=2, delt ball) mot ÉN individuell spiller (egen
|
||
ball). Første ASYMMETRISKE to-siders-format i prosjektet -- de åtte
|
||
tidligere formatene (se seksjonen over) har alle SAMME struktur på
|
||
begge sider.
|
||
|
||
**Bygget denne runden (kun frittstående runder, kun slagspill-
|
||
sammenligning):** skjema (migrasjon 056: `round.scramble_solo_
|
||
individual_pct`, `round_side.side_role`), motor (`TeamAverage`,
|
||
`net_stroke_play_margin` -- 8 nye enhetstester, 115/115 totalt), full
|
||
API-wiring i `app/routers/rounds.py` (asymmetrisk sidekapasitet,
|
||
konfigurerbar individuell allowance, `_build_scramble_solo_result`).
|
||
Frontend ALT promptet til V0 (brukerens eksplisitte instruks denne
|
||
runden) -- ny `ScrambleSoloResultView`-komponent + `ScrambleVsSoloSides`-
|
||
sideoppsett i opprett-runde-veiviseren, integrert (ikke blindt kopiert)
|
||
mot de eksisterende API-koblede filene. Se CHANGELOG.md 2026-08-05
|
||
(punkt 22) for full bygge-/verifiseringsdetalj, inkl. to separate
|
||
scratch-verifiseringsrunder (backend alene, deretter full stack med
|
||
ekte nettleser).
|
||
|
||
**Kildesjekk før design:** ingen av de tre HCP-PDF-ene (se CLAUDE.md)
|
||
dekker lag-vs-individuell eller vilkårlig lagstørrelse -- lagets
|
||
Playing Handicap (`TeamAverage`, rent snitt) er derfor en bevisst NY
|
||
TeeCup-regel, bekreftet eksplisitt med bruker, ikke kildebelagt slik
|
||
`scramble_2`/`scramble_4` sine `RankedSplit`-tabeller er.
|
||
|
||
**Bevisst utenfor omfang, fortsatt (ikke glemt):**
|
||
- **Org-lagturneringer** (`session.format`) -- kun frittstående runder
|
||
nå. `public-live.tsx` sin `FORMAT_LABELS` (som nøkler på
|
||
`session.format`) fikk derfor bevisst IKKE formatet lagt til.
|
||
- Ingen kobling til Order of Merit for dette formatet.
|
||
|
||
**Matchspill-varianten (`scramble_solo_match`) bygget samme dag,
|
||
2026-08-05 (migrasjon 057):** samme lag-mot-individuell-idé, avgjort
|
||
hull-for-hull (`match_play_strokes`/`compute_match_state`, begge
|
||
gjenbrukt uendret) i stedet for netto slagspill-totalsum. Ren
|
||
CHECK-migrasjon (ingen nye kolonner). Frontend gjenbruker
|
||
`TwoSidedBoard`/`LeadZone`/oppsett-UI-en fra slagspill-varianten helt
|
||
uendret (bekreftet med bruker, ingen ny V0-prompt for denne
|
||
varianten) -- kun én liten, dedikert (ikke-V0) gren i
|
||
`MatchScorecardGrid` for én rad per lag-side.
|
||
|
||
**Kritisk regresjonsbug funnet OG rettet samme dag, FØR denne
|
||
utrullingen:** `round-detail.tsx` sin "Score"-fane rutet feilaktig et
|
||
klikk på en LAGSPILLERS scorekort til per-deltaker-veiviseren i stedet
|
||
for den delte lag-veiviseren -- satt seg fast i en evig spinner, uten
|
||
feilmelding. Rammet BEGGE variantene (var allerede live og ødelagt for
|
||
ekte lagspillere på slagspill-varianten siden 2026-08-05 tidligere
|
||
samme dag, inntil denne rettelsen). Se CHANGELOG.md punkt 23 for full
|
||
rotårsak/fiks-detalj.
|
||
|
||
**Rullet ut live 2026-08-05** (begge varianter + bug-fiksen, samme
|
||
utrulling), bruker bekreftet eksplisitt.
|
||
|
||
---
|
||
|
||
## Tilskuer-visning (`/watch/[id]`): tre faner + "Spillere og runde" — ✅ HELT FERDIG, BYGGET, SCRATCH-VERIFISERT OG LIVE 2026-08-06 (ADR-045)
|
||
|
||
To bugger fanget opp samme dag (video-illustrasjon fra bruker): "Venner på
|
||
banen" på dashbordet lenket til feil rute (`/my-rounds/{id}` i stedet for
|
||
`/watch/{id}`), og selve `/watch/[id]` var kun ÉN enkelt side (kompakt
|
||
status + leaderboard/matchstatus, kun 8 av 17 formater dekket). Begge
|
||
rettet -- se ADR-045 for full detalj. Tilskueren får nå samme tre faner
|
||
som eieren (Score/Scorekort/Leaderboard, ekte full formatparitet via
|
||
`RoundScorecard`/`RoundLeaderboard` sin nye `publicMode`-prop) og
|
||
"Spillere og runde" (deltakerliste, HCP/utslag/tildelte slag, flight-
|
||
gruppering via et nytt `GET /public/rounds/{id}/flight-group`-endepunkt),
|
||
uten noen av eier-handlingene (Rediger/Fullfør/Slett runde, +Medspiller).
|
||
|
||
**Rullet ut live 2026-08-06**, bruker bekreftet eksplisitt. Se
|
||
CHANGELOG.md 2026-08-06 for full byggelogg.
|
||
|
||
---
|
||
|
||
## Bevisst endret fra opprinnelige (Gemini-)råd
|
||
|
||
- 🔀 **Banedata:** API mot teeoff (ADR-004), IKKE direkte delt database. Direkte
|
||
DB-kobling ville låst TeeCup til teeoffs skjemaendringer.
|
||
- 🔀 **Handicap-motor:** egen testet Python-modul, ikke den innlimte JS-funksjonen
|
||
(som bl.a. ikke håndterte 9-hull eller konfigurerbare allowances korrekt).
|
||
- 🔀 **Tenant-modell:** organisasjon som tenant med RLS, ikke bare «turnering-ID».
|
||
- 🔀 **Sesjons-secret:** egne secrets for TeeCup, ikke fallback til teeoffs
|
||
(teeoff selv bruker en slik fallback — bevisst unngått her).
|
||
|
||
## Visuell redesign-utforskning — parallell `/logg-inn`-side (V0), 2026-08-07
|
||
|
||
Brukeren er lite fornøyd med appens visuelle uttrykk generelt, og ba om
|
||
en test: gi V0 en ny chat med (1) et fyldig merkevare-/personlighets-
|
||
brief om hva TeeCup er og skal bli, (2) en presis, mekanisk teknisk
|
||
spesifikasjon av hva innloggingsskjermen faktisk må inneholde/gjøre
|
||
(hentet fra ekte kode, `login-form.tsx`/`verify-form.tsx` — fire
|
||
tilstander: magic-link, lenke-sendt, passord-fallback, bli-med-via-kode),
|
||
og (3) selve logoen, uten noen ytterligere beskrivelse av den — bevisst
|
||
IKKE bundet til dagens låste "Forest Green"-palett, for å se om et friskt
|
||
blikk gir noe bedre. V0-prompten er skrevet av Claude, sendt til bruker
|
||
for innliming i en NY, egen V0-chat (samme "Frontend via V0"-arbeidsdeling
|
||
som ellers, men her eksplisitt uten dagens design-tokens som ledetekst).
|
||
|
||
**Resultat:** V0 leverte en selvstendig, fungerende sluttbrukerflyt på
|
||
`app/logg-inn/page.tsx` + `components/teecup/{teecup-auth,wordmark}.tsx`
|
||
— alle fire tilstander bygget med ekte klientsidig validering/cooldown/
|
||
feilhåndtering (mocket backend internt, siden dette er en isolert
|
||
design-utforskning, ikke koblet til ekte `/auth/*`-endepunkter ennå).
|
||
Egen ordmerke tegnet på nytt som rene SVG-baner fra den opplastede
|
||
logoen ("Tee" i grønt, "Cup" i oransjerødt), og en egen, SKOPET
|
||
"clubhouse"-fargepalett (`--tee-strong: #2f6b1e`, `--cup-strong:
|
||
#cf3c17`, varm off-white bakgrunn) — en helt annen visuell retning enn
|
||
dagens låste palett.
|
||
|
||
**Integrert av Claude som parallell, isolert rute** (`/logg-inn`, IKKE en
|
||
overskriving av `/`): kopiert inn uendret, "clubhouse"-fargetokens lagt
|
||
til ADDITIVT nederst i `app/globals.css` (skoper kun til denne siden,
|
||
rører ingen eksisterende tokens). Typesjekket rent, ekte produksjonsbuild
|
||
kjørt og bekreftet ren, alle fire tilstander browserverifisert i en
|
||
isolert forhåndsvisnings-container (ingen konsollfeil).
|
||
|
||
**Rullet ut live 2026-08-07**, bruker bekreftet eksplisitt ("Ja takk"):
|
||
`docker compose up -d --build teecup_frontend`. Begge containere boot-et
|
||
rent, `/logg-inn` → 200, `/` (dagens innlogging, uendret) → 200 fortsatt.
|
||
Nåbar på `teecup.golf/logg-inn` for sammenligning på ekte enhet.
|
||
|
||
**✅ BESLUTTET OG RULLET UT 2026-08-08:** bruker valgte denne retningen.
|
||
`teecup-auth.tsx` koblet til de ekte `/auth/request-link`/`/auth/verify-
|
||
link`/`/auth/login-password`/`/public/tournaments/by-code`-endepunktene
|
||
(samme kontrakt som gamle `login-form.tsx` brukte). `/logg-inn` er nå den
|
||
ekte innloggingssiden — `app/page.tsx` (root) er en tynn videresending
|
||
dit (beholdt for gamle bokmerker), gamle `LoginForm`/Forest Green-skjemaet
|
||
fjernet. Se CHANGELOG.md 2026-08-08 for byggelogg.
|
||
|
||
**Bevisst avgrenset omfang (bekreftet med bruker 2026-08-08):** kun
|
||
innlogging/auth-flyten fikk den nye "clubhouse"-paletten. Resten av appen
|
||
(dashbord, scorekort, runde-visning osv.) beholder dagens etablerte
|
||
palett (se DESIGN_SYSTEM.md) inntil videre — retemaes gradvis, skjerm for
|
||
skjerm, når de likevel røres i senere runder. Ingen egen stor
|
||
retemaings-runde er igangsatt eller bedt om.
|