diff --git a/.claude/settings.json b/.claude/settings.json index d08f480..7cf6d0b 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -14,7 +14,8 @@ "Bash(rm -rf /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/zip19)", "Bash(mkdir -p /tmp/claude-1000/-opt-teecup/a8bd2fc3-4b9c-4682-a2be-cf36e143de78/scratchpad/zip19)", "Bash(python3 -m zipfile -e \"/opt/teecup/tee-cup-login-screen \\(19\\).zip\" .)", - "Bash(curl -s -o /dev/null -w \"my-friends: %{http_code}\\\\n\" https://teecup.teeoff.no/my-friends)" + "Bash(curl -s -o /dev/null -w \"my-friends: %{http_code}\\\\n\" https://teecup.teeoff.no/my-friends)", + "Bash(python3 test_display_name.py)" ] } } diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index 5a0f03d..37f4ddd 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -3071,7 +3071,12 @@ Disse må avklares før eller under de relevante fasene: 3. **Prising:** Per organisasjon (abonnement) eller per turnering? Påvirker ikke isolasjonsmodellen, men påvirker fakturerings-/kvotemodell. 4. **Scramble-grensesnitt:** Arkitekturen skal ta høyde for formatet; eksakt - UI-løsning spesifiseres senere. + UI-løsning spesifiseres senere. **Utvidet 2026-07-25:** brukeren ba om + statistikk over hvor mange utslag hver spiller har hatt (dvs. hvor + mange ganger spillerens drive ble valgt) — se egen seksjon i + FEATURE_BACKLOG.md ("Scramble: statistikk over utslag brukt per + spiller") for full detalj og åpne spørsmål. Sannsynligvis samme + problemstilling for greensome. 5. **Individuell-vs-delt-ball i `hole_score`:** Håndheves i app-laget, ikke av databasen (CHECK når ikke opp til `session.format`). Motoren/API-et må passe på at f.eks. et foursome ikke får per-spiller-scorer. diff --git a/CLAUDE.md b/CLAUDE.md index 8937ddd..0a64d66 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2711,6 +2711,62 @@ Ferdig og verifisert: 200, `teeoff.no` upåvirket. **ADR-036 fase 1 (venner-kjernen) er dermed helt ferdig, backend + frontend, live.** +- **Notat 2026-07-25: scramble-statistikk (utslag brukt per spiller) — + IKKE bygget, kun fanget opp.** Brukeren ba om at scramble-turneringer + skal føre statistikk over hvor mange ganger hver spillers utslag ble + valgt av laget. Dette er reelt NY datamodell — dagens `hole_score`/ + `match_hole_result` for scramble er en delt rad per side, ingen + kobling til HVILKEN spiller sitt utslag ble brukt. Trolig samme + problemstilling for greensome. Krysset mot det allerede eksisterende + "Scramble-grensesnitt"-punktet i ARCHITECTURE_DECISIONS.md sin "Åpne + spørsmål"-seksjon, full detalj i FEATURE_BACKLOG.md. + +- **Bug fikset: `display_name` synkroniserte aldri med for-/etternavn + (2026-07-25), rapportert av bruker med skjermbilde av dashbordet.** + `app_user.display_name` settes i dag KUN fra e-postens lokaldel ved + kontoopprettelse (`verify_magic_link`) — ADR-031s profil-fullføring la + til atskilte `first_name`/`last_name`-felt, men rørte aldri + `display_name`. Konsekvens: en bruker med fullstendig utfylt profil + viste fortsatt e-post-avledet plassholdernavn overalt `display_name` + brukes (org-medlemslister, invitasjons-e-post, dashbord-hilsen — ikke + bare dashbordet). **Fikset** i `app/routers/auth.py` sin + `update_profile`: synkroniserer nå `display_name` automatisk til + `"{first_name} {last_name}"` hver gang et av de to feltene endres via + `PATCH /auth/profile` — KUN når begge er satt etterpå (unngår et + halvferdig navn ved delvis utfylling). Scratch-verifisert (7/7 sjekker: + fersk konto får fortsatt e-post-plassholder, delvis utfylling (kun + fornavn) rører IKKE `display_name` ennå, komplett for-/etternavn + synkroniserer korrekt, urelaterte PATCH-er (f.eks. HCP) lar + `display_name` stå urørt, en SENERE navneendring re-synkroniserer på + nytt). + **Rullet ut mot ekte systemer 2026-07-25**, bruker bekreftet + eksplisitt (valgte "kjør begge deler"): `teecup_api` redeployet, samt + en engangs data-rettelse kjørt direkte mot ekte `teecup_db` + (`UPDATE app_user SET display_name = ... WHERE first_name/last_name + utfylt OG display_name avvek`) for å rette allerede-utfylte kontoer + som ikke ville blitt rettet av seg selv (krever en NY navneendring for + å trigge synk-koden). Bekreftet FØR kjøring med en dry-run `SELECT` + (2 kontoer berørt: `erolhaagenrud@gmail.com` → "Tore Morell", + `hei@erol.no` → "Erol Haagenrud"), kjørt med `RETURNING` for å + bekrefte nøyaktig hvilke rader som ble endret, `test_isolation.sql` + fortsatt 12/12 etterpå. Ingen migrasjon (ren datarettelse + kodefiks). + +- **Varsler (push til telefon + in-app varslingssenter) — DESIGNET + 2026-07-25, IKKE bygget.** Brukeren spurte om dagens PWA kan varsle + telefonens eget system (f.eks. ny venneforespørsel), og ba om en + in-app-fallback (bjelle-indikator + uleste-side) med et V0-prompt klart + "i tilfelle". Svar: ekte push er teknisk mulig (ADR-028s PWA-fundament), + men iOS Safari krever PWA-en installert til hjemskjermen for at Web + Push skal fungere i det hele tatt, pluss ny infrastruktur (VAPID, + `push_subscription`-tabell, `pywebpush`-utsending, eksplisitt + tillatelse) — anbefalt som EGEN, senere runde, ikke første steg. + Anbefalte i stedet et in-app varslingssenter først (ny + `notification`-tabell, triggerpunkter ved venneforespørsel sendt/ + akseptert, ny `/my-notifications`-rute — ikke `/notifications`, samme + kollisjonsklasse unngått som `/rounds`/`/friends`). V0-prompt skrevet + i FEATURE_BACKLOG.md, ikke sendt ennå. Ingen kode skrevet — ren + design-/dokumentasjonsrunde. + Neste steg: 1. **Klart for bygging, venter på brukerens go-ahead:** dashbord-redesign (ADR-035) + venner/kategorisert deling (ADR-036), begge designet diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md index 957e7be..fd1e485 100644 --- a/FEATURE_BACKLOG.md +++ b/FEATURE_BACKLOG.md @@ -576,6 +576,118 @@ --- +## 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: ren design-/dokumentasjonsrunde, ingen kode skrevet** — venter på +brukerens beslutning om (a) bygge in-app-senteret først, (b) sende +V0-prompten, eller (c) også gå videre med ekte push-varsler som en egen, +senere runde. + +--- + +## 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 | diff --git a/Screenshot_20260726-054841.png b/Screenshot_20260726-054841.png new file mode 100644 index 0000000..fa76649 Binary files /dev/null and b/Screenshot_20260726-054841.png differ diff --git a/Screenshot_20260726-054853.png b/Screenshot_20260726-054853.png new file mode 100644 index 0000000..182793c Binary files /dev/null and b/Screenshot_20260726-054853.png differ diff --git a/app/routers/auth.py b/app/routers/auth.py index 83fab00..e889e3f 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -834,6 +834,24 @@ async def update_profile( *values, ) + # `display_name` er brukt BREDT (org-medlemslister, invitasjons- + # e-post, dashbord-hilsen) og settes i dag KUN fra e-postens + # lokaldel ved kontoopprettelse (verify_magic_link) -- ADR-031s + # profil-fullføring la til atskilte first_name/last_name-felt, men + # rørte aldri display_name. Konsekvens (rapportert av bruker + # 2026-07-25, egen konto): en bruker med fullstendig utfylt profil + # viste fortsatt e-post-avledet plassholdernavn overalt. Fikset her + # ved å synkronisere display_name automatisk hver gang for-/ + # etternavn endres -- KUN når begge faktisk er satt etterpå (unngår + # et halvferdig "Fornavn None"-navn ved delvis utfylling). + if "first_name" in updates or "last_name" in updates: + name_row = await conn.fetchrow( + "SELECT first_name, last_name FROM app_user WHERE id = $1", user.user_id + ) + if name_row and name_row["first_name"] and name_row["last_name"]: + full_name = f"{name_row['first_name']} {name_row['last_name']}".strip() + await conn.execute("UPDATE app_user SET display_name = $1 WHERE id = $2", full_name, user.user_id) + if "handicap_index" in updates: new_hcp = updates["handicap_index"] # Kun faktiske tallverdier logges (ikke nullstilling) -- en