Fullfører ADR-098. Ny components/public-individual-live.tsx (V0-bygget,
presentasjonslaget uendret fra eksporten) koblet til de ekte endepunktene
fra forrige commit. Rettet en reell modellmismatch underveis: V0-mock-en
modellerte hver runde som sitt eget separate leaderboard, den ekte
backend-beregningen er ett samlet, kumulativt turnering-leaderboard --
rundevelgeren styrer nå kun hvilken rundes hull-for-hull som lastes.
Ny offentlig GET .../rounds/{round_id}/participants/{tournament_participant_id}
/holes for hull-for-hull-utvidelsen (egen rute, ikke gjenbruk av
frittstående-runders tilsvarende -- ulik datamodell).
app/t/[id]/live/page.tsx avgjør nå format SERVER-side før noe rendres,
unngår klient-side-feilklassen fra forrige commit helt for denne siden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4879 lines
296 KiB
Markdown
4879 lines
296 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-21 (funksjonelle hull funnet under utskrift-
|
||
> planlegging notert -- hullengde, turneringslogo, bracket-format
|
||
> utsatt, shotgun-start under bygging -- se de to nederste seksjonene)
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
**✅ BYGGET 2026-08-21 (ADR-098)** — `/t/[id]/live` forgrener seg nå
|
||
server-side på `format_type`, ny `PublicIndividualLive`-side (V0-bygget)
|
||
med rundevelger + samlet leaderboard + hull-for-hull, samme sanntids-
|
||
kanal som Cup-siden. Se ARCHITECTURE_DECISIONS.md for full detalj,
|
||
CHANGELOG.md for byggelogg. IKKE rullet ut ennå.
|
||
|
||
**✅ 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 — ✅ HELT FERDIG, BYGGET OG LIVE (ADR-072 2026-08-15, relokert + full historikk med grafer ADR-079 2026-08-16)
|
||
|
||
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.
|
||
|
||
**RETTELSE 2026-08-16 — "📋 NOTERT, IKKE bygget" var stale.** Bygget som
|
||
ADR-072 (2026-08-15): `app/hole_history.py`, ny `GET .../holes/{n}/
|
||
history`, aggregerer PÅ TVERS AV BÅDE frittstående runder OG
|
||
org-turneringer (bredere enn notatets opprinnelige "kun frittstående
|
||
runder"-forslag), banebro via `teeoff_facility_slug`/`teeoff_course_id`
|
||
(ikke navn-snapshotet notatet foreslo). Siden utvidet videre 2026-08-16
|
||
(ADR-079): historikken flyttet UT av selve slagvinduet/-registrerings-
|
||
skjermen til skjermen før, og et klikk åpner nå FULL historikk med en
|
||
score-fordelingsgraf (samme stolpe-mønster som `round-stats.tsx`), ikke
|
||
bare en kort oppsummering.
|
||
|
||
## 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).
|
||
**RETTELSE 2026-08-18 — "Gjenstår"-lista under var stale, alt i den er
|
||
nå ferdig:** `ShotMeasurementSheet` V0-integrert og LIVE (`shot/map-
|
||
point-picker.tsx` er i dag en ferdig, produksjonsbrukt "klikk et punkt
|
||
på kartet"-komponent, bekreftet gjenbrukbar i "Manuell koordinat-
|
||
editor"-notatet lenger ned i denne filen), ekte Mapbox-token satt,
|
||
migrasjon 060 rullet ut mot ekte `teecup_db`. Byggekjeden fortsatte
|
||
dessuten videre til rangefinder-avstand til faste banepunkter
|
||
(ADR-064/065, ADR-081) og et visuelt hull-diagram (ADR-083/084,
|
||
2026-08-17/18) -- hele "avstandsmåling ligger i kortene"-ambisjonen
|
||
denne brainstorm-tråden startet er nå bygget og live, ikke lenger et
|
||
åpent spørsmål. ~~Opprinnelig "Gjenstår"-liste, beholdt for
|
||
sporbarhet:~~ V0-eksport for selve `ShotMeasurementSheet`, kobling av
|
||
inngangspunktene i `round-detail.tsx`, reelle Mapbox-token i `.env`,
|
||
ekte nettleserverifisering av kostnadskontroll-invarianten (ADR-048
|
||
Beslutning B), utrulling til ekte database/containere.
|
||
|
||
**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) ✅ BYGGET OG LIVE 2026-08-17 (ADR-080)
|
||
|
||
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) — ✅ BYGGET OG LIVE 2026-08-17
|
||
|
||
**RETTELSE 2026-08-18 — "📋 NOTERT, IKKE designet" var stale.** Bygget
|
||
som ADR-080 (se ARCHITECTURE_DECISIONS.md): selvbetjent, "keeper"
|
||
(initiativtaker, overlever) vs. "taper" (målet, slås inn og slettes),
|
||
lenke sendt til MÅLETS e-post for å bevise eierskap (samme prinsipp
|
||
som e-postbytte, ADR-032 Beslutning B), ny `account_merge_token`-
|
||
tabell (migrasjon 077). Løser presist spørsmålet under (hva skjer når
|
||
adressen som legges til allerede eies av en annen konto) -- avviser
|
||
IKKE lenger med 409, tilbyr i stedet ekte sammenslåing.
|
||
|
||
---
|
||
|
||
## 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) — ✅ HELT FERDIG, ALLE TRE DELER BYGGET OG LIVE (spiller-OOM 2026-08-04 ADR-043/migrasjon 055; lag-OOM + eclectic på tvers av turneringer + offentlig visning 2026-08-15/16, ADR-074/075/076)
|
||
|
||
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).
|
||
|
||
**RETTELSE 2026-08-16 — "Eclectic + lag-OOM 📋 GJENSTÅR" var stale.** Alle
|
||
tre punktene under ble tatt i én samlet runde 2026-08-15/16:
|
||
- **Eclectic-aggregering PÅ TVERS AV LENKEDE TURNERINGER (OOM-nivå)** --
|
||
✅ BYGGET (ADR-075). `eclectic_best_per_hole()` fra `handicap_engine.py`
|
||
gjenbrukt UENDRET, som antatt her -- kun datainnsamlingen på tvers av
|
||
turneringer var ny. Samme-bane-håndheving lagt til (avviser lenking/
|
||
modus-bytte som ville gjort eclectic meningsløst på tvers av ulike
|
||
baner).
|
||
- **Lag-OOM sin faktiske leaderboard-beregning** -- ✅ BYGGET (ADR-074).
|
||
Full CRUD for lag/medlemmer + `GET .../leaderboard`-grenen for
|
||
`kind='team'`, nøyaktig prinsippet som var avklart (ETT lagret
|
||
aggregeringsvalg styrer begge nivåer, ingen egen lag-konfig).
|
||
- **Offentlig/delt visning** -- ✅ BYGGET (ADR-076, migrasjon 076). Ny
|
||
SECURITY DEFINER-bro + `/public/order-of-merits/{id}`-endepunkt + ny
|
||
uautentisert side, samme anti-enumereringsmønster som resten av appens
|
||
offentlige sider.
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
## NGF spilleformer vs. det TeeCup faktisk har bygget — 📋 NOTERT, IKKE besluttet hva som skal bygges
|
||
|
||
Bruker lastet opp NGF sitt offisielle "Spilleformer"-dokument (sist
|
||
endret 4. oktober 2023, `Temp-uploads/spilletyper-og-spilleformer-2023
|
||
(1).pdf` — identisk med den tidligere opplastede versjonen samme navn
|
||
uten "(1)") 2026-08-17 og ba om en sammenligning mot alt TeeCup faktisk
|
||
har bygget. Sjekket mot de LIVE CHECK-constraintene i `teecup_db` (ikke
|
||
bare dokumentasjon): `round.play_format`, `session.format`,
|
||
`tournament.scoring_method`.
|
||
|
||
**Dekket** (rett frem eller under annet navn): Single/match, Foursome,
|
||
Four-Ball, Greensome, Scramble (2-/4-manns + egen solo-variant utover
|
||
NGF), Individuell slagspill, Stableford, Eclectic, Flaggkonkurranse,
|
||
Københavner, Nassau (bygget som poeng-panel oppå en singel-match, ikke
|
||
egen format-verdi), Chapman (= NGFs "Irish Greensome", samme mekanikk
|
||
annet navn), High-low-high (= NGFs "Four-ball beste og dårligste").
|
||
Skins/Bingo Bango Bongo/Money Ball/Shamble er TeeCup-tillegg UTOVER
|
||
NGF-lista (ikke i dokumentet i det hele tatt).
|
||
|
||
**Ikke dekket -- ekte hull i NGF-lista, ingen omtale andre steder i
|
||
denne filen:**
|
||
- **Amerikaner** -- 4 spillere, 12 poeng/hull fordelt etter plassering
|
||
(8-4-0-0 / 6-6-0-0 / 4-4-4-0 / 3-3-3-3 osv. avhengig av utfall)
|
||
- **Four-ball sammenlagt** / **Four-ball besteball og sammenlagt** --
|
||
enklere lag-sammenlagt-varianter (kun best-ball-varianten er bygget)
|
||
- **Hallington** -- poengformel (hullets par × 2 minus brukte slag)
|
||
- **Hidden hole** -- et forhåndsbestemt antall tilfeldige hull trekkes
|
||
ut og telles etter at hele runden er spilt
|
||
- **Ransome** -- 3×6-hulls rotasjon: fourball → foursome → sammenlagt
|
||
- **Mexican Scramble** -- scramble-variant der spilleren hvis slag ble
|
||
valgt ikke får slå neste slag
|
||
- **Mulligan** -- HCP-baserte omslag, kan tas når som helst
|
||
- **Snorkonkurranse** -- spilleren får en snor (lengde = spillehandicap
|
||
i meter) til å forbedre ballens leie med
|
||
- **Three-ball match / Threesome (match og slagspill)** -- ekte
|
||
3-spiller-matchformater (kan i praksis omgås i dag med 3 separate
|
||
1v1-matcher manuelt satt opp, men ikke som egen, sammenhengende
|
||
turneringsform)
|
||
- **Maksimum Score** som egen, distinkt konkurranseform (migrasjon 038
|
||
sin generelle "pickup"-mekanikk dekker DELER av dette, men ikke som
|
||
eget valgbart format med par+4-cap)
|
||
- **Bogey/Par-konkurranse** -- matchspill mot et fast target-resultat,
|
||
ikke stableford-poeng
|
||
- **Nærmest hullet / Lengste drive** -- vanlige side-konkurranser ved
|
||
siden av hovedturneringen, ingen støtte i det hele tatt (verken som
|
||
eget "mini-format" eller som en tilleggsmarkering på en runde/økt)
|
||
|
||
**Vurdert, men trolig ikke aktuelt å bygge:** Pro Am er en deltaker-
|
||
sammensetning (profesjonell + amatører), ikke en distinkt scoringsform
|
||
-- TeeCup har ingen pro/am-status-modell i dag, og det er uklart om det
|
||
gir egenverdi å bygge som eget format fremfor bare la brukeren sette opp
|
||
et vanlig lag.
|
||
|
||
**Neste steg:** ingen bygging igangsatt. Når/hvis bruker vil prioritere
|
||
noen av disse, bør de vurderes én om gangen (samme mønster som de åtte
|
||
formatene i punkt 14/CHANGELOG.md) -- flere av dem (Nærmest hullet/
|
||
Lengste drive spesielt) er trolig enklere å bygge enn et helt nytt
|
||
scoringsformat, siden de er sidespill uavhengig av selve rundens
|
||
hovedformat.
|
||
|
||
## Manuell koordinat-editor for rangefinder-punkter — 📋 NOTERT, IKKE besluttet/designet
|
||
|
||
Etter ADR-081 (migrasjon 079, rangefinder for offisielle baner) kan
|
||
koordinater for en offisiell (TeeOff-koblet) bane settes via `PUT /orgs/
|
||
{id}/courses/{id}/coordinates` -- men KUN via et rått API-kall (Claude
|
||
kjørte dette manuelt for Tjøme, to runder på rad). Bruker ba 2026-08-17
|
||
om en ekte selvbetjent vei: "et system for mulighet for å legge inn
|
||
koordinater manuelt, uten at jeg må spørre deg."
|
||
|
||
**Grunnlag allerede på plass, verifisert denne runden:**
|
||
- Skrive-endepunktet for OFFISIELLE baner finnes allerede (`courses.py`,
|
||
se ADR-081) -- mangler kun et menneskelig grensesnitt foran seg.
|
||
- GolfAPI-koblede PERSONLIGE baner (`personal_course`/`golfapi_course_
|
||
coordinate`) har derimot INGEN manuell skrive-vei i det hele tatt --
|
||
fylles i dag kun automatisk via `golfapi_cache.get_or_fetch_golfapi_
|
||
course()`. Bør trolig få en tilsvarende manuell overstyrings-/
|
||
tilleggs-vei, ikke bare offisielle baner.
|
||
- `MapPointPicker` (`frontend/components/shot/map-point-picker.tsx`,
|
||
Mapbox, brukt til slagmåling/ADR-048) er en FERDIG, produksjonsbrukt
|
||
"klikk et punkt på kartet og få lat/long tilbake"-komponent -- den
|
||
tyngste enkeltbrikken en koordinat-editor trenger finnes allerede,
|
||
trolig gjenbrukbar med moderat tilpasning fremfor å bygges fra bunnen.
|
||
- Selve TYPE-valget (bunker/vann/tre/landemerke/tee/green osv. +
|
||
front/midt/bak + venstre/senter/høyre) må fortsatt gjøres av et
|
||
menneske via et skjema/dropdown -- Tjøme-rundens "Voll"/"Bjella"-
|
||
erfaring viste konkret at fritekst-til-kategori IKKE kan automatiseres
|
||
pålitelig. Editoren bør altså være: velg bane/hull → klikk punkt på
|
||
kart (eller tast lat/long manuelt) → velg type/plassering/side fra
|
||
dropdown → lagre. Sannsynligvis også en tabell-/listevisning av
|
||
allerede lagrede punkter per hull (les via eksisterende `GET .../
|
||
coordinates`) med slette-/rediger-mulighet.
|
||
|
||
**Neste steg:** egen planrunde (ikke påbegynt) -- avklare om `GET`/`PUT`
|
||
trenger utvidelse for GolfAPI-baner, konkret UI-flyt (kart vs. rent
|
||
skjema, eller begge), og om dette skal være en frittstående admin-side
|
||
eller hektes inn i eksisterende baneoppsett-flyt i `courses.py`-relatert
|
||
UI (se "Baneoppsett i turneringer"-seksjonen over).
|
||
|
||
## Massimport av spillere til organisasjon/turnering (CSV) + redigerbar spillertabell — ✅ HELT FERDIG, BYGGET, SCRATCH-/BROWSERVERIFISERT OG LIVE 2026-08-17 (ADR-082)
|
||
|
||
**RETTELSE 2026-08-18 — "🔨 PÅBEGYNT" var stale.** Fullført samme dag:
|
||
nytt bulk-endepunkt, CSV-parsing+kolonnegjetting
|
||
(`player-import-panel.tsx`, orkestrering) + redigerbar tabell (V0-
|
||
eksportert `player-import-view.tsx`, "stor skjerm først") -- første
|
||
redigerbar-tabell-UI-mønster i kodebasen. Én reell integreringsbug
|
||
funnet og fikset i scratch (V0s kjønn-nedtrekk brukte female/male/
|
||
other, rå CSV-tekst måtte normaliseres ved kolonnetilknytningen). Se
|
||
ADR-082/CHANGELOG.md 2026-08-17 for full detalj.
|
||
|
||
Bruker ba om en måte å masseimportere spillere fremfor én-og-én, og
|
||
spurte konkret om en tabell der ALLE felt er redigerbare. Research denne
|
||
runden bekreftet: ingen bulk-endepunkt finnes (`POST /orgs/{id}/players`
|
||
er kun én-om-gangen, frontend gjør det i en løkke i dag), og INGEN
|
||
redigerbar-tabell-UI-mønster finnes noe sted i kodebasen (all redigering
|
||
i dag er skjema/modal-basert) -- begge deler er reelt nytt arbeid, ikke
|
||
gjenbruk av et eksisterende mønster. `player.user_id` kobles automatisk
|
||
til en ekte konto ved fremtidig innlogging via e-post (samme mekanisme
|
||
som selvregistrering), så importen selv trenger ikke håndtere
|
||
invitasjon -- bare opprette rader.
|
||
|
||
Se ADR (skrives når planen er ferdig) for design/omfang.
|
||
|
||
## Retro-oversettelse av eksisterende UI (nb/en) — 🔨 PÅBEGYNT 2026-08-19 (ADR-094), resten 📋 planlagt i batcher
|
||
|
||
Grunnmuren (`next-intl`, cookie-basert språkvalg, ingen URL-endring)
|
||
og to genuint nye flater (hjelpeside/FAQ, den sammenslåtte Spillere-
|
||
tabellen) er bygget og fylt ut på begge språk, se ADR-094. **De
|
||
resterende ~68 eksisterende komponentfilene (~2200 hardkodede norske
|
||
UI-strenger, kartlagt 2026-08-19) er IKKE rørt ennå** -- for stort til
|
||
å gjøre trygt i én sammenhengende runde. Foreslått batch-rekkefølge for
|
||
påfølgende, avgrensede økter (hver batch = egen runde med egen
|
||
verifisering, samme inkrementelle disiplin som resten av appen):
|
||
|
||
1. 📋 Innlogging/dashbord/navigasjon (`logg-inn`, `dashboard.tsx`,
|
||
`more-menu.tsx`, `bottom-nav.tsx` osv.) -- mest brukte flater,
|
||
høyest synlighet.
|
||
2. 📋 Turneringsoppsett-wizarden (ADR-092/093/094, nettopp bygget,
|
||
fortsatt fersk i minnet).
|
||
3. 📋 Scorekort/leaderboard -- spillerens kjerneopplevelse under selve
|
||
rundens gang.
|
||
4. 📋 Resten (spillerpool, order of merit, feed/venner,
|
||
kontoinnstillinger, presentasjon/sponsorer).
|
||
5. 📋 Backend-feilkoder (479 `app_error`-kall) -- dekkes LØPENDE
|
||
gjennom batch 1-4 (hver batch oversetter kodene den faktisk støter
|
||
på i sine egne skjermer), ikke som en egen runde. **Reell
|
||
arkitekturbegrensning, ikke bare rekkefølge:** dagens kodetaksonomi
|
||
(`VALIDATION_FAILED`/`OUT_OF_SCOPE`/`NOT_FOUND` osv.) er for
|
||
grovkornet -- samme kode brukes til dusinvis av innbyrdes ulike
|
||
meldinger på tvers av appen, så kode→tekst-oversettelse alene gir
|
||
feil resultat for de fleste faktiske feil. Krever enten finere
|
||
koder (egen backend-refaktorering, ikke påbegynt) eller at backend
|
||
selv blir locale-bevisst (bryter det bevisste "ingen backend-
|
||
endring for oversettelse"-valget i ADR-094). Feilmeldinger forblir
|
||
norsk uansett språkvalg inntil en av disse løsningene velges.
|
||
|
||
## Funksjonelle hull funnet under "design-dokumentasjon"-runden (2026-08-20) — 📋 ikke fikset, kun notert
|
||
|
||
**Kontekst:** bruker ba om en fullstendig funksjonell spesifikasjon av
|
||
ALLE skjermer i appen (dashbord + 13 områdedokumenter, publisert som
|
||
artefakter), til bruk som grunnlag for en designer. Målet var å
|
||
beskrive hva som FINNES, ikke å lete etter feil -- men research-
|
||
prosessen (full gjennomlesning av all frontend-kode, i noen tilfeller
|
||
også backend-autorisasjon) avdekket en del reelle, eksisterende
|
||
funksjonelle hull underveis. Disse er IKKE fikset -- kun fanget opp og
|
||
notert her, som egne, fremtidige, avgrensede runder. Hvert punkt
|
||
under er hentet direkte fra research denne runden, ikke gjettet.
|
||
|
||
- **Rundelisten (`/my-rounds`, `/my-rounds/course/[name]`):**
|
||
mislykkes selve hentingen, kan en feilmelding og en tilsynelatende
|
||
EVIGVARENDE "Laster runder…"-indikasjon vises SAMTIDIG -- ingen
|
||
tydelig avsluttet feiltilstand for brukeren å reagere på.
|
||
- **Varsler (`/my-notifications`):** "ingen varsler" og "henting av
|
||
varsler feilet" vises i dag med nøyaktig samme tomme tilstand --
|
||
brukeren kan ikke skille reell tomhet fra en faktisk feil.
|
||
- **Ny-runde-veiviseren, steg 2 (Spilleform):** minimums-spillerantall
|
||
for Skins ("minst 3") og Københavner ("nøyaktig 3") vises kun som
|
||
hjelpetekst -- IKKE håndhevet. Veiviseren lar brukeren fullføre og
|
||
opprette runden uansett faktisk spillerantall.
|
||
- **Ny-runde-veiviseren, steg 4 (Lag og rekkefølge):** minimum 2
|
||
spillere i "Laget" for scramble mot enkeltspiller er kun en
|
||
informativ statusindikator -- ingen faktisk sperre på å gå videre.
|
||
- **Ny-runde-veiviseren, steg 5 (Deling):** advarselen om at "0
|
||
vennekategorier valgt" betyr at ingen venner faktisk kan se runden,
|
||
hindrer IKKE innsending -- kun informativ.
|
||
- **Turnering (individuell), Scorekort-fanen -- rettighetsbegrensning
|
||
usynlig i UI:** backend (`app/routers/individual_tournaments.py`,
|
||
`app/team_authz.py`) begrenser hvem som kan REGISTRERE/ENDRE en
|
||
deltakers score/GPS-flagg til (a) deltakeren selv, eller (b)
|
||
org-eier/administrator -- et vanlig org-medlem kan altså IKKE
|
||
registrere for andre. Dette vises INGEN steder i grensesnittet før
|
||
et lagringsforsøk faktisk feiler med en generisk feilmelding.
|
||
- **Samme fane -- DSQ/RTD/DNF/DNS-status og Cut:** begge blokkerer ny
|
||
score-registrering (for hele turneringen, hhv. for runder etter
|
||
cut-grensen), men INGEN av delene vises i selve Scorekort-fanen --
|
||
oppdages kun ved mislykket lagringsforsøk.
|
||
- **Samme fane -- Bingo Bango Bongo-panelet:** lagringsfeil er i dag
|
||
HELT stille -- ingen tilbakemelding overhodet ved mislykket lagring,
|
||
til forskjell fra absolutt alle andre lagringsflyter på samme fane.
|
||
- **Samme fane -- fjern flaggplantning:** mislykkes selve fjerningen,
|
||
får brukeren i dag INGEN tilbakemelding -- grensesnittet antar
|
||
alltid at det gikk bra.
|
||
- **Samme fane -- brutto/netto-inkonsistens i score-cellen:** hvilken
|
||
av de fem resultatkategoriene (eagle/birdie/par/bogey/dobbel bogey+)
|
||
en celle havner i regnes ut fra NETTO resultat, men TALLET som
|
||
faktisk vises i cellen er alltid BRUTTO -- kan fremstå
|
||
selvmotsigende for en spiller med tildelte slag på hullet.
|
||
- **Samme fane -- flaggkart-synlighet:** ingen eier/administrator-
|
||
unntak når kartoversikten over flagg IKKE er satt offentlig av
|
||
organisator -- selv org-eier/administrator ser da kun sine EGNE
|
||
plantede flagg, ulikt selve score-/flagg-REGISTRERINGEN (der
|
||
eier/administrator har en eksplisitt unntaksrett, se punktet over).
|
||
- **Organisasjon -- rolletilpasning ujevn:** kun Medlemmer-siden
|
||
henter og håndhever brukerens rolle (Eier/Administrator/Medlem) i
|
||
grensesnittet. Spillerpool- og Order of Merit-sidene viser samme
|
||
fulle rediger/opprett/slett-handlingssett til ETHVERT
|
||
organisasjonsmedlem, uansett rolle -- uklart fra frontend-koden
|
||
alene om noe håndheves usynlig server-side. Bør avklares eksplisitt
|
||
(enten bevisst full tilgang for alle medlemmer, eller server-side
|
||
rollesjekk legges til) fremfor å forbli en tilfeldighet.
|
||
- **Organisasjon -- bekreftelse ujevnt fordelt:** fjern medlem/forlat
|
||
organisasjon og slett hele Order of Merit-serien krever eksplisitt
|
||
bekreftelse; fjern lenket turnering (fra en Order of Merit-serie),
|
||
slett Order of Merit-lag, fjern lagmedlem og opphev invitasjon gjør
|
||
det IKKE, til tross for at flere av disse også er vanskelige/umulige
|
||
å angre.
|
||
- **Organisasjon -- Order of Merit "Eclectic"-regelen skjult:**
|
||
kravet om at ALLE lenkede turneringer må spilles på samme bane for
|
||
at Eclectic-aggregering skal fungere, vises kun i en LUKKET, ikke-
|
||
utvidet innstillings-seksjon -- en organisator oppdager regelen
|
||
først når et lenkeforsøk blir avvist av serveren.
|
||
- **Turnering (individuell), Oppsett -- bekreftelse ujevnt fordelt:**
|
||
fjern deltaker og fjern klasse krever ingen bekreftelse i det hele
|
||
tatt; slett runde bruker en to-stegs inline-bekreftelse; slett
|
||
turnering og anvend cut bruker fullverdige advarselsdialoger --
|
||
ingen tydelig, gradert logikk bak forskjellen i dag.
|
||
- **Samme sted -- fjern klasse, stille konsekvens:** deltakere satt
|
||
til en klasse som fjernes mister klassetilhørigheten sin stille,
|
||
uten noen egen advarsel om akkurat DEN konsekvensen (selve
|
||
klasse-fjerningen har uansett ingen bekreftelse i det hele tatt, se
|
||
punktet over).
|
||
|
||
**Fullstendig kontekst for hvert punkt** (nøyaktig hvor i koden, og
|
||
nøyaktig hvilke andre regler det henger sammen med) finnes i de
|
||
respektive funksjonsspesifikasjons-dokumentene sine "Oppsummert:
|
||
hva vi ber designeren om"-seksjoner (Egne runder del 1, Sosialt,
|
||
Turnering individuell del 2, Organisasjon-administrasjon) --
|
||
publisert som Claude-artefakter denne runden, ikke egne filer i
|
||
repoet. **Neste steg:** triasjere denne listen i egne, avgrensede
|
||
runder (sannsynligvis flere -- de spenner fra rene UX-forbedringer
|
||
til det som kan være en reell autorisasjons-/synlighets-beslutning
|
||
verdt en ADR, se rolletilpasning-punktet) -- ikke noe som bør fikses
|
||
alle på én gang.
|
||
|
||
## Funksjonelle hull funnet under "utskrift av turneringsdokumenter"-planlegging (2026-08-21) — 📋 delvis notert, delvis under bygging
|
||
|
||
**Kontekst:** bruker ba om V0-prompter for utskriftsklare startlister/
|
||
resultatlister/cart-tags/scorekort (PDF, A4/Letter + A3/A2 for Cup-
|
||
startlister). Undersøkt FØR promptene ble skrevet, for å unngå å designe
|
||
utskrift for felt/formater som ikke faktisk finnes ennå. Tre reelle hull
|
||
funnet; bruker har eksplisitt tatt stilling til alle tre (se hver):
|
||
|
||
- **Hullengde finnes ikke i datamodellen.** Verken `hole` (org-scopet,
|
||
brukt av turneringer), `personal_course_hole` eller
|
||
`golfapi_course_hole` har noen lengde-/avstandskolonne -- kun `par`
|
||
og `stroke_index`. Verken `golfapi_client.py` eller `teeoff_client.py`
|
||
henter/lagrer noe lengdefelt fra kildene i dag heller (uklart om
|
||
kildene faktisk TILBYR det uten videre, eller om det må legges inn
|
||
manuelt). Trengs for scorekort-utskrift (bruker: "Hullets lengde må
|
||
med"). **💤 Utsatt** -- mockes med plausible tall i selve V0-
|
||
utskriftsprompten (`v0-prompt-print-scorecard.md`), ekte
|
||
datamodell-utvidelse (ny kolonne + evt. import-kilde-sjekk) er en
|
||
egen, senere oppgave før feltet kan vise ekte tall.
|
||
- **Ingen dedikert turneringslogo finnes.** `tournament.hero_image_key`
|
||
(migrasjon 009) er et BREDT banner-bilde til presentasjonsfanen, ikke
|
||
egnet som en kompakt logo i en utskrift-topptekst. Sponsor-logoer
|
||
finnes (samme migrasjon) men er et annet konsept (sponsor, ikke
|
||
arrangør). Trengs på alle fire utskriftstyper (bruker: "Turneringslogo
|
||
(om den ikke eksisterer bruker vi TeeCup sin)"). **💤 Utsatt** --
|
||
mockes i V0-promptene, ekte `tournament.logo_key`-felt (+ fallback til
|
||
TeeCups egen logo) er en egen, senere oppgave.
|
||
- **128-spiller individuell utslagsturnering ("Sommercup"-stil,
|
||
single-elimination bracket) finnes ikke som turneringsform i det hele
|
||
tatt.** Appen har i dag KUN to-lag Ryder Cup-formatet (arkitektur-
|
||
invariant, CLAUDE.md: "v1 = nøyaktig to lag ... Match-modellen holdes
|
||
generell (to sider) så knockout/flere lag kan komme senere" -- aldri
|
||
bygget, kun forberedt for). Ingen runde-for-runde-parring/
|
||
vinner-videreføring-logikk finnes. **💤 Utsatt, bevisst.** Bruker
|
||
eksplisitt valgt: skip bracket-utskrift denne runden, kun de
|
||
formatene som faktisk finnes (individuell + to-lag Cup) dekkes av
|
||
utskriftsprompt-runden. Bracket-turneringsformen tas opp som egen,
|
||
betydelig oppgave (parring/videreføring/UI) FØR noen utskrift for
|
||
den designes.
|
||
- **Shotgun-start finnes ikke** (`tournament_round`/`tournament_round_
|
||
group` for individuelle turneringer, `session`/`match` for Cup-
|
||
format -- SAMME arkitektoniske begrensning begge steder: ett delt
|
||
starthull for HELE runden/økten, kun staggerte klokkeslett PER
|
||
gruppe/match, ingen forskjellig starthull per gruppe/match).
|
||
Individuell-siden ble dette BEVISST valgt bort i migrasjon 084
|
||
(2026-08-18, tre-spørsmål-avklaring med bruker den gang). Bruker denne
|
||
runden: "Det må komme frem om det er Shotgun eller løpende start...
|
||
Sorteringen av startlisten gjøres etter utslagsform." **✅ BYGGET
|
||
2026-08-21** (migrasjon 088, ADR-096) -- se ARCHITECTURE_DECISIONS.md
|
||
for full detalj, ikke gjentatt her.
|
||
|
||
**Oppfølging 2026-08-21 -- V0-eksporten koblet til ekte data (ADR-097):**
|
||
alle fire utskriftsflater (scorekort/startliste/cart-tags/resultatliste)
|
||
er nå kodet mot ekte API-endepunkter for begge turneringsformer, med
|
||
server-generert PDF via headless Chromium (Playwright) -- se ADR-097 for
|
||
full detalj (PDF-mekanisme-valget, rutestruktur, datajoin-mønster,
|
||
inngangspunkter). `tsc --noEmit` og hele `vitest`-suiten grønn.
|
||
**⏳ Ikke bygget/utrullet ennå** -- ny Chromium-avhengighet i
|
||
produksjons-API-imaget venter på eksplisitt bekreftelse før
|
||
`docker compose build && up -d` kjøres (samme disiplin som alltid for
|
||
noe som endrer produksjonscontaineren). Ekte scratch-verifisering (klikk
|
||
gjennom alle fire med ekte turneringsdata, bekreft PDF-nedlasting) gjenstår
|
||
også -- kun kodenivå-verifisering (typecheck/tester) er gjort så langt.
|
||
|
||
## Idé, ikke spesifisert: påminnelser + tilbakemeldings-"kartotek" under en runde (2026-08-21) — 📋 fanget, ikke designet
|
||
|
||
Bruker: "Jeg vil brukeren skal få spørsmål om han vil bli påminnet
|
||
nødvendige ting som å huske å drikke (og spise), huske rutinene sine og
|
||
andre gode poenger gjennom golfrunden. Kanskje vi også skulle
|
||
implementert et kartotek med tilbakemeldinger av ymse slag som popper
|
||
opp når spilleren registerer en score på et hull eller etter en
|
||
fullført runde eller etter en turnering?" To atskilte idéer, ingen av
|
||
dem designet i detalj ennå:
|
||
|
||
1. **Under-runde-påminnelser** -- spørre brukeren (trolig ved
|
||
rundestart, eller i profil-/innstillinger) om de vil ha periodiske
|
||
varsler under en pågående runde for ting som lett glemmes: drikke
|
||
vann, spise, egne faste rutiner/sjekklister. Uavklart: varslings-
|
||
mekanisme (push-varsel? in-app-banner?), frekvens/trigger
|
||
(tidsintervall? etter hvert hull?), hvor dette konfigureres, om det
|
||
er en global brukerinnstilling eller per-runde-valg.
|
||
2. **Tilbakemeldings-"kartotek"** -- en samling ferdigskrevne
|
||
tips/tilbakemeldinger som kan poppe opp kontekstuelt: ved
|
||
score-registrering på et hull, etter fullført runde, eller etter
|
||
fullført turnering. Uavklart: hva slags innhold (motiverende?
|
||
teknisk/instruktivt? statistikk-baserte observasjoner om spillerens
|
||
eget spill?), triggerlogikk (tilfeldig? regelbasert ut fra
|
||
score/statistikk?), om det er redigerbart av organisator/klubb eller
|
||
fast levert av TeeCup.
|
||
|
||
**Neste steg:** begge idéene trenger en egen avklaringsrunde med bruker
|
||
FØR design/bygging -- ingen beslutninger tatt ennå, kun fanget opp her
|
||
for å ikke gå tapt.
|