Bygget: hele frontend for ADR-039-spillformene — utvidet spilleform-velgeren i "Ny runde" til alle åtte formater med skins-konfig, en ny SidesPanel i "Spillere og runde" (opprett/slett sider, tildel/fjern spillere med sanntids kapasitetssperre), en ny FormatResultPanel (løpende matchstatus for match/fourball/foursome/greensome/scramble, skins-tavle for skins), setup_complete-gating av "Fullfør runde", og et eget delt-ball-scorekort+veiviser for foursome/greensome/scramble. Testet reelt i nettleser (Chrome DevTools mot en isolert scratch-backend, ikke bare typesjekk) — spilte gjennom en komplett match-, skins- og foursome-runde fra bunnen av. Fant og fikset to reelle stale-state-buger underveis (setup-melding og matchstatus ble stående utdatert etter side-tildeling til de ikke lenger refetchet runden). Skins- og foursome-handicap-matematikken kryssjekket for hånd og stemte eksakt. Rullet ut mot ekte systemer — ingen migrasjon, kun teecup_frontend bygget på nytt, begge containere boot-et rent, teeoff.no upåvirket. ADR-039 er dermed helt ferdig, backend og frontend.
3184 lines
193 KiB
Markdown
3184 lines
193 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-07-17
|
||
|
||
---
|
||
|
||
## 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 CLAUDE.md-status 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 CLAUDE.md-status 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 CLAUDE.md-status 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
|
||
CLAUDE.md-status 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:** 📋 planlagt (infrastruktur)
|
||
- «BREAKING: X vant matchen». PWA push. Egen infrastruktur-bit.
|
||
|
||
### 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:** 📋 notert, IKKE designet/bygget — trenger egen ADR-runde senere.
|
||
- 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é, uttrykkelig betinget av at GPS er integrert i appen
|
||
først** (se GPS/avstandsmåling-notatet lenger opp i denne filen, ADR-033-
|
||
seksjonen — samme avhengighet): bruk GPS til å markere/registrere HVOR på
|
||
banen spilleren faktisk endte opp når slagene tok slutt, ikke bare hvilket
|
||
hull. Ingen datamodell eller UI designet ennå for noen del av dette —
|
||
rent notat.
|
||
|
||
- **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.
|
||
|
||
### 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. **Ingen migrasjon skrevet** —
|
||
ADR-037 er ren struktur-beslutning, neste steg er et konkret
|
||
migrasjonsutkast til gjennomgang.
|
||
|
||
---
|
||
|
||
## Varsler: push til telefon + in-app varslingssenter — 📋 DESIGNET 2026-07-25, IKKE bygget
|
||
|
||
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 — teknisk mulig, men reell ny
|
||
infrastruktur, ikke en liten utvidelse:**
|
||
- Fundamentet finnes allerede (ADR-028: service worker, installerbar PWA).
|
||
Web Push (Push API + Notification API) fungerer UTEN en native app.
|
||
- **Viktig plattformbegrensning:** på iOS Safari fungerer Web Push KUN når
|
||
PWA-en er lagt til på hjemskjermen (iOS 16.4+) — en vanlig Safari-fane
|
||
kan ALDRI motta push, uansett tillatelse gitt. Android/desktop er langt
|
||
mer tilgivende.
|
||
- Krever: et VAPID-nøkkelpar, en ny `push_subscription`-tabell (én bruker
|
||
kan ha flere enheter/abonnementer), en backend-sende-funksjon (f.eks.
|
||
`pywebpush`), og et eksplisitt tillatelsesspørsmål fra nettleseren (kan
|
||
ikke sendes stille, brukeren kan avslå).
|
||
- **Vurdering:** reell verdi, men et eget, moderat-til-stort byggeløft med
|
||
en hard plattformbegrensning å designe rundt — IKKE anbefalt som første
|
||
steg.
|
||
|
||
**In-app varslingssenter — anbefalt første steg, fungerer overalt, ingen
|
||
tillatelse kreves:**
|
||
- 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
|
||
|
||
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 — 📋 VURDERT 2026-07-25, IKKE designet i detalj
|
||
|
||
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: kun vurdert/skissert i denne runden, ikke designet i detalj
|
||
eller bygget** — ingen V0-prompt skrevet ennå for dette (i motsetning
|
||
til varslingssenteret over). Naturlig neste steg om bruker vil gå videre:
|
||
en egen designrunde for selve UI-teksten/visuelt (særlig iOS-
|
||
instruksjonsbanneret, som må vise konkrete steg) — trolig verdt et eget
|
||
V0-prompt da, siden det er en egen, synlig UI-flate.
|
||
|
||
---
|
||
|
||
## Leaderboard for runder og turneringer — 🧠 DRØFTET 2026-07-25, IKKE besluttet
|
||
|
||
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 CLAUDE.md sin statuslogg
|
||
(2026-07-26) -- ikke gjentatt her for å unngå duplisering.
|
||
|
||
---
|
||
|
||
## 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 CLAUDE.md sin statuslogg — 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 CLAUDE.md sin statuslogg (2026-07-27) — ikke duplisert her.
|
||
|
||
---
|
||
|
||
## 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 CLAUDE.md sin statuslogg
|
||
(2026-07-28) — ikke duplisert her.
|
||
|
||
---
|
||
|
||
## Scramble: statistikk over utslag brukt per spiller — 📋 NOTERT 2026-07-25, IKKE bygget
|
||
|
||
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, ikke besluttet:**
|
||
- Hvem registrerer dette — kapteinen/den som fører score for laget
|
||
(samme autorisasjonsmodell som resten av match-scoring, ADR-023), og
|
||
på hvilket tidspunkt (samtidig med selve hull-scoren, eller separat)?
|
||
- Hvor vises statistikken — per match, aggregert for hele turneringen,
|
||
eller begge deler?
|
||
- Teller uspilte/ikke-registrerte hull annerledes enn et hull der ingen
|
||
eksplisitt valgte utslag ble registrert (skal det være mulig å hoppe
|
||
over)?
|
||
|
||
**Ingen datamodell eller UI designet ennå** — rent notat, fanget opp slik
|
||
at det ikke går i glemmeboken til scramble/greensome-scoring tas fatt på
|
||
som egen runde.
|
||
|
||
---
|
||
|
||
## 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). |
|
||
|
||
Se ARCHITECTURE_DECISIONS.md ADR-025 for alle fire hovedbeslutningene og
|
||
CLAUDE.md-status for full byggerunde (datamodell, autorisasjon, scratch-
|
||
verifisering med 20 automatiserte sjekker inkl. reell WebSocket-sanntid,
|
||
og utrulling).
|
||
|
||
---
|
||
|
||
## 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 CLAUDE.md-status). 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
|
||
CLAUDE.md-status 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 CLAUDE.md-status 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 CLAUDE.md-status 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
|
||
CLAUDE.md-status 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.
|
||
|
||
---
|
||
|
||
## 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 CLAUDE.md-status 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 CLAUDE.md-status 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 CLAUDE.md-status 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 CLAUDE.md-status.
|
||
|
||
**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 — 📋 FORESLÅTT 2026-07-20, IKKE bygget ennå
|
||
|
||
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, trengs FØR bygging:**
|
||
- Skal utsendingsknappen ligge på ØKT-nivå (send til alle i denne ene
|
||
runden) eller TURNERING-nivå (send til alle på tvers av alle økter, når
|
||
hele turneringen er ferdig)? Økt-nivå virker riktigst — en spiller kan
|
||
ha spilt kun én av flere økter.
|
||
- Skal systemet spore "allerede sendt til denne spilleren for denne økten"
|
||
for å hindre dobbel utsending ved et nytt klikk (sannsynligvis ja — én
|
||
liten ny tabell/kolonne)?
|
||
- Skal e-posten sendes på spillerens/organisasjonens foretrukne språk
|
||
(samme `locale`-mønster som magic-link-e-posten, ADR-015 Beslutning C)?
|
||
|
||
---
|
||
|
||
## 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 CLAUDE.md-status 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 — 📋 KONKRET FORSLAG LAGT FREM (2026-07-25), IKKE bygget
|
||
|
||
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 CLAUDE.md-status, ✅ 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 (📋 DESIGNET, IKKE BYGGET)
|
||
|
||
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 — ✅ FASE 1 (venner-kjernen) FERDIG, ✅ deler av FASE 3 (søk+legg til medspiller, skriv for hele flighten) BYGGET 2026-07-26, fase 2 (rundevisibilitet) fortsatt kun designet
|
||
|
||
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.
|
||
|
||
**Foreslått byggerekkefølge (IKKE bekreftet), tre uavhengig leverbare
|
||
faser:**
|
||
1. Venner-kjernen (vennskap, kategorisering, `/people/search`, ny
|
||
`/friends`-side) — leverbar og nyttig helt alene.
|
||
2. Rundevisibilitet (visibility-valg ved rundestart, gatede
|
||
lese-endepunkter, sanntids-livevisning for venner/offentlighet).
|
||
3. Ekte medspillere (ikke bare gjester) — utvider
|
||
`POST /rounds/{id}/participants` til å godta et søkt `user_id`.
|
||
**✅ DENNE DELEN 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/slette spillere mellom lag — 📋 NOTERT 2026-07-25, IKKE bygget
|
||
|
||
Reist av brukeren samme runde som dashbord-integreringen. I dag finnes
|
||
`DELETE .../roster/{roster_id}` (fjerner en spiller fra ETT lag) og
|
||
`PATCH .../roster/{roster_id}` (kun `is_captain`, ADR-023) — men INGEN vei
|
||
til å FLYTTE en allerede rostret spiller til et ANNET lag i samme
|
||
turnering i én operasjon. I dag må organisatoren gjøre det som to separate
|
||
kall (fjern fra lag A, legg til på lag B), og det finnes ingen slik
|
||
UI-handling i `tournament-detail.tsx` sin `TeamPanel` i det hele tatt —
|
||
kun fjerning.
|
||
|
||
**Ikke designet i detalj ennå** — rent notert som et reelt hull. Naturlig
|
||
retning ved bygging: enten et eget `POST .../roster/{roster_id}/move`
|
||
(target-team-id) som gjør begge operasjonene atomisk (unngår en
|
||
mellomtilstand der spilleren midlertidig ikke er rostret noe sted), eller
|
||
en UI-snarvei som bare kjører de to eksisterende kallene i sekvens — det
|
||
første er tryggere (ingen delvis fullført tilstand ved feil midtveis).
|
||
|
||
---
|
||
|
||
## Frittstående runder: flere flighter i én "vanlig" runde — 📋 NOTERT OG PRESISERT 2026-07-25/26, IKKE besluttet eller bygget
|
||
|
||
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.
|
||
|
||
---
|
||
|
||
## 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 CLAUDE.md-status 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, IKKE bygget (brukerens egen kommentar mens punkt 7 ble
|
||
avklart):** "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. Frittstående runder har i dag INGEN
|
||
Stableford-poengberegning i det hele tatt (kun rå slagtall for HCP-
|
||
differensial) -- dette er et helt eget, udesignet format-spørsmål, ikke
|
||
løst av at "spilt"-avkrysningen ble fjernet. Trenger egen designrunde
|
||
(scoring-format-valg per runde, poengberegning, og en "plukket opp"-
|
||
tilstand som sannsynligvis bør lagres som en cap på nettoscore, samme
|
||
prinsipp som WHS sin Net Double Bogey) den dagen Stableford faktisk
|
||
bygges.
|
||
|
||
**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.
|
||
|
||
**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) — ✅ BACKEND BYGGET OG SCRATCH-VERIFISERT (ADR-039), IKKE ENNÅ rullet ut mot ekte systemer, frontend fortsatt ikke bygget
|
||
|
||
**Oppdatering 2026-07-28 (ADR-039):** design + backend er ferdig samme
|
||
dag som punktet ble reist. Se CLAUDE.md-status 2026-07-28 for full
|
||
detalj (migrasjon `031`, nye endepunkter, 96/96 scratch-sjekker). 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. **Gjenstår:** frontend (ingen skjerm
|
||
for sideoppsett/skins-konfig/matchstatus-visning ennå) og selve
|
||
utrullingen mot ekte `teecup_db`/`teecup_api` — begge egne, separate
|
||
neste steg.
|
||
|
||
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, ingen besvart ennå:**
|
||
- Skal `round.play_format` utvides med `'skins'`/`'pair_team'` (ny
|
||
migrasjon, ny CHECK-verdi), eller er dette en helt egen entitet
|
||
parallelt med `round`?
|
||
- 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?
|
||
- Skins: netto eller brutto, og hvordan behandles uavgjorte hull
|
||
(rullerer premien, eller deles)?
|
||
- 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?
|
||
- 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å).
|
||
|
||
**Ingen kode skrevet** — dette er bevisst kun fanget/dokumentert nå, på
|
||
brukerens eksplisitte instruks. Trenger en egen, dedikert design-/
|
||
ADR-runde (samme skala som ADR-033/036/037) før noe bygges, gitt at
|
||
punkt 2-4 hver krever nytt skjema, ny motorlogikk (for skins) eller
|
||
gjenbruk av eksisterende turnering-motor (for match/par-lag), og en
|
||
egen scorekort-presentasjon per format.
|
||
|
||
---
|
||
|
||
## Én person, flere e-postadresser — DEL 1 (det enkle tilfellet) ✅ BYGGET OG LIVE 2026-07-21, DEL 2 (kontosammenslåing) fortsatt 📋 NOTERT
|
||
|
||
Reist av brukeren rett etter ADR-032 (verifisert e-postbytte). Et beslektet,
|
||
men DISTINKT behov: én person kan ha flere e-postadresser i omløp samtidig
|
||
(f.eks. registrert seg privat med én adresse, men fått en turnering-
|
||
invitasjon rettet mot en jobb-adresse organisator la inn) — ikke et BYTTE
|
||
(ADR-032 sin løsning), men en TILLEGGS-tilknytning. I dag oppretter et
|
||
magic-link-innlogg på en ny adresse alltid en HELT NY, tom `app_user`-konto
|
||
(ADR-009) — nøyaktig det som gjør at spilleren aldri "finner" turneringen
|
||
sin med sin vanlige, primære konto.
|
||
|
||
**Brukerens beskrevne flyt, ordrett fanget:** innlogget med
|
||
eksempel@domene.no, sier "jeg eier også test@domain.com". Ved dette kravet
|
||
sendes en e-post til test@domain.com med en nøkkel som limes inn et sted på
|
||
dashbordet. Etter bekreftelse dukker inviterte turneringer på den adressen
|
||
opp. Annen data (spilte runder, personlig informasjon) skal "forespørres
|
||
slått sammen eller justert". Fremtidige innlogginger skal kunne gjøres med
|
||
ENHVER av de tilknyttede adressene.
|
||
|
||
**Foreslått retning, basert på gjenbruk av allerede bygget mønster:**
|
||
samme token-i-e-post-bevis-eierskap-mekanisme som ADR-032 sin
|
||
e-postbytte-flyt (`email_change_token`), men ADDITIV i stedet for
|
||
ERSTATTENDE — en ny tabell for verifiserte SEKUNDÆRE e-poster knyttet til
|
||
kontoen (i stedet for å overskrive `app_user.email`). Login (`/auth/
|
||
request-link` m.fl.) må da slå opp BÅDE primær- og sekundær-e-poster.
|
||
`link_player_by_email()`/organisasjonsinvitasjon-aksept (som i dag kun
|
||
kjører mot `app_user.email`) må kjøres for HVER av kontoens verifiserte
|
||
adresser — naturlig utløst rett etter en ny adresse er bekreftet, og
|
||
sannsynligvis også trygt å kjøre på nytt ved hver innlogging (idempotent,
|
||
samme mønster som i dag).
|
||
|
||
**Det virkelig vanskelige, uløste spørsmålet, IKKE adressert av brukerens
|
||
beskrevne flyt:** hva skjer hvis den "krevde" adressen ALLEREDE er
|
||
primær- (eller sekundær-)adressen til en ANNEN, eksisterende `app_user`-
|
||
konto — altså at spilleren faktisk har logget inn med DEN adressen
|
||
tidligere og dermed har to helt separate kontoer med egen historikk
|
||
(ulike org-medlemskap, ulike spillerkoblinger, kanskje ulikt passord/2FA)?
|
||
Da holder det ikke å bare "legge til" adressen — det er en ekte KONTO-
|
||
SAMMENSLÅING (slå sammen organisasjonsmedlemskap uten å bryte "én rolle
|
||
per bruker per org"-unikheten, deduplisere spiller-koblinger, avgjøre
|
||
hvilken konto som "vinner" for tvetydige felt som `preferred_locale`/2FA
|
||
når begge har satt noe ulikt). Dette er en betydelig større og mer
|
||
risikofylt operasjon enn "legg til en frisk, ukrevd adresse" — bør
|
||
utredes og besluttes som en egen, separat sak, ikke antas løst av samme
|
||
runde som det enkle tilfellet.
|
||
|
||
**Plassering:** brukeren presiserte eksplisitt at dette må skje FRA
|
||
dashbord-siden (ikke `/account`, der ADR-032 sin e-postbytte-flyt ellers
|
||
naturlig ville hørt hjemme) — trolig fordi selve GEVINSTEN (nye turneringer
|
||
dukker opp) er noe som vises på dashbordet, så handlingen bør ligge der
|
||
resultatet vises.
|
||
|
||
**Ble delt i to separate runder, som foreslått:** (1) legg til en frisk,
|
||
ukrevd sekundær-e-post — ✅ BYGGET, se under. (2) Ekte konto-sammenslåing
|
||
for det vanskelige tilfellet — fortsatt IKKE designet, egen fremtidig
|
||
runde.
|
||
|
||
### Del 1 (det enkle tilfellet) — ✅ BYGGET OG LIVE 2026-07-21
|
||
|
||
Migrasjon `017_secondary_email.sql`: to nye tabeller
|
||
(`secondary_email_token` — midlertidig, samme token-hash-og-utløp-mønster
|
||
som `email_change_token`; `user_secondary_email` — den faktiske,
|
||
verifiserte adressen, globalt UNIQUE). Nye endepunkter i
|
||
`app/routers/auth.py`: `POST /auth/secondary-email` (send
|
||
bekreftelseslenke), `POST /auth/secondary-email/confirm` (ingen sesjon
|
||
påkrevd, samme mønster som selve magic-link-verifiseringen),
|
||
`DELETE /auth/secondary-email/{id}`.
|
||
|
||
**Kjernestykket, ikke bare CRUD:** `verify_magic_link` og
|
||
`login_with_password` sjekker nå `user_secondary_email` FØR de gjør sitt
|
||
vanlige `app_user.email`-oppslag — finner de en match, løses innloggingen
|
||
til DEN EKSISTERENDE eierens konto i stedet for å (som før) stille
|
||
opprette en helt ny, separat konto. Dette er selve mekanismen som gjør
|
||
adressen nyttig, ikke bare en liste over "andre adresser".
|
||
|
||
**Plassering, bevisst avvik fra brukerens opprinnelige "fra dashbordet"-
|
||
instruks:** lagt i `/account` (samme sted som ADR-032 sin e-postbytte),
|
||
IKKE dashbordet — begrunnet med at dette kun er del 1 (det enkle
|
||
tilfellet); når/hvis del 2 (kontosammenslåing, "data dukker opp") bygges,
|
||
er dashbordet trolig riktigere siden GEVINSTEN vises der. Flagget
|
||
eksplisitt til bruker, ikke stille besluttet.
|
||
|
||
**Scratch-verifisert, 20 sjekker:** adresse legges IKKE til før bekreftet;
|
||
token ikke gjenbrukbart; adresse som allerede er en ANNEN kontos
|
||
hovedadresse ELLER sekundæradresse avvist tydelig (409 DUPLICATE) i begge
|
||
retninger; innlogging via sekundæradressen (BÅDE magic-link OG passord)
|
||
løses korrekt til samme, eksisterende konto (bekreftet: samme `id`,
|
||
`email` i responsen forblir hovedadressen); en fremmed kan ikke slette
|
||
andres sekundæradresse; og — den kritiske sjekken — en ny innlogging på
|
||
adressen ETTER at den er fjernet oppretter en genuint NY, separat konto
|
||
(beviser fjerningen er reell, ikke kosmetisk). `test_isolation.sql`
|
||
fortsatt 12/12 (additiv migrasjon). Ekte typesjekket produksjonsbuild av
|
||
frontend kjørt og bekreftet.
|
||
|
||
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon 017
|
||
kjørt mot ekte `teecup_db` (bekreftet begge nye tabeller finnes,
|
||
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
|
||
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||
`/health`/`/dashboard`/`/account`/`/verify-email` → 200, `teeoff.no`
|
||
upåvirket.
|
||
|
||
### Del 2 (ekte kontosammenslåing) — fortsatt 📋 NOTERT, IKKE designet
|
||
|
||
Uendret fra den opprinnelige analysen: hva skjer hvis adressen som legges
|
||
til ALLEREDE er primær- eller sekundæradressen til en ANNEN, eksisterende
|
||
konto (spilleren har altså to helt separate kontoer med egen historikk —
|
||
ulike org-medlemskap, ulike spillerkoblinger, kanskje ulikt passord/2FA)?
|
||
Dagens del 1-løsning avviser dette tydelig (409 DUPLICATE) i stedet for å
|
||
gjette — en ekte sammenslåing (slå sammen org-medlemskap uten å bryte
|
||
"én rolle per bruker per org", deduplisere spillerkoblinger, avgjøre
|
||
hvilken konto som "vinner" for motstridende felt) er en betydelig større
|
||
og mer risikofylt operasjon, fortsatt bevisst utsatt til en egen,
|
||
dedikert designrunde.
|
||
|
||
---
|
||
|
||
## UX / frontend (senere fase)
|
||
|
||
- 🔀 **Tilgjengelighet — STÅENDE krav, ikke lenger et enkeltpunkt
|
||
(skjerpet 2026-07-22, se CLAUDE.md):** all frontend, eksisterende og
|
||
fremtidig (inkl. alle nye V0-skjermer), skal være lesbar/forståelig/
|
||
betjenbar for noen med noe redusert syn UTEN briller. Det tidligere,
|
||
vagere punktet under ("høy kontrast, store knapper...") er nå en
|
||
KONKRET instans av dette generelle, varige kravet — ikke en egen,
|
||
isolert senere-fase-oppgave. Ingen dedikert retrofit-runde igangsatt
|
||
ennå; rettes opportunistisk når skjermer likevel røres, og tas inn i
|
||
enhver ny V0-prompt fremover.
|
||
- 📋 Høy kontrast, dark/light, store +/- knapper, stor «Neste hull»-knapp
|
||
(banebruk i sollys/med solbriller) — konkret eksempel på punktet over.
|
||
- ✅ Offline-first (ADR-006) — BYGGET 2026-07-19 (ADR-028), se eget punkt
|
||
under. Scoreregistrering (hole-scores/hole-results) fungerer nå offline
|
||
med automatisk synk.
|
||
- ✅ PWA: manifest, service worker, «Legg til på hjemskjerm» — BYGGET
|
||
2026-07-19 (ADR-028).
|
||
|
||
### PWA — ✅ BYGGET OG LIVE 2026-07-19 (ADR-028)
|
||
|
||
Full design i ARCHITECTURE_DECISIONS.md ADR-028. Kort:
|
||
|
||
| Del | Status | Notat |
|
||
|---|---|---|
|
||
| Installerbar app (manifest + ikoner + «Legg til på hjemskjerm») | ✅ bygget | `app/manifest.ts` (Next.js sin innebygde manifest-generator), `components/sw-register.tsx`, `appleWebApp`-metadata for iOS. |
|
||
| Ikoner | ✅ bygget, **MIDLERTIDIG** | Enkelt grønt golf-flagg generert programmatisk (`public/icons/*`, `public/apple-icon.png`) — **skal erstattes med ekte design senere.** Erstattet samtidig den gamle v0.app-plassholderlogoen som lå i `apple-icon.png` fra før (var aldri TeeCup-merkevare). |
|
||
| 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) | ❌ **FORTSATT IKKE GJORT — OPPFØLGINGSPUNKT** | Kun verifisert med typesjekket build + container-boot/curl, aldri i en ekte nettleser. Ingen nettleserverktøy tilgjengelig i byggeøkten. **Brukeren bør selv åpne et scorekort, skru på Chrome DevTools sin Offline-bryter, registrere et par slag, skru nettet på igjen, og bekrefte at de faktisk synkes** — først da er offline-flyten reelt bevist, ikke bare kodegjennomgått. |
|
||
|
||
**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.
|
||
|
||
---
|
||
|
||
## 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).
|