diff --git a/.claude/settings.json b/.claude/settings.json index 441d8c0..066c2c9 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -48,7 +48,8 @@ "Bash(curl -s -H \"Connection: Upgrade\" -H \"Upgrade: websocket\" -H \"Sec-WebSocket-Version: 13\" -H \"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\" https://teecup.teeoff.no/ws/rounds/00000000-0000-0000-0000-000000000000/live)", "Bash(grep -n \"participants/\\\\${\" /opt/teecup/frontend/components/round-detail.tsx)", "Bash(python3 -)", - "Bash(curl -s -o /dev/null -w \"teeoff.no: %{http_code}\\\\n\" https://teeoff.no/)" + "Bash(curl -s -o /dev/null -w \"teeoff.no: %{http_code}\\\\n\" https://teeoff.no/)", + "Bash(python3 -c \"import ast; ast.parse\\(open\\('app/routers/rounds.py'\\).read\\(\\)\\)\")" ] } } diff --git a/029_round_participant_guest_email.sql b/029_round_participant_guest_email.sql new file mode 100644 index 0000000..baaabe4 --- /dev/null +++ b/029_round_participant_guest_email.sql @@ -0,0 +1,15 @@ +-- ===================================================================== +-- TeeCup — valgfri e-post for gjestedeltakere (migrasjon 029) +-- ===================================================================== +-- Reist av bruker 2026-07-26 sammen med et ønske om å kunne redigere +-- navn/kjønn/e-post for en "midlertidig spiller" (gjest uten konto) fra +-- samme sted som utslag/HCP redigeres. Rent kontaktfelt -- ingen +-- kontosammenkobling/invitasjon bygget her, kun lagring+redigering. +-- Kun meningsfullt for gjester (user_id IS NULL) -- håndheves i app-laget, +-- ikke en CHECK, siden det speiler de andre gjeste-kun-feltene (guest_name) +-- som heller ikke er CHECK-håndhevet mot user_id her. +-- ===================================================================== + +\set ON_ERROR_STOP on + +ALTER TABLE round_participant ADD COLUMN guest_email text; diff --git a/CLAUDE.md b/CLAUDE.md index d388076..8079036 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3162,9 +3162,57 @@ Ferdig og verifisert: uavhengige merker: "Eier" (fra API-ets `is_owner`) og "Deg" (fra en `participant_id`-prop komponenten mottar utenfra, ikke fra selve leaderboard-dataen). Prompten er ikke sendt til v0.app ennå. +- **Mottatte slag i hull-headeren + gjeste-e-post/kjønn/navn redigerbart, + BYGGET OG SCRATCH-VERIFISERT (2026-07-26), IKKE ENNÅ RULLET UT:** brukeren + viste et skjermbilde av det nettopp rullede "Rediger for denne runden"- + tillegget og ba om to ting: (1) hvor mange slag aktiv spiller MOTTAR på + det aktive hullet vist i selve hull-headeren (eksempel "Hull 7 - Par 4 - + Hcp 5 - -1"), og (2) for en midlertidig spiller (gjest) skal Navn/Kjønn/ + E-post også være redigerbart, ikke bare utslag/HCP. Ba samtidig om et + V0-prompt for å designe om selve spillerlisten (utslag/HCP/rediger inn i + selve spillerknappen, listet vertikalt i stedet for dagens horisontale + scroll-rad). + **Punkt 1 bygget direkte** (triviell, ren frontend, ingen backend-endring + -- `strokes_received` var allerede hentet fra `GET .../holes` fra før, + bare aldri vist FØR scoring): `round-detail.tsx` sin hull-header viser nå + `· −N` (ekte minustegn, golfvis fortegn) når aktiv spiller mottar minst + ett slag på hullet, ellers uendret. + **Punkt 2 krevde ny backend:** ny migrasjon `029_round_participant_ + guest_email.sql` (`round_participant.guest_email`, nullable). `Participant + Create` fikk et valgfritt `guest_email: EmailStr | None` (avvist sammen + med `user_id`). `ParticipantUpdate` fikk `guest_name`/`gender`/ + `guest_email` -- KUN gyldig for en gjest (`user_id IS NULL`), avvist + (400) på en lenket deltaker. `gender`-endring inngår nå i samme "rating_ + changed"-bunt som utslag/HCP (påvirker gyldig rating), avvist etter + fullføring (409, samme presedens); `guest_name` alene har ingen rating- + implikasjon og forblir redigerbar selv etter fullføring (ren metadata). + **Frontend for punkt 2 IKKE bygget ennå** -- overlatt til V0-prompten + (se FEATURE_BACKLOG.md), siden brukeren eksplisitt ba om det og den + eksisterende hånd-bygde `EditParticipantPanel` uansett skal erstattes av + den nye vertikale spillerlisten. + **Scratch-verifisert, 22/22 nye sjekker** (gjest med e-post ved + opprettelse, avvist sammen med user_id, navn+kjønn+e-post-redigering + lykkes og rating regnes om for nytt kjønn, eksplisitt null fjerner + e-post, kjønnsendring til urepresentert rating avvist med full rollback, + alle tre gjeste-feltene avvist på en LENKET deltaker mens utslag/HCP + fortsatt fungerer uendret der, kjønn/utslag/HCP-endring blokkert etter + fullføring mens navneendring fortsatt tillates). Full regresjonskjøring + av samme dags 27+35-punkts testsuiter mot samme friske scratch-database, + alle 62 fortsatt grønne. `test_isolation.sql` 12/12. Ekte typesjekket + produksjonsbuild kompilerte rent (inkl. punkt 1). + **Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt: + migrasjon 029 kjørt mot ekte `teecup_db` (kolonne bekreftet, + `test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d + --build teecup_api teecup_frontend`. Begge containere boot-et rent, + `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. Neste steg: -0. **Rundeleaderboard — venter på V0-zip (2026-07-26):** backenden +0a. **Spillerliste-redesign — venter på V0-zip (2026-07-26):** utslag/HCP/ + Rediger flyttet inn i selve spillerkortet, vertikal liste, gjeste-navn/ + kjønn/e-post redigerbart. Backenden er live (migrasjon 029), prompten + er skrevet og sendt til bruker (se FEATURE_BACKLOG.md), ikke kjørt i + v0.app ennå. +0b. **Rundeleaderboard — venter på V0-zip (2026-07-26):** backenden (`GET /rounds/{id}/leaderboard`) er live siden tidligere samme dag. V0-prompten for selve VISNINGEN er skrevet, sendt til bruker, og rettet ("Deg"/"Eier"-merke, se status over) — men ikke kjørt i diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md index 42984ca..16e80ec 100644 --- a/FEATURE_BACKLOG.md +++ b/FEATURE_BACKLOG.md @@ -1053,6 +1053,154 @@ over. Fortsatt ikke kjørt i v0.app. --- +## Spillerliste-redesign (vertikal, utslag/HCP/rediger inline) — backend BYGGET, V0-prompt sendt 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? } +``` + +**Ikke avgjort ennå, avklares når zip-en er klar til integrering:** nøyaktig +plassering av "+ Medspiller"-knappen i den nye vertikale listen (nederst +som et eget kort, eller en fast knapp over/under listen — din vurdering +når du ser V0s forslag), og om Rediger-flyten skal være en inline- +utvidelse av kortet eller et eget ark/modal (V0 fikk begge alternativer +åpne i prompten over, bevisst). + +--- + ## En tredje (informasjons-)farge til designet — 🧠 DRØFTET 2026-07-25, IKKE besluttet Brukeren spurte om det ville vært en idé å introdusere én (eller kanskje diff --git a/app/routers/rounds.py b/app/routers/rounds.py index 42bfff1..093123d 100644 --- a/app/routers/rounds.py +++ b/app/routers/rounds.py @@ -31,7 +31,7 @@ from datetime import date, datetime from typing import Literal from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect -from pydantic import BaseModel, Field +from pydantic import BaseModel, EmailStr, Field from .. import teeoff_client from ..auth import CurrentUser, get_current_user, get_current_user_from_websocket @@ -422,6 +422,9 @@ class RoundParticipantOut(BaseModel): id: str user_id: str | None guest_name: str | None + # Valgfritt kontaktfelt, KUN for gjester (2026-07-26) -- ingen betydning + # for en lenket bruker, som allerede har sin egen kontos e-post. + guest_email: str | None # Alltid utfylt -- guest_name for gjester, ellers en LEVENDE oppslått # app_user.display_name for enhver lenket bruker (eier ELLER medspiller, # ADR-036 fase 3-utvidelsen 2026-07-26) -- ikke en snapshot, samme @@ -474,7 +477,7 @@ async def _load_round_out(conn, round_id: str, viewer_user_id: str) -> RoundOut: ) participant_rows = await conn.fetch( """ - SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, + SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email, COALESCE(rp.guest_name, au.display_name, 'Medspiller') AS display_name, rp.is_owner, rp.gender, rp.tee_name_snapshot, rp.handicap_index_snapshot::float AS handicap_index_snapshot, @@ -538,6 +541,7 @@ async def _create_participant( gender: str, handicap_index: float | None, stat_level: StatLevel = "strokes_only", + guest_email: str | None = None, ) -> str: rating = resolved.rating_for(tee_name, gender) course_rating = slope_rating = tee_par = None @@ -550,15 +554,16 @@ async def _create_participant( participant_row = await conn.fetchrow( """ INSERT INTO round_participant - (round_id, user_id, guest_name, is_owner, gender, tee_name_snapshot, handicap_index_snapshot, + (round_id, user_id, guest_name, guest_email, is_owner, gender, tee_name_snapshot, handicap_index_snapshot, course_rating_snapshot, slope_rating_snapshot, tee_par_snapshot, course_handicap_snapshot, stat_level) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id::text AS id """, round_id, user_id, guest_name, + guest_email, is_owner, gender, tee_name, @@ -899,6 +904,9 @@ class ParticipantCreate(BaseModel): # personens egen profil (samme kilde som eierens egen deltaker-rad). gender: Literal["m", "f", "x"] | None = None handicap_index: float | None = Field(default=None, ge=-10, le=54) + # Valgfritt kontaktfelt for en gjest (2026-07-26) -- avvist hvis oppgitt + # sammen med user_id (en lenket bruker har allerede sin egen e-post). + guest_email: EmailStr | None = None # Utslagssted for DENNE deltakeren (2026-07-26) -- valgfritt, faller # tilbake til rundens eget utslag (samme som før) hvis utelatt. Kan # avvike fra de andre deltakerne, f.eks. et kjønnsdelt utslag. @@ -914,6 +922,8 @@ async def add_participant( ) -> RoundParticipantOut: if (body.user_id is not None) == (body.guest_name is not None): raise app_error(400, "VALIDATION_FAILED", "Oppgi enten user_id (funnet via søk) eller guest_name, ikke begge/ingen.") + if body.user_id is not None and body.guest_email is not None: + raise app_error(400, "VALIDATION_FAILED", "guest_email gir ikke mening sammen med user_id -- en lenket bruker har allerede sin egen konto-e-post.") async with plain_connection() as conn: round_row = await conn.fetchrow( @@ -970,10 +980,11 @@ async def add_participant( user_id=linked_user_id, guest_name=guest_name, is_owner=False, gender=gender, handicap_index=handicap_index, stat_level=body.stat_level, + guest_email=body.guest_email, ) row = await conn.fetchrow( """ - SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, + SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email, COALESCE(rp.guest_name, au.display_name, 'Medspiller') AS display_name, rp.is_owner, rp.gender, rp.tee_name_snapshot, rp.handicap_index_snapshot::float AS handicap_index_snapshot, @@ -1021,6 +1032,13 @@ class ParticipantUpdate(BaseModel): # (differensialen er da allerede beregnet fra det gamle grunnlaget). tee_name: str | None = None handicap_index: float | None = Field(default=None, ge=-10, le=54) + # KUN gyldig for en gjest (user_id IS NULL) -- gir ingen mening for en + # lenket bruker, som allerede har sin egen kontos navn/kjønn/e-post. + # `gender` er en del av rating-bunten under (påvirker hvilken utslags- + # rating som er gyldig), `guest_name`/`guest_email` er rene metadata. + guest_name: str | None = Field(default=None, min_length=1, max_length=100) + gender: Literal["m", "f", "x"] | None = None + guest_email: EmailStr | None = None @router.patch("/rounds/{round_id}/participants/{participant_id}", response_model=RoundParticipantOut) @@ -1038,7 +1056,7 @@ async def update_participant( await _get_owned_round_or_404(conn, round_id, user.user_id) current = await conn.fetchrow( """ - SELECT rp.gender, rp.tee_name_snapshot, + SELECT rp.user_id::text AS user_id, rp.gender, rp.tee_name_snapshot, rp.handicap_index_snapshot::float AS handicap_index_snapshot, r.completed_at, r.course_source, r.teeoff_facility_slug, r.teeoff_course_id, r.personal_course_id @@ -1051,11 +1069,18 @@ async def update_participant( if current is None: raise app_error(404, "NOT_FOUND", "Deltakeren finnes ikke på denne runden.") - rating_changed = "tee_name" in updates or "handicap_index" in updates + guest_only_fields = {"guest_name", "gender", "guest_email"} + if current["user_id"] is not None and guest_only_fields & updates.keys(): + raise app_error( + 400, "VALIDATION_FAILED", + "Navn/kjønn/e-post kan ikke endres her for en spiller med egen konto -- kun for en gjest.", + ) + + rating_changed = "tee_name" in updates or "handicap_index" in updates or "gender" in updates if rating_changed and current["completed_at"] is not None: raise app_error( 409, "ALREADY_COMPLETED", - "Runden er allerede fullført -- kan ikke endre utslag eller HCP i etterkant.", + "Runden er allerede fullført -- kan ikke endre utslag, HCP eller kjønn i etterkant.", ) set_clauses: list[str] = [] @@ -1064,6 +1089,12 @@ async def update_participant( if "stat_level" in updates: values.append(updates["stat_level"]) set_clauses.append(f"stat_level = ${len(values)}") + if "guest_name" in updates: + values.append(updates["guest_name"].strip()) + set_clauses.append(f"guest_name = ${len(values)}") + if "guest_email" in updates: + values.append(updates["guest_email"]) + set_clauses.append(f"guest_email = ${len(values)}") if rating_changed: if current["course_source"] == "teeoff": @@ -1074,14 +1105,15 @@ async def update_participant( resolved = await _resolve_personal_course(current["personal_course_id"]) new_tee_name = updates.get("tee_name") or current["tee_name_snapshot"] + new_gender = updates.get("gender") or current["gender"] new_handicap_index = ( updates["handicap_index"] if "handicap_index" in updates else current["handicap_index_snapshot"] ) - rating = resolved.rating_for(new_tee_name, current["gender"]) + rating = resolved.rating_for(new_tee_name, new_gender) if rating is None: raise app_error( 400, "VALIDATION_FAILED", - "Valgt utslag har ingen rating for spillerens registrerte kjønn.", + "Valgt utslag har ingen rating for spillerens (nye) kjønn.", ) course_rating, slope_rating, tee_par = rating course_handicap_snapshot = ( @@ -1101,6 +1133,9 @@ async def update_participant( set_clauses.append(f"tee_par_snapshot = ${len(values)}") values.append(course_handicap_snapshot) set_clauses.append(f"course_handicap_snapshot = ${len(values)}") + if "gender" in updates: + values.append(new_gender) + set_clauses.append(f"gender = ${len(values)}") values.append(participant_id) updated_id = await conn.fetchval( @@ -1109,7 +1144,7 @@ async def update_participant( ) row = await conn.fetchrow( """ - SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, + SELECT rp.id::text AS id, rp.user_id::text AS user_id, rp.guest_name, rp.guest_email, COALESCE(rp.guest_name, au.display_name, 'Medspiller') AS display_name, rp.is_owner, rp.gender, rp.tee_name_snapshot, rp.handicap_index_snapshot::float AS handicap_index_snapshot, diff --git a/frontend/components/round-detail.tsx b/frontend/components/round-detail.tsx index ee39e0c..4c4463b 100644 --- a/frontend/components/round-detail.tsx +++ b/frontend/components/round-detail.tsx @@ -813,6 +813,14 @@ export function RoundDetail({ roundId }: { roundId: string }) {

Hull {hole.holeNumber} · Par {hole.par} · Hcp {hole.index} + {/* Mottatte slag for AKTIV spiller på dette hullet (2026-07-26) + -- samme allerede hentede strokes_received som ScoreSoFar/ + leaderboardet bruker til netto, bare aldri vist FØR + scoring her. Golfvis fortegn (ekte minustegn), kun vist + når spilleren faktisk mottar minst ett slag her. */} + {currentApiHole?.strokes_received != null && currentApiHole.strokes_received > 0 && ( + <> · −{currentApiHole.strokes_received} + )}

{showGir && (